Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions Generals/Code/GameEngine/Source/Common/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1088,8 +1088,6 @@ Bool RecorderClass::playbackFile(AsciiString filename)
}
}

m_mode = RECORDERMODETYPE_PLAYBACK;

ReplayHeader header;
header.forPlayback = TRUE;
header.filename = filename;
Expand Down Expand Up @@ -1147,8 +1145,6 @@ Bool RecorderClass::playbackFile(AsciiString filename)
DEBUG_ASSERTCRASH(!exeDifferent && !iniDifferent, (debugString.str()));
#endif

TheWritableGlobalData->m_pendingFile = m_gameInfo.getMap();

#ifdef DEBUG_LOGGING
if (header.localPlayerIndex >= 0)
{
Expand Down Expand Up @@ -1181,6 +1177,13 @@ Bool RecorderClass::playbackFile(AsciiString filename)
TheCommandList->reset();

readNextFrame();
// readNextFrame() closes m_file via stopPlayback() if the first frame cannot be read.
if(m_file == nullptr)
{
return FALSE;
}

TheWritableGlobalData->m_pendingFile = m_gameInfo.getMap();

// send a message to the logic for a new game
if (!m_doingAnalysis)
Expand All @@ -1199,6 +1202,11 @@ Bool RecorderClass::playbackFile(AsciiString filename)
InitRandom( m_gameInfo.getSeed() );
}

// TheSuperHackers @bugfix bobtista 25/07/2026 Enter playback mode only once the playback is ready.
// Previously a failed open left the recorder in playback mode with a NULL m_file, and the next
// update dereferenced it, for example when the replay is deleted during the version mismatch prompt.
m_mode = RECORDERMODETYPE_PLAYBACK;
Comment thread
greptile-apps[bot] marked this conversation as resolved.

m_currentReplayFilename = filename;
m_playbackFrameCount = header.frameCount;
return TRUE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,27 @@ WindowMsgHandledType ReplayMenuInput( GameWindow *window, UnsignedInt msg,

}

static void handleReplayLoadFailure()
{
UnicodeString title = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayLoadFailedTitle", L"REPLAY CANNOT BE LOADED");
UnicodeString body = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayLoadFailed", L"The replay file could not be opened or is invalid.");

MessageBoxOk(title, body, nullptr);

GadgetListBoxReset(listboxReplayFiles);
PopulateReplayFileListbox(listboxReplayFiles);
}

static void showReplayMapNotFound()
{
UnicodeString title = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayMapNotFoundTitle", L"MAP NOT FOUND");
UnicodeString body = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayMapNotFound", L"This replay cannot be loaded because the map was not found on this device.");

MessageBoxOk(title, body, nullptr);
}

//-------------------------------------------------------------------------------------------------

void reallyLoadReplay()
{
UnicodeString filename;
Expand All @@ -564,11 +585,35 @@ void reallyLoadReplay()
AsciiString asciiFilename;
asciiFilename.translate(filename);

TheRecorder->playbackFile(asciiFilename);
// TheSuperHackers @bugfix bobtista 25/07/2026 Re-validate the replay before starting playback.
// The user can delete the file while the version mismatch prompt is open, in which case the
// listbox entry is stale. Prompts the same message box as loadReplay and refreshes the list.
RecorderClass::ReplayHeader header;
ReplayGameInfo info;
const MapMetaData *mapData;

if(parentReplayMenu != nullptr)
if(!readReplayMapInfo(asciiFilename, header, info, mapData))
{
parentReplayMenu->winHide(TRUE);
handleReplayLoadFailure();
return;
}

if(mapData == nullptr)
{
showReplayMapNotFound();
return;
}

if(TheRecorder->playbackFile(asciiFilename))
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment thread
greptile-apps[bot] marked this conversation as resolved.
{
if(parentReplayMenu != nullptr)
{
parentReplayMenu->winHide(TRUE);
}
}
else
{
handleReplayLoadFailure();
}
}

Expand All @@ -585,19 +630,13 @@ static void loadReplay(UnicodeString filename)
{
// TheSuperHackers @bugfix Prompts a message box when the replay was deleted by the user while the Replay Menu was opened.

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, nullptr);
handleReplayLoadFailure();
}
else if(mapData == nullptr)
{
// TheSuperHackers @bugfix Prompts a message box when the map used by the replay was not found.

UnicodeString title = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayMapNotFoundTitle", L"MAP NOT FOUND");
UnicodeString body = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayMapNotFound", L"This replay cannot be loaded because the map was not found on this device.");

MessageBoxOk(title, body, nullptr);
showReplayMapNotFound();
}
else if(!TheRecorder->replayMatchesGameVersion(header))
{
Expand All @@ -607,11 +646,18 @@ static void loadReplay(UnicodeString filename)
}
else
{
TheRecorder->playbackFile(asciiFilename);

if(parentReplayMenu != nullptr)
// TheSuperHackers @bugfix bobtista 25/07/2026 Keep the Replay Menu open when the playback
// could not be started, for example when the replay was deleted after it was validated above.
if(TheRecorder->playbackFile(asciiFilename))
{
parentReplayMenu->winHide(TRUE);
if(parentReplayMenu != nullptr)
{
parentReplayMenu->winHide(TRUE);
}
}
else
{
handleReplayLoadFailure();
}
}
}
Expand Down Expand Up @@ -826,4 +872,3 @@ void copyReplay()
}

}

