From b2e84cdc9fd36b2acf7f501cbd1c51c05b1fac2b Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Thu, 23 Apr 2026 06:45:21 +0200 Subject: [PATCH] feat(replay): Check CRC messages from all players in replays. --- .../GameEngine/Include/Common/GlobalData.h | 2 + .../Code/GameEngine/Include/Common/Player.h | 2 +- .../Code/GameEngine/Include/Common/Recorder.h | 64 +++- .../GameEngine/Include/GameLogic/GameLogic.h | 9 +- .../GameEngine/Source/Common/CommandLine.cpp | 10 + .../GameEngine/Source/Common/GlobalData.cpp | 1 + .../GameEngine/Source/Common/Recorder.cpp | 319 +++++++++++------- .../Source/GameLogic/System/GameLogic.cpp | 109 +++--- .../GameLogic/System/GameLogicDispatch.cpp | 41 ++- 9 files changed, 359 insertions(+), 198 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 53ae5d7459a..a947cb590e2 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -367,6 +367,8 @@ class GlobalData : public SubsystemInterface Bool m_afterIntro; ///< we need to tell the game our intro is done Bool m_allowExitOutOfMovies; ///< flag to allow exit out of movies only after the Intro has played + Bool m_replayOnlyCheckLocalPlayer; ///< flag to check only the CRC messages from the player that recorded a replay + Bool m_loadScreenRender; ///< flag to disallow rendering of almost everything during a loadscreen Real m_keyboardScrollFactor; ///< Factor applied to game scrolling speed via keyboard scrolling diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Player.h b/GeneralsMD/Code/GameEngine/Include/Common/Player.h index 5808a9bfa6a..b6155bf9d94 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Player.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Player.h @@ -221,7 +221,7 @@ class Player : public Snapshot void deletePlayerAI(); - UnicodeString getPlayerDisplayName() { return m_playerDisplayName; } + UnicodeString getPlayerDisplayName() const { return m_playerDisplayName; } NameKeyType getPlayerNameKey() const { return m_playerNameKey; } AsciiString getSide() const { return m_side; } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h index 142a04ececd..114dfd86702 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Recorder.h @@ -53,7 +53,61 @@ 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: + struct MismatchData + { + MismatchData() : + mismatched(FALSE), + playerIndex(0), + queueSize(0), + playbackCRC(0), + playerCRC(0) + {} + + Bool mismatched; + Byte playerIndex; + UnsignedShort queueSize; + UnsignedInt playbackCRC; + UnsignedInt playerCRC; + }; + + CRCInfo(); + void init(Bool isMultiplayer, Int localPlayerIndex); + void addPlaybackCRC(UnsignedInt val); + void addPlayerCRC(Int playerIndex, UnsignedInt val); + void setSawCRCMismatch(); + Bool sawCRCMismatch() const; + Byte getLocalPlayerIndex() const; + MismatchData getMismatchData(); + + static Int getPlayerIndexOffset(); + +protected: + UnsignedInt getLargestPlayerQueueSize() const; + + Bool m_skippedOne; + Bool m_sawCRCMismatch; + Byte m_localPlayerIndex; + std::list m_playbackData; + std::list m_playerData[MAX_SLOTS]; +}; class RecorderClass : public SubsystemInterface { public: @@ -84,10 +138,12 @@ class RecorderClass : public SubsystemInterface { #endif Bool isPlaybackInProgress() const; -public: - void handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback); + void handlePlaybackCRCMessage(UnsignedInt newCRC); + void handlePlayerCRCMessage(Int playerIndex, UnsignedInt newCRC); + void checkForMismatch(); + 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/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index ac733f5c299..dad33ff591c 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -293,6 +293,13 @@ class GameLogic : public SubsystemInterface, public Snapshot private: + enum CRCValidationMode CPP_11(: UnsignedByte) + { + CRCMODE_NONE, + CRCMODE_NETWORK, + CRCMODE_REPLAY, + }; + /** overrides to thing template buildable status. doesn't really belong here, but has to go somewhere. (srj) @@ -312,7 +319,7 @@ class GameLogic : public SubsystemInterface, public Snapshot // CRC cache system ----------------------------------------------------------------------------- UnsignedInt m_CRC; ///< Cache of previous CRC value std::map m_cachedCRCs; ///< CRCs we've seen this frame - Bool m_shouldValidateCRCs; ///< Should we validate CRCs this frame? + CRCValidationMode m_shouldValidateCRCs; ///< Should we validate CRCs this frame? //----------------------------------------------------------------------------------------------- //Bool m_loadingScene; Bool m_loadingMap; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index cd2284943a5..f9ade3a6122 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -465,6 +465,13 @@ Int parseJobs(char *args[], int num) return 1; } +Int parseReplayCRC(char* args[], int num) +{ + TheWritableGlobalData->m_replayOnlyCheckLocalPlayer = TRUE; + + return 1; +} + Int parseXRes(char *args[], int num) { if (num > 1) @@ -1177,6 +1184,9 @@ static CommandLineParam paramsForEngineInit[] = // TheSuperHackers @feature xezon 03/08/2025 Force full viewport for 'Control Bar Pro' Addons like GenTool did it. { "-forcefullviewport", parseFullViewport }, + // TheSuperHackers @feature Caball009 24/04/2026 Enable checking only the CRC messages from the player that recorded a replay. + { "-replayLocalPlayerCRC", parseReplayCRC }, + #if defined(RTS_DEBUG) { "-noaudio", parseNoAudio }, { "-map", parseMapName }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 3d9382d0b60..772483073db 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -1009,6 +1009,7 @@ GlobalData::GlobalData() m_playSizzle = TRUE; m_afterIntro = FALSE; m_allowExitOutOfMovies = FALSE; + m_replayOnlyCheckLocalPlayer = FALSE; m_loadScreenRender = FALSE; m_keyboardDefaultScrollFactor = m_keyboardScrollFactor = 0.5f; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index 17d67ef055e..2338982628a 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -102,6 +102,144 @@ static FILE* openStatsLogFile() } #endif +Int CRCInfo::getPlayerIndexOffset() +{ + return 2; // first two slots of PlayerList::m_players are used for neutral and civilian players +} + +CRCInfo::CRCInfo() : + m_skippedOne(FALSE), + m_sawCRCMismatch(FALSE), + m_localPlayerIndex(-1) +{} + +void CRCInfo::init(Bool isMultiplayer, Int localPlayerIndex) +{ + DEBUG_ASSERTCRASH((localPlayerIndex >= 0 && localPlayerIndex < MAX_SLOTS) || localPlayerIndex == -1, + ("replay local player index is unexpected")); + + m_skippedOne = !isMultiplayer; + m_sawCRCMismatch = FALSE; + m_localPlayerIndex = static_cast(localPlayerIndex); + + m_playbackData.clear(); + + for (size_t i = 0; i < ARRAY_SIZE(m_playerData); ++i) + { + m_playerData[i].clear(); + } +} + +void CRCInfo::addPlaybackCRC(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_playbackData.push_back(val); + //DEBUG_LOG(("CRCInfo::addPlaybackCRC() - crc %8.8X pushes list to %d entries", val, m_playbackData.size())); +} + +void CRCInfo::addPlayerCRC(Int playerIndex, UnsignedInt val) +{ + const UnsignedInt index = static_cast(playerIndex - getPlayerIndexOffset()); + if (index < ARRAY_SIZE(m_playerData)) + { + m_playerData[index].push_back(val); + //DEBUG_LOG(("CRCInfo::addPlayerCRC() - crc %8.8X pushes list to %d entries", val, m_playerData[index].size())); + } +} + +void CRCInfo::setSawCRCMismatch() +{ + m_sawCRCMismatch = TRUE; +} + +Bool CRCInfo::sawCRCMismatch() const +{ + return m_sawCRCMismatch; +} + +Byte CRCInfo::getLocalPlayerIndex() const +{ + return m_localPlayerIndex; +} + +CRCInfo::MismatchData CRCInfo::getMismatchData() +{ + const UnsignedInt size = getLargestPlayerQueueSize(); + CRCInfo::MismatchData data; + + for (UnsignedInt j = 0; j < size; ++j) + { + const UnsignedInt playbackCRC = m_playbackData.empty() ? 0 : m_playbackData.front(); + if (!m_playbackData.empty()) + { + m_playbackData.pop_front(); + } + + UnsignedInt playerCount = 0; + UnsignedInt mismatchPlayerCount = 0; + + for (Int i = 0; i < ARRAY_SIZE(m_playerData); ++i) + { + if (m_playerData[i].empty()) + continue; + + const UnsignedInt playerCRC = m_playerData[i].front(); + m_playerData[i].pop_front(); + + ++playerCount; + + if (playbackCRC == playerCRC) + continue; + + ++mismatchPlayerCount; + + if (!data.mismatched) + { + if (const Bool isAllowedToSetCRCMismatch = m_localPlayerIndex >= 0 ? i == m_localPlayerIndex : TRUE) + { + data.mismatched = TRUE; + data.playerIndex = static_cast(i + getPlayerIndexOffset()); + data.queueSize = static_cast(m_playbackData.size()); + data.playbackCRC = playbackCRC; + data.playerCRC = playerCRC; + } + } + } + + if (playerCount <= 1 || mismatchPlayerCount >= 2) + { + data.playerIndex = -1; + } + } + + return data; +} + +UnsignedInt CRCInfo::getLargestPlayerQueueSize() const +{ + UnsignedInt size = 0; + + for (size_t i = 0; i < ARRAY_SIZE(m_playerData); ++i) + { + if (m_playerData[i].size() > size) + { + size = m_playerData[i].size(); + } + } + + return size; +} + void RecorderClass::logGameStart(AsciiString options) { if (!m_file) @@ -937,151 +1075,72 @@ 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) +Bool RecorderClass::sawCRCMismatch() const { - m_localPlayer = localPlayer; - m_skippedOne = !isMultiplayer; - m_sawCRCMismatch = FALSE; + return m_crcInfo.sawCRCMismatch(); } -void CRCInfo::addCRC(UnsignedInt val) +void RecorderClass::handlePlaybackCRCMessage(UnsignedInt newCRC) { - // 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_crcInfo.addPlaybackCRC(newCRC); - 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())); + //DEBUG_LOG(("RecorderClass::handlePlaybackCRCMessage() - Adding CRC of %X from playback to m_crcInfo", newCRC)); } -UnsignedInt CRCInfo::readCRC() +void RecorderClass::handlePlayerCRCMessage(Int playerIndex, UnsignedInt newCRC) { - if (m_data.empty()) - { - DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d", m_data.size())); - return 0; - } + m_crcInfo.addPlayerCRC(playerIndex, newCRC); - 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; + //DEBUG_LOG(("RecorderClass::handlePlayerCRCMessage() - Adding CRC of %X from %d to m_crcInfo", newCRC, playerIndex)); } -Bool RecorderClass::sawCRCMismatch() const +void RecorderClass::checkForMismatch() { - return m_crcInfo->sawCRCMismatch(); -} - -void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool fromPlayback) -{ - if (fromPlayback) + const CRCInfo::MismatchData data = m_crcInfo.getMismatchData(); + if (data.mismatched) { - //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Adding CRC of %X from %d to m_crcInfo", newCRC, playerIndex)); - m_crcInfo->addCRC(newCRC); - return; - } - - Int localPlayerIndex = m_crcInfo->getLocalPlayer(); - Bool samePlayer = FALSE; - AsciiString playerName; - playerName.format("player%d", localPlayerIndex); - const Player *p = ThePlayerList->getNthPlayer(playerIndex); - if (!p || (p->getPlayerNameKey() == NAMEKEY(playerName))) - samePlayer = TRUE; - if (samePlayer || (localPlayerIndex < 0)) - { - 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()) + // 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 + // virtually every replay, the assumption is our CRC checking is faulty. Since we're at the + // tail end of patch season, let's just disable the message, and hope the users believe the + // problem is fixed. -MDC 3/20/2003 + // + // TheSuperHackers @tweak helmutbuhler 03/04/2025 + // More than 20 years later, but finally fixed and re-enabled! + TheInGameUI->message("GUI:CRCMismatch"); + + // 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() - data.queueSize - 1; + + // Now also prints a UI message for it. + const Player* player = ThePlayerList->getNthPlayer(data.playerIndex); + const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE("GUI:CRCMismatchDetails", + L"InGame:%8.8X Replay:%8.8X Frame:%d Player:%ls"); + TheInGameUI->message(mismatchDetailsStr, data.playbackCRC, data.playerCRC, mismatchFrame, + player ? player->getPlayerDisplayName().str() : L"Unknown"); + + DEBUG_LOG(("Replay has gone out of sync!\nInGame:%8.8X Replay:%8.8X\nFrame:%d\nPlayer:%ls", + data.playbackCRC, data.playerCRC, mismatchFrame, player ? player->getPlayerDisplayName().str() : L"Unknown")); + + // Print Mismatch in case we are simulating replays from console. + printf("CRC Mismatch in Frame %d, Local PlayerIndex %d, Mismatch PlayerIndex %d\n", + mismatchFrame, m_crcInfo.getLocalPlayerIndex(), data.playerIndex == -1 ? -1 : data.playerIndex - m_crcInfo.getPlayerIndexOffset()); + + // TheSuperHackers @tweak Pause the game on mismatch. + // But not when a window with focus is opened, because that can make resuming difficult. + if (TheWindowManager->winGetFocus() == nullptr) { - //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 - // virtually every replay, the assumption is our CRC checking is faulty. Since we're at the - // tail end of patch season, let's just disable the message, and hope the users believe the - // problem is fixed. -MDC 3/20/2003 - // - // TheSuperHackers @tweak helmutbuhler 03/04/2025 - // More than 20 years later, but finally fixed and re-enabled! - TheInGameUI->message("GUI:CRCMismatch"); - - // 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; - - // 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"); - TheInGameUI->message(mismatchDetailsStr, playbackCRC, newCRC, mismatchFrame); - - DEBUG_LOG(("Replay has gone out of sync!\nInGame:%8.8X Replay:%8.8X\nFrame:%d", - playbackCRC, newCRC, mismatchFrame)); - - // Print Mismatch in case we are simulating replays from console. - printf("CRC Mismatch in Frame %d\n", mismatchFrame); - - // TheSuperHackers @tweak Pause the game on mismatch. - // But not when a window with focus is opened, because that can make resuming difficult. - if (TheWindowManager->winGetFocus() == nullptr) - { - Bool pause = TRUE; - Bool pauseMusic = FALSE; - Bool pauseInput = FALSE; - TheGameLogic->setGamePaused(pause, pauseMusic, pauseInput); + const Bool pause = TRUE; + const Bool pauseMusic = FALSE; + const Bool pauseInput = FALSE; + TheGameLogic->setGamePaused(pause, pauseMusic, pauseInput); - // Mark this mismatch as seen when we had the chance to pause once. - m_crcInfo->setSawCRCMismatch(); - } + // Mark this mismatch as seen when we had the chance to pause once. + m_crcInfo.setSawCRCMismatch(); } - return; } - - //DEBUG_LOG(("RecorderClass::handleCRCMessage() - Skipping CRC of %8.8X from %d (our index is %d)", newCRC, playerIndex, localPlayerIndex)); } /** @@ -1197,9 +1256,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.init(isMultiplayer, TheGlobalData->m_replayOnlyCheckLocalPlayer ? header.localPlayerIndex : -1); 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", header.localPlayerIndex, REPLAY_CRC_INTERVAL)); Int difficulty = 0; m_file->read(&difficulty, sizeof(difficulty)); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index b4e678aa1d6..291fba504fd 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -254,7 +254,7 @@ GameLogic::GameLogic() m_progressCompleteTimeout[i] = 0; } - m_shouldValidateCRCs = FALSE; + m_shouldValidateCRCs = CRCMODE_NONE; m_startNewGame = FALSE; @@ -2600,17 +2600,65 @@ void GameLogic::processDestroyList() //in the request for a new deletion (sub-object), the new object was added to the end of this list. } +static void checkForMismatch(const std::map& cachedCRCs) +{ + Bool sawCRCMismatch = FALSE; + Int numPlayers = 0; + + for (Int i=0; iisPlayerConnected(i)) + ++numPlayers; + } + + if (cachedCRCs.size() < numPlayers) + { + DEBUG_CRASH(("Not enough CRCs!")); + sawCRCMismatch = TRUE; + } + else + { + //DEBUG_LOG(("Comparing %d CRCs on frame %d", cachedCRCs.size(), m_frame)); + std::map::const_iterator crcIt = cachedCRCs.begin(); + Int validatorCRC = crcIt->second; + //DEBUG_LOG(("Validator CRC from player %d is %8.8X", crcIt->first, validatorCRC)); + while (++crcIt != cachedCRCs.end()) + { + Int validatedCRC = crcIt->second; + //DEBUG_LOG(("CRC to validate is from player %d: %8.8X", crcIt->first, validatedCRC)); + if (validatorCRC != validatedCRC) + { + DEBUG_CRASH(("CRC mismatch!")); + sawCRCMismatch = TRUE; + } + } + } + + if (sawCRCMismatch) + { +#ifdef DEBUG_LOGGING + DEBUG_LOG(("CRC Mismatch - saw %d CRCs from %d players", cachedCRCs.size(), numPlayers)); + for (std::map::const_iterator crcIt = cachedCRCs.begin(); crcIt != cachedCRCs.end(); ++crcIt) + { + Player* player = ThePlayerList->getNthPlayer(crcIt->first); + DEBUG_LOG(("CRC from player %d (%ls) = %X", crcIt->first, + player ? player->getPlayerDisplayName().str() : L"", crcIt->second)); + } +#endif // DEBUG_LOGGING + + TheNetwork->setSawCRCMismatch(); + } +} + //------------------------------------------------------------------------------------------------- /** Process the command list passed to the logic from the network */ //------------------------------------------------------------------------------------------------- void GameLogic::processCommandList( CommandList *list ) { m_cachedCRCs.clear(); - m_shouldValidateCRCs = FALSE; - - GameMessage* msg; + m_shouldValidateCRCs = CRCMODE_NONE; - for( msg = list->getFirstMessage(); msg; msg = msg->next() ) + for( GameMessage* msg = list->getFirstMessage(); msg; msg = msg->next() ) { #ifdef RTS_DEBUG DEBUG_ASSERTCRASH(msg != nullptr && msg != (GameMessage*)0xdeadbeef, ("bad msg")); @@ -2618,58 +2666,17 @@ void GameLogic::processCommandList( CommandList *list ) logicMessageDispatcher( msg, nullptr ); } - if (m_shouldValidateCRCs && !TheNetwork->sawCRCMismatch()) + if (m_shouldValidateCRCs) { - Bool sawCRCMismatch = FALSE; - Int numPlayers = 0; - DEBUG_ASSERTCRASH(TheNetwork, ("No Network!")); - if (TheNetwork) + if (m_shouldValidateCRCs == CRCMODE_NETWORK) { - for (Int i=0; iisPlayerConnected(i)) - ++numPlayers; - } - - if (m_cachedCRCs.size() < numPlayers) - { - DEBUG_CRASH(("Not enough CRCs!")); - sawCRCMismatch = TRUE; - } - else - { - //DEBUG_LOG(("Comparing %d CRCs on frame %d", m_cachedCRCs.size(), m_frame)); - std::map::const_iterator crcIt = m_cachedCRCs.begin(); - Int validatorCRC = crcIt->second; - //DEBUG_LOG(("Validator CRC from player %d is %8.8X", crcIt->first, validatorCRC)); - while (++crcIt != m_cachedCRCs.end()) - { - Int validatedCRC = crcIt->second; - //DEBUG_LOG(("CRC to validate is from player %d: %8.8X", crcIt->first, validatedCRC)); - if (validatorCRC != validatedCRC) - { - DEBUG_CRASH(("CRC mismatch!")); - sawCRCMismatch = TRUE; - } - } - } + checkForMismatch(m_cachedCRCs); } - - if (sawCRCMismatch) + else if (m_shouldValidateCRCs == CRCMODE_REPLAY) { -#ifdef DEBUG_LOGGING - DEBUG_LOG(("CRC Mismatch - saw %d CRCs from %d players", m_cachedCRCs.size(), numPlayers)); - for (std::map::const_iterator crcIt = m_cachedCRCs.begin(); crcIt != m_cachedCRCs.end(); ++crcIt) - { - Player *player = ThePlayerList->getNthPlayer(crcIt->first); - DEBUG_LOG(("CRC from player %d (%ls) = %X", crcIt->first, - player?player->getPlayerDisplayName().str():L"", crcIt->second)); - } -#endif // DEBUG_LOGGING - TheNetwork->setSawCRCMismatch(); + TheRecorder->checkForMismatch(); } } - } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 04c364ab8b9..78a51a56e8a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -2045,6 +2045,9 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) { if (TheNetwork) { + if (TheNetwork->sawCRCMismatch()) + break; + Int slotIndex = -1; for (Int i=0; iisPlayerConnected(slotIndex)) break; + const UnsignedInt newCRC = msg->getArgument(0)->integer; + //DEBUG_LOG(("Received CRC of %8.8X from %ls on frame %d", newCRC, + //msgPlayer->getPlayerDisplayName().str(), m_frame)); + if (msgPlayer->isLocalPlayer()) { #if defined(RTS_DEBUG) // don't even put this in release, cause someone might hack it. if (!TheDebugIgnoreSyncErrors) - { -#endif - m_shouldValidateCRCs = TRUE; -#if defined(RTS_DEBUG) - } #endif + m_shouldValidateCRCs = CRCMODE_NETWORK; } - UnsignedInt newCRC = msg->getArgument(0)->integer; - //DEBUG_LOG(("Received CRC of %8.8X from %ls on frame %d", newCRC, - //msgPlayer->getPlayerDisplayName().str(), m_frame)); m_cachedCRCs[msgPlayer->getPlayerIndex()] = newCRC; } else if (TheRecorder && TheRecorder->isPlaybackMode()) { - UnsignedInt newCRC = msg->getArgument(0)->integer; - //DEBUG_LOG(("Saw CRC of %X from player %d. Our CRC is %X. Arg count is %d", + if (TheRecorder->sawCRCMismatch()) + break; + + const UnsignedInt newCRC = msg->getArgument(0)->integer; + //DEBUG_LOG(("Saw CRC of %X from player %d. Our CRC is %X. Arg count is %d", //newCRC, msgPlayer->getPlayerIndex(), getCRC(), msg->getArgumentCount())); - TheRecorder->handleCRCMessage(newCRC, msgPlayer->getPlayerIndex(), (msg->getArgument(1)->boolean)); + DEBUG_ASSERTCRASH(msg->getArgument(1)->boolean == msgPlayer->isLocalPlayer(), + ("CRC message origin is unexpected; playback message argument doesn't match message player index")); + + if (msgPlayer->isLocalPlayer()) + { + TheRecorder->handlePlaybackCRCMessage(newCRC); + } + else + { +#if defined(RTS_DEBUG) + // don't even put this in release, cause someone might hack it. + if (!TheDebugIgnoreSyncErrors) +#endif + m_shouldValidateCRCs = CRCMODE_REPLAY; + + TheRecorder->handlePlayerCRCMessage(msgPlayer->getPlayerIndex(), newCRC); + } } break;