From 3362b3688a6ddabbb24ddb3b7cd19eb8180bfc74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 21 Sep 2024 13:40:08 +0200 Subject: [PATCH 1/3] Fix editor crash when saving external RGB images after embedding Always convert image data to RGBA and dilate if enabled also for external images, as images may be embedded later and should therefore always be in RGBA format. --- src/game/editor/editor.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 3cd8faa2a..77a42d468 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -4358,13 +4358,10 @@ bool CEditor::ReplaceImage(const char *pFileName, int StorageType, bool CheckDup str_copy(pImg->m_aName, aBuf); pImg->m_External = IsVanillaImage(pImg->m_aName); - if(!pImg->m_External) + ConvertToRgba(*pImg); + if(g_Config.m_ClEditorDilate == 1) { - ConvertToRgba(*pImg); - if(g_Config.m_ClEditorDilate == 1) - { - DilateImage(*pImg); - } + DilateImage(*pImg); } pImg->m_AutoMapper.Load(pImg->m_aName); @@ -4425,13 +4422,10 @@ bool CEditor::AddImage(const char *pFileName, int StorageType, void *pUser) pImg->m_pData = ImgInfo.m_pData; pImg->m_External = IsVanillaImage(aBuf); - if(!pImg->m_External) + ConvertToRgba(*pImg); + if(g_Config.m_ClEditorDilate == 1) { - ConvertToRgba(*pImg); - if(g_Config.m_ClEditorDilate == 1) - { - DilateImage(*pImg); - } + DilateImage(*pImg); } int TextureLoadFlag = pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; From aea648e44d259da2bd67ce35b9ce53656dcfd096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 21 Sep 2024 13:47:09 +0200 Subject: [PATCH 2/3] Fix incorrect image data used when loading external RGB image The data of `ImgInfo` is freed by the preceding call to `ConvertToRgba`. --- src/game/editor/mapitems/map_io.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/game/editor/mapitems/map_io.cpp b/src/game/editor/mapitems/map_io.cpp index 8322289fd..2ad4c6966 100644 --- a/src/game/editor/mapitems/map_io.cpp +++ b/src/game/editor/mapitems/map_io.cpp @@ -513,11 +513,10 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio ConvertToRgba(*pImg); int TextureLoadFlag = m_pEditor->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; - if(ImgInfo.m_Width % 16 != 0 || ImgInfo.m_Height % 16 != 0) + if(pImg->m_Width % 16 != 0 || pImg->m_Height % 16 != 0) TextureLoadFlag = 0; - pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(ImgInfo, TextureLoadFlag, aBuf); - ImgInfo.m_pData = nullptr; pImg->m_External = 1; + pImg->m_Texture = m_pEditor->Graphics()->LoadTextureRaw(*pImg, TextureLoadFlag, aBuf); } } else From 94a401226da73f91aefba3e518861647f3aa3a2b 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 3/3] 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 77a42d468..c527a6af9 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 86521f561..39cf313b7 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -200,7 +200,8 @@ public: 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 2ad4c6966..30f85c676 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(aBuf); 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 e251ff240..8f13151d1 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;