From 34a7d6fcc40150488e95ae9352ce808a9990ddf6 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Thu, 14 May 2026 21:38:02 +0200 Subject: [PATCH 1/5] Moved 'CRCInfo' class from .cpp to .h file. --- .../Code/GameEngine/Include/Common/Recorder.h | 75 ++++++++++++++++++- .../GameEngine/Source/Common/Recorder.cpp | 75 ------------------- 2 files changed, 74 insertions(+), 76 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index 142a04ececd..0ed1f362515 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -53,7 +53,80 @@ enum RecorderModeType CPP_11(: Int) { RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games }; -class CRCInfo; +// TheSuperHackers @info helmutbuhler 03/04/2025 +// Some info about CRC: +// In each game, each peer periodically calculates a CRC from the local gamestate and sends that +// in a message to all peers (including itself) so that everyone can check that the crc is synchronous. +// In a network game, there is a delay between sending the CRC message and receiving it. This is +// necessary because if you were to wait each frame for all messages from all peers, things would go +// horribly slow. +// But this delay is not a problem for CRC checking because everyone receives the CRC in the same frame +// and every peer just makes sure all the received CRCs are equal. +// While playing replays, this is a problem however: The CRC messages in the replays appear on the frame +// they were received, which can be a few frames delayed if it was a network game. And if we were to +// compare those with the local gamestate, they wouldn't sync up. +// So, in order to fix this, we need to queue up our local CRCs, +// so that we can check it with the crc messages that come later. +// This class is basically that queue. +class CRCInfo +{ +public: + CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer); + void addCRC(UnsignedInt val); + UnsignedInt readCRC(); + + int GetQueueSize() const { return m_data.size(); } + + UnsignedInt getLocalPlayer() { return m_localPlayer; } + + void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; } + Bool sawCRCMismatch() const { return m_sawCRCMismatch; } + +protected: + + Bool m_sawCRCMismatch; + Bool m_skippedOne; + std::list m_data; + UnsignedInt m_localPlayer; +}; + +CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) +{ + m_localPlayer = localPlayer; + m_skippedOne = !isMultiplayer; + m_sawCRCMismatch = FALSE; +} + +void CRCInfo::addCRC(UnsignedInt val) +{ + // TheSuperHackers @fix helmutbuhler 03/04/2025 + // In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network. + // Perhaps this happens because the network is not yet set up on frame 0. + // So we also don't queue up the first local crc message, otherwise the crc + // messages wouldn't match up anymore and we'd desync immediately during playback. + if (!m_skippedOne) + { + m_skippedOne = TRUE; + return; + } + + m_data.push_back(val); + //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty())); +} + +UnsignedInt CRCInfo::readCRC() +{ + if (m_data.empty()) + { + DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size())); + return 0; + } + + UnsignedInt val = m_data.front(); + m_data.pop_front(); + //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size())); + return val; +} class RecorderClass : public SubsystemInterface { public: diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index 7e38c456b89..2b90e0af3d9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -935,81 +935,6 @@ AsciiString RecorderClass::getCurrentReplayFilename() return AsciiString::TheEmptyString; } -// TheSuperHackers @info helmutbuhler 03/04/2025 -// Some info about CRC: -// In each game, each peer periodically calculates a CRC from the local gamestate and sends that -// in a message to all peers (including itself) so that everyone can check that the crc is synchronous. -// In a network game, there is a delay between sending the CRC message and receiving it. This is -// necessary because if you were to wait each frame for all messages from all peers, things would go -// horribly slow. -// But this delay is not a problem for CRC checking because everyone receives the CRC in the same frame -// and every peer just makes sure all the received CRCs are equal. -// While playing replays, this is a problem however: The CRC messages in the replays appear on the frame -// they were received, which can be a few frames delayed if it was a network game. And if we were to -// compare those with the local gamestate, they wouldn't sync up. -// So, in order to fix this, we need to queue up our local CRCs, -// so that we can check it with the crc messages that come later. -// This class is basically that queue. -class CRCInfo -{ -public: - CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer); - void addCRC(UnsignedInt val); - UnsignedInt readCRC(); - - int GetQueueSize() const { return m_data.size(); } - - UnsignedInt getLocalPlayer() { return m_localPlayer; } - - void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; } - Bool sawCRCMismatch() const { return m_sawCRCMismatch; } - -protected: - - Bool m_sawCRCMismatch; - Bool m_skippedOne; - std::list m_data; - UnsignedInt m_localPlayer; -}; - -CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) -{ - m_localPlayer = localPlayer; - m_skippedOne = !isMultiplayer; - m_sawCRCMismatch = FALSE; -} - -void CRCInfo::addCRC(UnsignedInt val) -{ - // TheSuperHackers @fix helmutbuhler 03/04/2025 - // In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network. - // Perhaps this happens because the network is not yet set up on frame 0. - // So we also don't queue up the first local crc message, otherwise the crc - // messages wouldn't match up anymore and we'd desync immediately during playback. - if (!m_skippedOne) - { - m_skippedOne = TRUE; - return; - } - - m_data.push_back(val); - //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty())); -} - -UnsignedInt CRCInfo::readCRC() -{ - if (m_data.empty()) - { - DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size())); - return 0; - } - - UnsignedInt val = m_data.front(); - m_data.pop_front(); - //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size())); - return val; -} - Bool RecorderClass::sawCRCMismatch() const { return m_crcInfo->sawCRCMismatch(); From 59ae549b0d94f8787456b912dd58a11bc7b38c11 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Thu, 14 May 2026 21:46:19 +0200 Subject: [PATCH 2/5] Fixed compilation. --- GeneralsMD/Code/GameEngine/Include/Common/Recorder.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index 0ed1f362515..377501d8d12 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -90,14 +90,14 @@ class CRCInfo UnsignedInt m_localPlayer; }; -CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) +inline CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) { m_localPlayer = localPlayer; m_skippedOne = !isMultiplayer; m_sawCRCMismatch = FALSE; } -void CRCInfo::addCRC(UnsignedInt val) +inline void CRCInfo::addCRC(UnsignedInt val) { // TheSuperHackers @fix helmutbuhler 03/04/2025 // In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network. @@ -114,7 +114,7 @@ void CRCInfo::addCRC(UnsignedInt val) //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty())); } -UnsignedInt CRCInfo::readCRC() +inline UnsignedInt CRCInfo::readCRC() { if (m_data.empty()) { From c5e3079c083a9ee040979438bfe439a11f8e9dd2 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Thu, 14 May 2026 21:48:12 +0200 Subject: [PATCH 3/5] Changed 'm_crcInfo' from 'CRCInfo*' to 'CRCInfo'. This avoids an unnecessary dynamic memory allocation and fixes a memory leak. --- .../Code/GameEngine/Include/Common/Recorder.h | 8 ++++++-- .../GameEngine/Source/Common/Recorder.cpp | 20 +++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index 377501d8d12..df149e0b9b2 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -71,6 +71,11 @@ enum RecorderModeType CPP_11(: Int) { class CRCInfo { public: + CRCInfo() : + m_sawCRCMismatch(FALSE), + m_skippedOne(FALSE), + m_localPlayer(0) + {} CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer); void addCRC(UnsignedInt val); UnsignedInt readCRC(); @@ -83,7 +88,6 @@ class CRCInfo Bool sawCRCMismatch() const { return m_sawCRCMismatch; } protected: - Bool m_sawCRCMismatch; Bool m_skippedOne; std::list m_data; @@ -160,7 +164,7 @@ class RecorderClass : public SubsystemInterface { public: void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback); protected: - CRCInfo *m_crcInfo; + CRCInfo m_crcInfo; public: // read in info relating to a replay, conditionally setting up m_file for playback diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index 2b90e0af3d9..c765b13e2ed 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -937,7 +937,7 @@ AsciiString RecorderClass::getCurrentReplayFilename() Bool RecorderClass::sawCRCMismatch() const { - return m_crcInfo->sawCRCMismatch(); + return m_crcInfo.sawCRCMismatch(); } void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback) @@ -945,11 +945,11 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f if (fromPlayback) { //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Adding CRC of %X from %d to m_crcInfo", newCRC, playerIndex)); - m_crcInfo->addCRC(newCRC); + m_crcInfo.addCRC(newCRC); return; } - Int localPlayerIndex = m_crcInfo->getLocalPlayer(); + Int localPlayerIndex = m_crcInfo.getLocalPlayer(); Bool samePlayer = FALSE; AsciiString playerName; playerName.format("player%d", localPlayerIndex); @@ -958,10 +958,10 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f samePlayer = TRUE; if (samePlayer || (localPlayerIndex < 0)) { - UnsignedInt playbackCRC = m_crcInfo->readCRC(); + UnsignedInt playbackCRC = m_crcInfo.readCRC(); //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Comparing CRCs of InGame:%8.8X Replay:%8.8X Frame:%d from Player %d", - // playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo->GetQueueSize()-1, playerIndex)); - if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo->sawCRCMismatch()) + // playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo.GetQueueSize()-1, playerIndex)); + if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo.sawCRCMismatch()) { //Kris: Patch 1.01 November 10, 2003 (integrated changes from Matt Campbell) // Since we don't seem to have any *visible* desyncs when replaying games, but get this warning @@ -976,7 +976,7 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f // TheSuperHackers @info helmutbuhler 03/04/2025 // Note: We subtract the queue size from the frame number. This way we calculate the correct frame // the mismatch first happened in case the NetCRCInterval is set to 1 during the game. - const UnsignedInt mismatchFrame = TheGameLogic->getFrame() - m_crcInfo->GetQueueSize() - 1; + const UnsignedInt mismatchFrame = TheGameLogic->getFrame() - m_crcInfo.GetQueueSize() - 1; // Now also prints a UI message for it. const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE("GUI:CRCMismatchDetails", L"InGame:%8.8X Replay:%8.8X Frame:%d"); @@ -998,7 +998,7 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f TheGameLogic->setGamePaused(pause, pauseMusic, pauseInput); // Mark this mismatch as seen when we had the chance to pause once. - m_crcInfo->setSawCRCMismatch(); + m_crcInfo.setSawCRCMismatch(); } } return; @@ -1120,9 +1120,9 @@ Bool RecorderClass::playbackFile(AsciiString filename) #endif Bool isMultiplayer = m_gameInfo.getSlot(header.localPlayerIndex)->getIP() != 0; - m_crcInfo = NEW CRCInfo(header.localPlayerIndex, isMultiplayer); + m_crcInfo = CRCInfo(header.localPlayerIndex, isMultiplayer); REPLAY_CRC_INTERVAL = m_gameInfo.getCRCInterval(); - DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo->getLocalPlayer(), REPLAY_CRC_INTERVAL)); + DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo.getLocalPlayer(), REPLAY_CRC_INTERVAL)); Int difficulty = 0; m_file->read(&difficulty, sizeof(difficulty)); From 2eaa0206fca91c1009601e548248d6b69865db76 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Fri, 15 May 2026 20:41:52 +0200 Subject: [PATCH 4/5] Moved functions to .cpp file. --- .../GameEngine/Source/Common/Recorder.cpp | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index c765b13e2ed..ad1783583b2 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -100,6 +100,50 @@ static FILE* openStatsLogFile() } #endif +RecorderClass::CRCInfo::CRCInfo() : + m_sawCRCMismatch(FALSE), + m_skippedOne(FALSE), + m_localPlayer(0) +{} + +RecorderClass::CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) +{ + m_sawCRCMismatch = FALSE; + m_skippedOne = !isMultiplayer; + m_localPlayer = localPlayer; +} + +void RecorderClass::CRCInfo::addCRC(UnsignedInt val) +{ + // TheSuperHackers @fix helmutbuhler 03/04/2025 + // In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network. + // Perhaps this happens because the network is not yet set up on frame 0. + // So we also don't queue up the first local crc message, otherwise the crc + // messages wouldn't match up anymore and we'd desync immediately during playback. + if (!m_skippedOne) + { + m_skippedOne = TRUE; + return; + } + + m_data.push_back(val); + //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty())); +} + +UnsignedInt RecorderClass::CRCInfo::readCRC() +{ + if (m_data.empty()) + { + DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size())); + return 0; + } + + UnsignedInt val = m_data.front(); + m_data.pop_front(); + //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size())); + return val; +} + void RecorderClass::logGameStart(AsciiString options) { if (!m_file) From ed1352da2c5e55eb0a9dcc03f0cdc682b1d98036 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Fri, 15 May 2026 20:47:24 +0200 Subject: [PATCH 5/5] Made 'CRCInfo' class part of 'RecorderClass' class, and made some tweaks to the latter. --- .../Code/GameEngine/Include/Common/Recorder.h | 111 ++++++------------ 1 file changed, 34 insertions(+), 77 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index df149e0b9b2..f306752ef47 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -53,90 +53,50 @@ enum RecorderModeType CPP_11(: Int) { RECORDERMODETYPE_NONE // this is a valid state to be in on the shell map, or in saved games }; -// TheSuperHackers @info helmutbuhler 03/04/2025 -// Some info about CRC: -// In each game, each peer periodically calculates a CRC from the local gamestate and sends that -// in a message to all peers (including itself) so that everyone can check that the crc is synchronous. -// In a network game, there is a delay between sending the CRC message and receiving it. This is -// necessary because if you were to wait each frame for all messages from all peers, things would go -// horribly slow. -// But this delay is not a problem for CRC checking because everyone receives the CRC in the same frame -// and every peer just makes sure all the received CRCs are equal. -// While playing replays, this is a problem however: The CRC messages in the replays appear on the frame -// they were received, which can be a few frames delayed if it was a network game. And if we were to -// compare those with the local gamestate, they wouldn't sync up. -// So, in order to fix this, we need to queue up our local CRCs, -// so that we can check it with the crc messages that come later. -// This class is basically that queue. -class CRCInfo -{ -public: - CRCInfo() : - m_sawCRCMismatch(FALSE), - m_skippedOne(FALSE), - m_localPlayer(0) - {} - CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer); - void addCRC(UnsignedInt val); - UnsignedInt readCRC(); - - int GetQueueSize() const { return m_data.size(); } - - UnsignedInt getLocalPlayer() { return m_localPlayer; } - - void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; } - Bool sawCRCMismatch() const { return m_sawCRCMismatch; } - +class RecorderClass : public SubsystemInterface { protected: - Bool m_sawCRCMismatch; - Bool m_skippedOne; - std::list m_data; - UnsignedInt m_localPlayer; -}; + // TheSuperHackers @info helmutbuhler 03/04/2025 + // Some info about CRC: + // In each game, each peer periodically calculates a CRC from the local gamestate and sends that + // in a message to all peers (including itself) so that everyone can check that the crc is synchronous. + // In a network game, there is a delay between sending the CRC message and receiving it. This is + // necessary because if you were to wait each frame for all messages from all peers, things would go + // horribly slow. + // But this delay is not a problem for CRC checking because everyone receives the CRC in the same frame + // and every peer just makes sure all the received CRCs are equal. + // While playing replays, this is a problem however: The CRC messages in the replays appear on the frame + // they were received, which can be a few frames delayed if it was a network game. And if we were to + // compare those with the local gamestate, they wouldn't sync up. + // So, in order to fix this, we need to queue up our local CRCs, + // so that we can check it with the crc messages that come later. + // This class is basically that queue. + class CRCInfo + { + public: + CRCInfo(); + CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer); + void addCRC(UnsignedInt val); + UnsignedInt readCRC(); -inline CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer) -{ - m_localPlayer = localPlayer; - m_skippedOne = !isMultiplayer; - m_sawCRCMismatch = FALSE; -} + int GetQueueSize() const { return m_data.size(); } -inline void CRCInfo::addCRC(UnsignedInt val) -{ - // TheSuperHackers @fix helmutbuhler 03/04/2025 - // In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network. - // Perhaps this happens because the network is not yet set up on frame 0. - // So we also don't queue up the first local crc message, otherwise the crc - // messages wouldn't match up anymore and we'd desync immediately during playback. - if (!m_skippedOne) - { - m_skippedOne = TRUE; - return; - } + UnsignedInt getLocalPlayer() { return m_localPlayer; } - m_data.push_back(val); - //DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)", val, m_data.size(), !m_data.empty())); -} + void setSawCRCMismatch() { m_sawCRCMismatch = TRUE; } + Bool sawCRCMismatch() const { return m_sawCRCMismatch; } -inline UnsignedInt CRCInfo::readCRC() -{ - if (m_data.empty()) - { - DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size())); - return 0; - } + protected: + Bool m_sawCRCMismatch; + Bool m_skippedOne; + UnsignedInt m_localPlayer; + std::list m_data; + }; - UnsignedInt val = m_data.front(); - m_data.pop_front(); - //DEBUG_LOG(("CRCInfo::readCRC() - returning %8.8X, full=%d, size=%d", val, !m_data.empty(), m_data.size())); - return val; -} + CRCInfo m_crcInfo; -class RecorderClass : public SubsystemInterface { public: struct ReplayHeader; -public: RecorderClass(); ///< Constructor. virtual ~RecorderClass() override; ///< Destructor. @@ -163,9 +123,6 @@ class RecorderClass : public SubsystemInterface { public: void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback); -protected: - CRCInfo m_crcInfo; -public: // read in info relating to a replay, conditionally setting up m_file for playback struct ReplayHeader