From f877b48684567bfa1a4a6a9ad5fb7d92e548db0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 21 Sep 2024 13:25:52 +0200 Subject: [PATCH] Fix editor crashes when images/sounds cannot be loaded Show error messages when loading maps, when exporting images/sounds and when embedding images, if images/sounds could not be loaded. Images and sounds not being loaded is supported so the editor can be used to fix maps by removing/replacing the images/sounds. Saving maps is prevented if embedded images/sounds could not be loaded, as the data is required to save the map. --- src/game/editor/editor.cpp | 6 +++- src/game/editor/editor.h | 3 +- src/game/editor/mapitems/map_io.cpp | 53 +++++++++++++++++++++++++++-- src/game/editor/popups.cpp | 15 ++++++++ 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 77a42d46882..c527a6af9a3 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -8720,7 +8720,11 @@ bool CEditor::Save(const char *pFilename) if(std::any_of(std::begin(m_WriterFinishJobs), std::end(m_WriterFinishJobs), [pFilename](const std::shared_ptr &Job) { return str_comp(pFilename, Job->GetRealFileName()) == 0; })) return false; - return m_Map.Save(pFilename); + const auto &&ErrorHandler = [this](const char *pErrorMessage) { + ShowFileDialogError("%s", pErrorMessage); + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor/save", pErrorMessage); + }; + return m_Map.Save(pFilename, ErrorHandler); } bool CEditor::HandleMapDrop(const char *pFileName, int StorageType) diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index 86521f5618b..39cf313b7f1 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -200,7 +200,8 @@ class CEditorMap void CreateDefault(IGraphics::CTextureHandle EntitiesTexture); // io - bool Save(const char *pFilename); + bool Save(const char *pFilename, const std::function &ErrorHandler); + bool PerformPreSaveSanityChecks(const std::function &ErrorHandler); bool Load(const char *pFilename, int StorageType, const std::function &ErrorHandler); void PerformSanityChecks(const std::function &ErrorHandler); diff --git a/src/game/editor/mapitems/map_io.cpp b/src/game/editor/mapitems/map_io.cpp index 2ad4c696632..2ce402589cb 100644 --- a/src/game/editor/mapitems/map_io.cpp +++ b/src/game/editor/mapitems/map_io.cpp @@ -34,7 +34,7 @@ struct CSoundSource_DEPRECATED int m_SoundEnvOffset; }; -bool CEditorMap::Save(const char *pFileName) +bool CEditorMap::Save(const char *pFileName, const std::function &ErrorHandler) { char aFileNameTmp[IO_MAX_PATH_LENGTH]; IStorage::FormatTmpPath(aFileNameTmp, sizeof(aFileNameTmp), pFileName); @@ -42,11 +42,17 @@ bool CEditorMap::Save(const char *pFileName) char aBuf[IO_MAX_PATH_LENGTH + 64]; str_format(aBuf, sizeof(aBuf), "saving to '%s'...", aFileNameTmp); m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor", aBuf); + + if(!PerformPreSaveSanityChecks(ErrorHandler)) + { + return false; + } + CDataFileWriter Writer; if(!Writer.Open(m_pEditor->Storage(), aFileNameTmp)) { - str_format(aBuf, sizeof(aBuf), "failed to open file '%s'...", aFileNameTmp); - m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "editor", aBuf); + str_format(aBuf, sizeof(aBuf), "Error: Failed to open file '%s' for writing.", aFileNameTmp); + ErrorHandler(aFileNameTmp); return false; } @@ -402,11 +408,42 @@ bool CEditorMap::Save(const char *pFileName) return true; } +bool CEditorMap::PerformPreSaveSanityChecks(const std::function &ErrorHandler) +{ + bool Success = true; + char aErrorMessage[256]; + + for(const std::shared_ptr &pImage : m_vpImages) + { + if(!pImage->m_External && pImage->m_pData == nullptr) + { + str_format(aErrorMessage, sizeof(aErrorMessage), "Error: Saving is not possible because the image '%s' could not be loaded. Remove or replace this image.", pImage->m_aName); + ErrorHandler(aErrorMessage); + Success = false; + } + } + + for(const std::shared_ptr &pSound : m_vpSounds) + { + if(pSound->m_pData == nullptr) + { + str_format(aErrorMessage, sizeof(aErrorMessage), "Error: Saving is not possible because the sound '%s' could not be loaded. Remove or replace this sound.", pSound->m_aName); + ErrorHandler(aErrorMessage); + Success = false; + } + } + + return Success; +} + bool CEditorMap::Load(const char *pFileName, int StorageType, const std::function &ErrorHandler) { CDataFileReader DataFile; if(!DataFile.Open(m_pEditor->Storage(), pFileName, StorageType)) + { + ErrorHandler("Error: Failed to open map file. See local console for details."); return false; + } // check version const CMapItemVersion *pItemVersion = static_cast(DataFile.FindItem(MAPITEMTYPE_VERSION, 0)); @@ -518,6 +555,11 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio pImg->m_External = 1; pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(*pImg, TextureLoadFlag, aBuf); } + else + { + str_format(aBuf, sizeof(aBuf), "Error: Failed to load external image '%s'.", pImg->m_aName); + ErrorHandler(aBuf); + } } else { @@ -578,6 +620,11 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio { pSound->m_SoundId = m_pEditor->Sound()->LoadOpusFromMem(pSound->m_pData, pSound->m_DataSize, true); } + else + { + str_format(aBuf, sizeof(aBuf), "Error: Failed to load external sound '%s'.", pSound->m_aName); + ErrorHandler(aBuf); + } } else { diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index e251ff240db..8f13151d12b 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -1655,6 +1655,11 @@ CUi::EPopupMenuFunctionResult CEditor::PopupImage(void *pContext, CUIRect View, { if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Embed", 0, &Slot, 0, "Embeds the image into the map file.")) { + if(pImg->m_pData == nullptr) + { + pEditor->ShowFileDialogError("Embedding is not possible because the image could not be loaded."); + return CUi::POPUP_KEEP_OPEN; + } pImg->m_External = 0; return CUi::POPUP_CLOSE_CURRENT; } @@ -1730,6 +1735,11 @@ CUi::EPopupMenuFunctionResult CEditor::PopupImage(void *pContext, CUIRect View, View.HSplitTop(RowHeight, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_ExportButton, "Export", 0, &Slot, 0, "Export the image")) { + if(pImg->m_pData == nullptr) + { + pEditor->ShowFileDialogError("Exporting is not possible because the image could not be loaded."); + return CUi::POPUP_KEEP_OPEN; + } pEditor->InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_IMG, "Save image", "Save", "mapres", false, CallbackSaveImage, pEditor); pEditor->m_FileDialogFileNameInput.Set(pImg->m_aName); return CUi::POPUP_CLOSE_CURRENT; @@ -1825,6 +1835,11 @@ CUi::EPopupMenuFunctionResult CEditor::PopupSound(void *pContext, CUIRect View, View.HSplitTop(RowHeight, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_ExportButton, "Export", 0, &Slot, 0, "Export sound")) { + if(pSound->m_pData == nullptr) + { + pEditor->ShowFileDialogError("Exporting is not possible because the sound could not be loaded."); + return CUi::POPUP_KEEP_OPEN; + } pEditor->InvokeFileDialog(IStorage::TYPE_SAVE, FILETYPE_SOUND, "Save sound", "Save", "mapres", false, CallbackSaveSound, pEditor); pEditor->m_FileDialogFileNameInput.Set(pSound->m_aName); return CUi::POPUP_CLOSE_CURRENT;