Skip to content

Commit e2570a2

Browse files
helmutbuhlerCaball009
authored andcommitted
[ZH] Fix Replay CRC Checking (#545)
1 parent c115e91 commit e2570a2

File tree

1 file changed

+51
-17
lines changed

1 file changed

+51
-17
lines changed

GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -924,14 +924,30 @@ AsciiString RecorderClass::getCurrentReplayFilename( void )
924924
return AsciiString::TheEmptyString;
925925
}
926926

927+
// TheSuperHackers @info helmutbuhler 03/04/2025
928+
// Some info about CRC:
929+
// In each game, each peer periodically calculates a CRC from the local gamestate and sends that
930+
// in a message to all peers (including itself) so that everyone can check that the crc is synchronous.
931+
// In a network game, there is a delay between sending the CRC message and receiving it. This is
932+
// necessary because if you were to wait each frame for all messages from all peers, things would go
933+
// horribly slow.
934+
// But this delay is not a problem for CRC checking because everyone receives the CRC in the same frame
935+
// and every peer just makes sure all the received CRCs are equal.
936+
// While playing replays, this is a problem however: The CRC messages in the replays appear on the frame
937+
// they were received, which can be a few frames delayed if it was a network game. And if we were to
938+
// compare those with the local gamestate, they wouldn't sync up.
939+
// So, in order to fix this, we need to queue up our local CRCs,
940+
// so that we can check it with the crc messages that come later.
941+
// This class is basically that queue.
927942
class CRCInfo
928943
{
929944
public:
930-
CRCInfo();
945+
CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer);
931946
void addCRC(UnsignedInt val);
932947
UnsignedInt readCRC(void);
933948

934-
void setLocalPlayer(UnsignedInt index) { m_localPlayer = index; }
949+
int GetQueueSize() const { return m_data.size(); }
950+
935951
UnsignedInt getLocalPlayer(void) { return m_localPlayer; }
936952

937953
void setSawCRCMismatch(void) { m_sawCRCMismatch = TRUE; }
@@ -945,20 +961,25 @@ class CRCInfo
945961
UnsignedInt m_localPlayer;
946962
};
947963

948-
CRCInfo::CRCInfo()
964+
CRCInfo::CRCInfo(UnsignedInt localPlayer, Bool isMultiplayer)
949965
{
950-
m_localPlayer = ~0;
951-
m_skippedOne = FALSE;
966+
m_localPlayer = localPlayer;
967+
m_skippedOne = !isMultiplayer;
952968
m_sawCRCMismatch = FALSE;
953969
}
954970

955971
void CRCInfo::addCRC(UnsignedInt val)
956972
{
957-
//if (!m_skippedOne)
958-
//{
959-
// m_skippedOne = TRUE;
960-
// return;
961-
//}
973+
// TheSuperHackers @fix helmutbuhler 03/04/2025
974+
// In Multiplayer, the first MSG_LOGIC_CRC message somehow doesn't make it through the network.
975+
// Perhaps this happens because the network is not yet set up on frame 0.
976+
// So we also don't queue up the first local crc message, otherwise the crc
977+
// messages wouldn't match up anymore and we'd desync immediately during playback.
978+
if (!m_skippedOne)
979+
{
980+
m_skippedOne = TRUE;
981+
return;
982+
}
962983

963984
m_data.push_back(val);
964985
//DEBUG_LOG(("CRCInfo::addCRC() - crc %8.8X pushes list to %d entries (full=%d)\n", val, m_data.size(), !m_data.empty()));
@@ -968,7 +989,7 @@ UnsignedInt CRCInfo::readCRC(void)
968989
{
969990
if (m_data.empty())
970991
{
971-
//DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d\n", m_data.size()));
992+
DEBUG_LOG(("CRCInfo::readCRC() - bailing, full=0, size=%d\n", m_data.size()));
972993
return 0;
973994
}
974995

@@ -997,7 +1018,8 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f
9971018
if (samePlayer || (localPlayerIndex < 0))
9981019
{
9991020
UnsignedInt playbackCRC = m_crcInfo->readCRC();
1000-
//DEBUG_LOG(("RecorderClass::handleCRCMessage() - Comparing CRCs of %8.8X/%8.8X from %d\n", newCRC, playbackCRC, playerIndex));
1021+
//DEBUG_LOG(("RecorderClass::handleCRCMessage() - Comparing CRCs of InGame:%8.8X Replay:%8.8X Frame:%d from Player %d\n",
1022+
// playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo->GetQueueSize()-1, playerIndex));
10011023
if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo->sawCRCMismatch())
10021024
{
10031025
m_crcInfo->setSawCRCMismatch();
@@ -1007,9 +1029,16 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f
10071029
// virtually every replay, the assumption is our CRC checking is faulty. Since we're at the
10081030
// tail end of patch season, let's just disable the message, and hope the users believe the
10091031
// problem is fixed. -MDC 3/20/2003
1010-
//TheInGameUI->message("GUI:CRCMismatch");
1011-
DEBUG_CRASH(("Replay has gone out of sync! All bets are off!\nOld:%8.8X New:%8.8X\nFrame:%d",
1012-
playbackCRC, newCRC, TheGameLogic->getFrame()));
1032+
//
1033+
// TheSuperHackers @tweak helmutbuhler 03/04/2025
1034+
// More than 20 years later, but finally fixed and reenabled!
1035+
TheInGameUI->message("GUI:CRCMismatch");
1036+
1037+
// TheSuperHackers @info helmutbuhler 03/04/2025
1038+
// Note: We subtract the queue size from the frame no. This way we calculate the correct frame
1039+
// the mismatch first happened in case the NetCRCInterval is set to 1 during the game.
1040+
DEBUG_CRASH(("Replay has gone out of sync! All bets are off!\nInGame:%8.8X Replay:%8.8X\nFrame:%d",
1041+
playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo->GetQueueSize()-1));
10131042
}
10141043
return;
10151044
}
@@ -1124,8 +1153,8 @@ Bool RecorderClass::playbackFile(AsciiString filename)
11241153
}
11251154
#endif
11261155

1127-
m_crcInfo = NEW CRCInfo;
1128-
m_crcInfo->setLocalPlayer(header.localPlayerIndex);
1156+
Bool isMultiplayer = m_gameInfo.getSlot(header.localPlayerIndex)->getIP() != 0;
1157+
m_crcInfo = NEW CRCInfo(header.localPlayerIndex, isMultiplayer);
11291158
REPLAY_CRC_INTERVAL = m_gameInfo.getCRCInterval();
11301159
DEBUG_LOG(("Player index is %d, replay CRC interval is %d\n", m_crcInfo->getLocalPlayer(), REPLAY_CRC_INTERVAL));
11311160

@@ -1141,6 +1170,11 @@ Bool RecorderClass::playbackFile(AsciiString filename)
11411170
fread(&maxFPS, sizeof(maxFPS), 1, m_file);
11421171

11431172
DEBUG_LOG(("RecorderClass::playbackFile() - original game was mode %d\n", m_originalGameMode));
1173+
1174+
// TheSuperHackers @fix helmutbuhler 03/04/2025
1175+
// In case we restart a replay, we need to clear the command list.
1176+
// Otherwise a crc message remains and messes up the crc calculation on the restarted replay.
1177+
TheCommandList->reset();
11441178

11451179
readNextFrame();
11461180

0 commit comments

Comments
 (0)