From bf32f047a98c103fab6896fe8807ad6349f9acd2 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 11 Nov 2025 23:03:15 -0500 Subject: [PATCH 1/2] Validate replay file exists before attempting playback in version mismatch callback --- .../GUI/GUICallbacks/Menus/ReplayMenu.cpp | 14 ++++++++++++++ .../GUI/GUICallbacks/Menus/ReplayMenu.cpp | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp index 65b999a688..53bc19c654 100644 --- a/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp @@ -534,6 +534,20 @@ void reallyLoadReplay(void) AsciiString asciiFilename; asciiFilename.translate(filename); + // TheSuperHackers @bugfix bobtista Check if file still exists before attempting playback + // to prevent crash when file is deleted during version mismatch prompt + RecorderClass::ReplayHeader header; + ReplayGameInfo info; + const MapMetaData *mapData; + + if(!readReplayMapInfo(asciiFilename, header, info, mapData)) + { + UnicodeString title = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayFileNotFoundTitle", L"REPLAY NOT FOUND"); + UnicodeString body = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayFileNotFound", L"This replay cannot be loaded because the file no longer exists on this device."); + MessageBoxOk(title, body, NULL); + return; + } + TheRecorder->playbackFile(asciiFilename); if(parentReplayMenu != NULL) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp index 9a0d3751c2..28edaa49e1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/ReplayMenu.cpp @@ -534,6 +534,20 @@ void reallyLoadReplay(void) AsciiString asciiFilename; asciiFilename.translate(filename); + // TheSuperHackers @bugfix bobtista Check if file still exists before attempting playback + // to prevent crash when file is deleted during version mismatch prompt + RecorderClass::ReplayHeader header; + ReplayGameInfo info; + const MapMetaData *mapData; + + if(!readReplayMapInfo(asciiFilename, header, info, mapData)) + { + UnicodeString title = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayFileNotFoundTitle", L"REPLAY NOT FOUND"); + UnicodeString body = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayFileNotFound", L"This replay cannot be loaded because the file no longer exists on this device."); + MessageBoxOk(title, body, NULL); + return; + } + TheRecorder->playbackFile(asciiFilename); if(parentReplayMenu != NULL) From 66502a85599419099442822bb954780aa477ea88 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 11 Nov 2025 23:03:34 -0500 Subject: [PATCH 2/2] Delay playback mode assignment until after replay file opens successfully --- Generals/Code/GameEngine/Source/Common/Recorder.cpp | 5 ++++- GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/Recorder.cpp b/Generals/Code/GameEngine/Source/Common/Recorder.cpp index 04be1d230e..1026d6c10e 100644 --- a/Generals/Code/GameEngine/Source/Common/Recorder.cpp +++ b/Generals/Code/GameEngine/Source/Common/Recorder.cpp @@ -1196,7 +1196,8 @@ Bool RecorderClass::playbackFile(AsciiString filename) } } - m_mode = RECORDERMODETYPE_PLAYBACK; + // TheSuperHackers @bugfix bobtista Don't set playback mode until after file opens successfully + // to prevent crash if playback is updated while m_file is NULL ReplayHeader header; header.forPlayback = TRUE; @@ -1207,6 +1208,8 @@ Bool RecorderClass::playbackFile(AsciiString filename) return FALSE; } + m_mode = RECORDERMODETYPE_PLAYBACK; + #ifdef DEBUG_CRASHING Bool versionStringDiff = header.versionString != TheVersion->getUnicodeVersion(); Bool versionTimeStringDiff = header.versionTimeString != TheVersion->getUnicodeBuildTime(); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index a9d064db43..821f0ff5c6 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -1199,7 +1199,8 @@ Bool RecorderClass::playbackFile(AsciiString filename) } } - m_mode = RECORDERMODETYPE_PLAYBACK; + // TheSuperHackers @bugfix bobtista Don't set playback mode until after file opens successfully + // to prevent crash if playback is updated while m_file is NULL ReplayHeader header; header.forPlayback = TRUE; @@ -1210,6 +1211,8 @@ Bool RecorderClass::playbackFile(AsciiString filename) return FALSE; } + m_mode = RECORDERMODETYPE_PLAYBACK; + #ifdef DEBUG_CRASHING Bool versionStringDiff = header.versionString != TheVersion->getUnicodeVersion(); Bool versionTimeStringDiff = header.versionTimeString != TheVersion->getUnicodeBuildTime();