From d094357ad25ef93a89d78a9ed433f2d34372027a Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sat, 29 Aug 2026 18:55:59 -0400 Subject: [PATCH 1/3] bugfix(replay): Derive the replay CRC queue from the recorded game mode --- GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index 9d71eb45b1b..68fc12acb0b 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -1152,16 +1152,21 @@ Bool RecorderClass::playbackFile(AsciiString filename) } #endif - Bool isMultiplayer = m_gameInfo.getSlot(header.localPlayerIndex)->getIP() != 0; - 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)); Int difficulty = 0; m_file->read(&difficulty, sizeof(difficulty)); m_file->read(&m_originalGameMode, sizeof(m_originalGameMode)); + // TheSuperHackers @bugfix bobtista 30/08/2026 A replay header is allowed to carry a local player + // index of -1 and getSlot returns NULL for it, so reading the slot to tell a network game from a + // local one dereferenced NULL. The recorded game mode answers the same question directly, so the + // crc queue is now primed from the mode and the local slot is no longer read here. + const Bool isMultiplayer = m_originalGameMode == GAME_LAN || m_originalGameMode == GAME_INTERNET; + m_crcInfo = CRCInfo(header.localPlayerIndex, isMultiplayer); + DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo.getLocalPlayer(), REPLAY_CRC_INTERVAL)); + Int rankPoints = 0; m_file->read(&rankPoints, sizeof(rankPoints)); From fd1f41b9d459ac5cdf5f1305885e7008f7f830bc Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sun, 30 Aug 2026 19:22:54 -0400 Subject: [PATCH 2/3] bugfix(replay): Replicate replay CRC queue mode derivation to Generals --- Generals/Code/GameEngine/Source/Common/Recorder.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/Recorder.cpp b/Generals/Code/GameEngine/Source/Common/Recorder.cpp index 7a955895827..9f0d9f74cfc 100644 --- a/Generals/Code/GameEngine/Source/Common/Recorder.cpp +++ b/Generals/Code/GameEngine/Source/Common/Recorder.cpp @@ -1149,16 +1149,21 @@ Bool RecorderClass::playbackFile(AsciiString filename) } #endif - Bool isMultiplayer = m_gameInfo.getSlot(header.localPlayerIndex)->getIP() != 0; - 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)); Int difficulty = 0; m_file->read(&difficulty, sizeof(difficulty)); m_file->read(&m_originalGameMode, sizeof(m_originalGameMode)); + // TheSuperHackers @bugfix bobtista 30/08/2026 A replay header is allowed to carry a local player + // index of -1 and getSlot returns NULL for it, so reading the slot to tell a network game from a + // local one dereferenced NULL. The recorded game mode answers the same question directly, so the + // crc queue is now primed from the mode and the local slot is no longer read here. + const Bool isMultiplayer = m_originalGameMode == GAME_LAN || m_originalGameMode == GAME_INTERNET; + m_crcInfo = CRCInfo(header.localPlayerIndex, isMultiplayer); + DEBUG_LOG(("Player index is %d, replay CRC interval is %d", m_crcInfo.getLocalPlayer(), REPLAY_CRC_INTERVAL)); + Int rankPoints = 0; m_file->read(&rankPoints, sizeof(rankPoints)); From 8eb966d3189c46df42d810c3bae5b94ebbafa50a Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Sun, 30 Aug 2026 19:52:03 -0400 Subject: [PATCH 3/3] bugfix(replay): Guard the local slot lookup when a replay has no local player --- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 6 +++++- .../Code/GameEngine/Source/GameLogic/System/GameLogic.cpp | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 932caa24f82..ac71eb4247c 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1315,7 +1315,11 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) d.setInt(TheKey_multiplayerStartIndex, slot->getStartPos()); // d.setBool(TheKey_multiplayerIsLocal, slot->isLocalPlayer()); // d.setBool(TheKey_multiplayerIsLocal, slot->getIP() == game->getLocalIP()); - d.setBool(TheKey_multiplayerIsLocal, slot->isHuman() && (slot->getName().compare(TheGameInfo->getSlot(TheGameInfo->getLocalSlotNum())->getName().str()) == 0)); + // TheSuperHackers @bugfix bobtista 30/08/2026 A replay recorded without a local player has no + // local slot number and getSlot returns NULL for it, so no slot can be the local one. + const Int localSlotNum = TheGameInfo->getLocalSlotNum(); + const GameSlot *localGameSlot = localSlotNum >= 0 ? TheGameInfo->getSlot(localSlotNum) : nullptr; + d.setBool(TheKey_multiplayerIsLocal, slot->isHuman() && localGameSlot != nullptr && (slot->getName().compare(localGameSlot->getName().str()) == 0)); /* if (slot->getIP() == game->getLocalIP()) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 60bae04f186..71e8f72b235 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -1476,7 +1476,11 @@ void GameLogic::tryStartNewGame( Bool loadingSaveGame ) d.setInt(TheKey_multiplayerStartIndex, slot->getStartPos()); // d.setBool(TheKey_multiplayerIsLocal, slot->isLocalPlayer()); // d.setBool(TheKey_multiplayerIsLocal, slot->getIP() == game->getLocalIP()); - d.setBool(TheKey_multiplayerIsLocal, slot->isHuman() && (slot->getName().compare(TheGameInfo->getSlot(TheGameInfo->getLocalSlotNum())->getName().str()) == 0)); + // TheSuperHackers @bugfix bobtista 30/08/2026 A replay recorded without a local player has no + // local slot number and getSlot returns NULL for it, so no slot can be the local one. + const Int localSlotNum = TheGameInfo->getLocalSlotNum(); + const GameSlot *localGameSlot = localSlotNum >= 0 ? TheGameInfo->getSlot(localSlotNum) : nullptr; + d.setBool(TheKey_multiplayerIsLocal, slot->isHuman() && localGameSlot != nullptr && (slot->getName().compare(localGameSlot->getName().str()) == 0)); /* if (slot->getIP() == game->getLocalIP())