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.
This commit is contained in:
Robert Müller 2024-09-21 13:25:52 +02:00
parent aea648e44d
commit 94a401226d
4 changed files with 72 additions and 5 deletions

View file

@ -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<CDataFileWriterFinishJob> &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)

View file

@ -200,7 +200,8 @@ public:
void CreateDefault(IGraphics::CTextureHandle EntitiesTexture);
// io
bool Save(const char *pFilename);
bool Save(const char *pFilename, const std::function<void(const char *pErrorMessage)> &ErrorHandler);
bool PerformPreSaveSanityChecks(const std::function<void(const char *pErrorMessage)> &ErrorHandler);
bool Load(const char *pFilename, int StorageType, const std::function<void(const char *pErrorMessage)> &ErrorHandler);
void PerformSanityChecks(const std::function<void(const char *pErrorMessage)> &ErrorHandler);

View file

@ -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<void(const char *pErrorMessage)> &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<void(const char *pErrorMessage)> &ErrorHandler)
{
bool Success = true;
char aErrorMessage[256];
for(const std::shared_ptr<CEditorImage> &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<CEditorSound> &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<void(const char *pErrorMessage)> &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<CMapItemVersion *>(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
{

View file

@ -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;