16 changes: 12 additions & 4 deletions GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1091,8 +1091,6 @@ Bool RecorderClass::playbackFile(AsciiString filename)
}
}

m_mode = RECORDERMODETYPE_PLAYBACK;

ReplayHeader header;
header.forPlayback = TRUE;
header.filename = filename;
Expand Down Expand Up @@ -1150,8 +1148,6 @@ Bool RecorderClass::playbackFile(AsciiString filename)
DEBUG_ASSERTCRASH(!exeDifferent && !iniDifferent, (debugString.str()));
#endif

TheWritableGlobalData->m_pendingFile = m_gameInfo.getMap();

#ifdef DEBUG_LOGGING
if (header.localPlayerIndex >= 0)
{
Expand Down Expand Up @@ -1184,6 +1180,13 @@ Bool RecorderClass::playbackFile(AsciiString filename)
TheCommandList->reset();

readNextFrame();
// readNextFrame() closes m_file via stopPlayback() if the first frame cannot be read.
if(m_file == nullptr)
{
return FALSE;
}

TheWritableGlobalData->m_pendingFile = m_gameInfo.getMap();

// send a message to the logic for a new game
if (!m_doingAnalysis)
Expand All @@ -1202,6 +1205,11 @@ Bool RecorderClass::playbackFile(AsciiString filename)
InitRandom( m_gameInfo.getSeed() );
}

// TheSuperHackers @bugfix bobtista 25/07/2026 Enter playback mode only once the playback is ready.
// Previously a failed open left the recorder in playback mode with a NULL m_file, and the next
// update dereferenced it, for example when the replay is deleted during the version mismatch prompt.
m_mode = RECORDERMODETYPE_PLAYBACK;

m_currentReplayFilename = filename;
m_playbackFrameCount = header.frameCount;
return TRUE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,27 @@ WindowMsgHandledType ReplayMenuInput( GameWindow *window, UnsignedInt msg,

}

static void handleReplayLoadFailure()
{
UnicodeString title = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayLoadFailedTitle", L"REPLAY CANNOT BE LOADED");
UnicodeString body = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayLoadFailed", L"The replay file could not be opened or is invalid.");

MessageBoxOk(title, body, nullptr);

GadgetListBoxReset(listboxReplayFiles);
PopulateReplayFileListbox(listboxReplayFiles);
}

static void showReplayMapNotFound()
{
UnicodeString title = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayMapNotFoundTitle", L"MAP NOT FOUND");
UnicodeString body = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayMapNotFound", L"This replay cannot be loaded because the map was not found on this device.");

MessageBoxOk(title, body, nullptr);
}

//-------------------------------------------------------------------------------------------------

void reallyLoadReplay()
{
UnicodeString filename;
Expand All @@ -564,11 +585,35 @@ void reallyLoadReplay()
AsciiString asciiFilename;
asciiFilename.translate(filename);

TheRecorder->playbackFile(asciiFilename);
// TheSuperHackers @bugfix bobtista 25/07/2026 Re-validate the replay before starting playback.
// The user can delete the file while the version mismatch prompt is open, in which case the
// listbox entry is stale. Prompts the same message box as loadReplay and refreshes the list.
RecorderClass::ReplayHeader header;
ReplayGameInfo info;
const MapMetaData *mapData;

if(parentReplayMenu != nullptr)
if(!readReplayMapInfo(asciiFilename, header, info, mapData))
{
parentReplayMenu->winHide(TRUE);
handleReplayLoadFailure();
return;
}

if(mapData == nullptr)
{
showReplayMapNotFound();
return;
}

if(TheRecorder->playbackFile(asciiFilename))
{
if(parentReplayMenu != nullptr)
{
parentReplayMenu->winHide(TRUE);
}
}
else
{
handleReplayLoadFailure();
}
}

Expand All @@ -585,19 +630,13 @@ static void loadReplay(UnicodeString filename)
{
// TheSuperHackers @bugfix Prompts a message box when the replay was deleted by the user while the Replay Menu was opened.

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, nullptr);
handleReplayLoadFailure();
}
else if(mapData == nullptr)
{
// TheSuperHackers @bugfix Prompts a message box when the map used by the replay was not found.

UnicodeString title = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayMapNotFoundTitle", L"MAP NOT FOUND");
UnicodeString body = TheGameText->FETCH_OR_SUBSTITUTE("GUI:ReplayMapNotFound", L"This replay cannot be loaded because the map was not found on this device.");

MessageBoxOk(title, body, nullptr);
showReplayMapNotFound();
}
else if(!TheRecorder->replayMatchesGameVersion(header))
{
Expand All @@ -607,11 +646,18 @@ static void loadReplay(UnicodeString filename)
}
else
{
TheRecorder->playbackFile(asciiFilename);

if(parentReplayMenu != nullptr)
// TheSuperHackers @bugfix bobtista 25/07/2026 Keep the Replay Menu open when the playback
// could not be started, for example when the replay was deleted after it was validated above.
if(TheRecorder->playbackFile(asciiFilename))
{
parentReplayMenu->winHide(TRUE);
if(parentReplayMenu != nullptr)
{
parentReplayMenu->winHide(TRUE);
}
}
else
{
handleReplayLoadFailure();
}
}
}
Expand Down Expand Up @@ -826,4 +872,3 @@ void copyReplay()
}

}

Loading