From 35d02a2a9a2a89751dc66941dcdfdb563facd765 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Thu, 29 May 2025 15:00:20 +0200 Subject: [PATCH 1/3] [ZH] Fix crash when exiting Replay playback while a player places a beacon --- .../Code/GameEngine/Include/Common/Recorder.h | 8 +++++- .../GameEngine/Source/Common/Recorder.cpp | 26 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index be8e9c4d01e..50faaf1cfaf 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -137,7 +137,13 @@ class RecorderClass : public SubsystemInterface { void writeArgument(GameMessageArgumentDataType type, const GameMessageArgumentType arg); void readArgument(GameMessageArgumentDataType type, GameMessage *msg); - void cullBadCommands(); ///< prevent the user from giving mouse commands that he shouldn't be able to do during playback. + struct CullBadCommandsResult + { + CullBadCommandsResult() : hasClearGameDataMessage(false) {} + Bool hasClearGameDataMessage; + }; + + CullBadCommandsResult cullBadCommands(); ///< prevent the user from giving mouse commands that he shouldn't be able to do during playback. FILE *m_file; AsciiString m_fileName; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index 7e40e5f0f8d..bbe5243b5a2 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -439,8 +439,17 @@ void RecorderClass::update() { * Do the update for the next frame of this playback. */ void RecorderClass::updatePlayback() { - cullBadCommands(); // Remove any bad commands that have been inserted by the local user that shouldn't be - // executed during playback. + // Remove any bad commands that have been inserted by the local user that shouldn't be + // executed during playback. + CullBadCommandsResult result = cullBadCommands(); + + if (result.hasClearGameDataMessage) { + // TheSuperHackers @bugfix Stop appending more commands if the replay playback is about to end. + // Previously this would be able to append more commands, which could have unintended consequences, + // such as crashing the game when a MSG_PLACE_BEACON is appended after MSG_CLEAR_GAME_DATA. + // MSG_CLEAR_GAME_DATA is supposed to be processed later this frame, which will then stop this playback. + return; + } if (m_nextFrame == -1) { // This is reached if there are no more commands to be executed. @@ -1504,9 +1513,11 @@ void RecorderClass::readArgument(GameMessageArgumentDataType type, GameMessage * /** * This needs to be called for every frame during playback. Basically it prevents the user from inserting. */ -void RecorderClass::cullBadCommands() { +RecorderClass::CullBadCommandsResult RecorderClass::cullBadCommands() { + CullBadCommandsResult result; + if (m_doingAnalysis) - return; + return result; GameMessage *msg = TheCommandList->getFirstMessage(); GameMessage *next = NULL; @@ -1520,8 +1531,15 @@ void RecorderClass::cullBadCommands() { deleteInstance(msg); } + if (msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) + { + result.hasClearGameDataMessage = true; + } + msg = next; } + + return result; } /** From 093a4846e16529d2cb2a16f26c1256da8e8aea28 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Thu, 29 May 2025 15:01:13 +0200 Subject: [PATCH 2/3] Replicate in Generals --- .../Code/GameEngine/Include/Common/Recorder.h | 8 +++++- .../GameEngine/Source/Common/Recorder.cpp | 26 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Generals/Code/GameEngine/Include/Common/Recorder.h b/Generals/Code/GameEngine/Include/Common/Recorder.h index 581c5dfe72f..b276ee358b8 100644 --- a/Generals/Code/GameEngine/Include/Common/Recorder.h +++ b/Generals/Code/GameEngine/Include/Common/Recorder.h @@ -137,7 +137,13 @@ class RecorderClass : public SubsystemInterface { void writeArgument(GameMessageArgumentDataType type, const GameMessageArgumentType arg); void readArgument(GameMessageArgumentDataType type, GameMessage *msg); - void cullBadCommands(); ///< prevent the user from giving mouse commands that he shouldn't be able to do during playback. + struct CullBadCommandsResult + { + CullBadCommandsResult() : hasClearGameDataMessage(false) {} + Bool hasClearGameDataMessage; + }; + + CullBadCommandsResult cullBadCommands(); ///< prevent the user from giving mouse commands that he shouldn't be able to do during playback. FILE *m_file; AsciiString m_fileName; diff --git a/Generals/Code/GameEngine/Source/Common/Recorder.cpp b/Generals/Code/GameEngine/Source/Common/Recorder.cpp index f7339056230..4925829a36a 100644 --- a/Generals/Code/GameEngine/Source/Common/Recorder.cpp +++ b/Generals/Code/GameEngine/Source/Common/Recorder.cpp @@ -439,8 +439,17 @@ void RecorderClass::update() { * Do the update for the next frame of this playback. */ void RecorderClass::updatePlayback() { - cullBadCommands(); // Remove any bad commands that have been inserted by the local user that shouldn't be - // executed during playback. + // Remove any bad commands that have been inserted by the local user that shouldn't be + // executed during playback. + CullBadCommandsResult result = cullBadCommands(); + + if (result.hasClearGameDataMessage) { + // TheSuperHackers @bugfix Stop appending more commands if the replay playback is about to end. + // Previously this would be able to append more commands, which could have unintended consequences, + // such as crashing the game when a MSG_PLACE_BEACON is appended after MSG_CLEAR_GAME_DATA. + // MSG_CLEAR_GAME_DATA is supposed to be processed later this frame, which will then stop this playback. + return; + } if (m_nextFrame == -1) { // This is reached if there are no more commands to be executed. @@ -1501,9 +1510,11 @@ void RecorderClass::readArgument(GameMessageArgumentDataType type, GameMessage * /** * This needs to be called for every frame during playback. Basically it prevents the user from inserting. */ -void RecorderClass::cullBadCommands() { +RecorderClass::CullBadCommandsResult RecorderClass::cullBadCommands() { + CullBadCommandsResult result; + if (m_doingAnalysis) - return; + return result; GameMessage *msg = TheCommandList->getFirstMessage(); GameMessage *next = NULL; @@ -1517,8 +1528,15 @@ void RecorderClass::cullBadCommands() { deleteInstance(msg); } + if (msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) + { + result.hasClearGameDataMessage = true; + } + msg = next; } + + return result; } /** From b82c52e0cb6632effdd85077624e159481c29590 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Fri, 30 May 2025 09:47:05 +0200 Subject: [PATCH 3/3] Minor optimization in RecorderClass::cullBadCommands --- Generals/Code/GameEngine/Source/Common/Recorder.cpp | 3 +-- GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/Recorder.cpp b/Generals/Code/GameEngine/Source/Common/Recorder.cpp index 4925829a36a..9071d3a920d 100644 --- a/Generals/Code/GameEngine/Source/Common/Recorder.cpp +++ b/Generals/Code/GameEngine/Source/Common/Recorder.cpp @@ -1527,8 +1527,7 @@ RecorderClass::CullBadCommandsResult RecorderClass::cullBadCommands() { deleteInstance(msg); } - - if (msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) + else if (msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) { result.hasClearGameDataMessage = true; } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index bbe5243b5a2..3536b8504ef 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -1530,8 +1530,7 @@ RecorderClass::CullBadCommandsResult RecorderClass::cullBadCommands() { deleteInstance(msg); } - - if (msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) + else if (msg->getType() == GameMessage::MSG_CLEAR_GAME_DATA) { result.hasClearGameDataMessage = true; }