From 869b640813abb8c92486ab54f4ab0ecb547f4fba Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Wed, 16 Apr 2025 22:13:13 +0200 Subject: [PATCH] [GEN] Improve CRC Logging Debug Functionality --- .../Code/GameEngine/Include/Common/CRCDebug.h | 6 +- .../Code/GameEngine/Include/Common/Xfer.h | 8 + .../GameEngine/Source/Common/CRCDebug.cpp | 166 ++++++++++++------ .../GameEngine/Source/Common/CommandLine.cpp | 67 ++++++- .../GameEngine/Source/Common/System/Debug.cpp | 14 +- .../Source/GameLogic/Object/Object.cpp | 19 +- .../Source/GameLogic/System/GameLogic.cpp | 24 ++- .../GameEngine/Source/GameNetwork/Network.cpp | 3 +- 8 files changed, 225 insertions(+), 82 deletions(-) diff --git a/Generals/Code/GameEngine/Include/Common/CRCDebug.h b/Generals/Code/GameEngine/Include/Common/CRCDebug.h index 3452b9fe014..6a0d7e2f267 100644 --- a/Generals/Code/GameEngine/Include/Common/CRCDebug.h +++ b/Generals/Code/GameEngine/Include/Common/CRCDebug.h @@ -68,9 +68,11 @@ void dumpReal(Real r, AsciiString name, AsciiString fname, Int line); void outputCRCDebugLines( void ); + void CRCDebugStartNewGame( void ); void outputCRCDumpLines( void ); void addCRCDebugLine(const char *fmt, ...); + void addCRCDebugLineNoCounter(const char *fmt, ...); void addCRCDumpLine(const char *fmt, ...); void addCRCGenLine(const char *fmt, ...); #define CRCDEBUG_LOG(x) addCRCDebugLine x @@ -97,7 +99,9 @@ extern Bool g_crcModuleDataFromLogic; extern Bool g_keepCRCSaves; - + extern Bool g_saveDebugCRCPerFrame; + extern AsciiString g_saveDebugCRCPerFrameDir; + extern Bool g_logObjectCRCs; #else // DEBUG_CRC diff --git a/Generals/Code/GameEngine/Include/Common/Xfer.h b/Generals/Code/GameEngine/Include/Common/Xfer.h index 9975e9ae5e5..7b0eabd3077 100644 --- a/Generals/Code/GameEngine/Include/Common/Xfer.h +++ b/Generals/Code/GameEngine/Include/Common/Xfer.h @@ -27,6 +27,14 @@ // Desc: The Xfer system is capable of setting up operations to work with blocks of data // from other subsystems. It can work things such as file reading, file writing, // CRC computations etc +// +// TheSuperHackers @info helmutbuhler 04/09/2025 +// The baseclass Xfer has 3 implementations: +// - XferLoad: Load gamestate +// - XferSave: Save gamestate +// - XferCRC: Calculate gamestate CRC +// - XferDeepCRC: This derives from XferCRC and also writes the gamestate data relevant +// to crc calculation to a file (only used in developer builds) /////////////////////////////////////////////////////////////////////////////////////////////////// #pragma once diff --git a/Generals/Code/GameEngine/Source/Common/CRCDebug.cpp b/Generals/Code/GameEngine/Source/Common/CRCDebug.cpp index 2907bf16a89..ab40d141bf1 100644 --- a/Generals/Code/GameEngine/Source/Common/CRCDebug.cpp +++ b/Generals/Code/GameEngine/Source/Common/CRCDebug.cpp @@ -28,6 +28,7 @@ #include "Common/CRCDebug.h" #include "Common/Debug.h" #include "Common/PerfTimer.h" +#include "Common/LocalFileSystem.h" #include "GameClient/InGameUI.h" #include "GameNetwork/IPEnumeration.h" #include @@ -41,11 +42,12 @@ #ifdef DEBUG_CRC static const Int MaxStrings = 64000; +static const Int MaxStringLen = 1024; -static char DebugStrings[MaxStrings][1024]; +static char DebugStrings[MaxStrings][MaxStringLen]; static Int nextDebugString = 0; static Int numDebugStrings = 0; -//static char DumpStrings[MaxStrings][1024]; +//static char DumpStrings[MaxStrings][MaxStringLen]; //static Int nextDumpString = 0; //static Int numDumpStrings = 0; @@ -91,12 +93,8 @@ CRCVerification::~CRCVerification() #endif } -static Bool dumped = FALSE; void outputCRCDebugLines( void ) { - if (dumped) - return; - dumped = TRUE; IPEnumeration ips; AsciiString fname; fname.format("crcDebug%s.txt", ips.getMachineName().str()); @@ -116,6 +114,60 @@ void outputCRCDebugLines( void ) if (fp) fclose(fp); } +Int lastCRCDebugFrame = 0; +Int lastCRCDebugIndex = 0; +extern Bool inCRCGen; + +void CRCDebugStartNewGame() +{ + if (g_saveDebugCRCPerFrame) + { + // Create folder for frame data, if it doesn't exist yet. + CreateDirectory(g_saveDebugCRCPerFrameDir.str(), NULL); + + // Delete existing files + FilenameList files; + AsciiString dir = g_saveDebugCRCPerFrameDir; + dir.concat("/"); + TheLocalFileSystem->getFileListInDirectory(dir.str(), "", "DebugFrame_*.txt", files, FALSE); + FilenameList::iterator it; + for (it = files.begin(); it != files.end(); ++it) + { + DeleteFile(it->str()); + } + } + nextDebugString = 0; + numDebugStrings = 0; + lastCRCDebugFrame = 0; + lastCRCDebugIndex = 0; +} + +static void outputCRCDebugLinesPerFrame() +{ + if (!g_saveDebugCRCPerFrame || numDebugStrings == 0) + return; + AsciiString fname; + fname.format("%s/DebugFrame_%06d.txt", g_saveDebugCRCPerFrameDir.str(), lastCRCDebugFrame); + FILE *fp = fopen(fname.str(), "wt"); + int start = 0; + int end = nextDebugString; + if (numDebugStrings >= MaxStrings) + start = nextDebugString - MaxStrings; + nextDebugString = 0; + numDebugStrings = 0; + if (!fp) + return; + + for (Int i=start; iisInGameLogicUpdate()*/) + if (TheGameLogic == NULL || !(IS_FRAME_OK_TO_LOG)) return; - if (IS_FRAME_OK_TO_LOG) + if (lastCRCDebugFrame != TheGameLogic->getFrame()) { + outputCRCDebugLinesPerFrame(); + lastCRCDebugFrame = TheGameLogic->getFrame(); + lastCRCDebugIndex = 0; + } - if (lastCRCDebugFrame != TheGameLogic->getFrame()) - { - lastCRCDebugFrame = TheGameLogic->getFrame(); - lastCRCDebugIndex = 0; - } - - sprintf(DebugStrings[nextDebugString], "%d:%d ", TheGameLogic->getFrame(), lastCRCDebugIndex++); - //DebugStrings[nextDebugString][0] = 0; - Int len = strlen(DebugStrings[nextDebugString]); + if (count) + sprintf(DebugStrings[nextDebugString], "%d:%05d ", TheGameLogic->getFrame(), lastCRCDebugIndex++); + else + DebugStrings[nextDebugString][0] = 0; + Int len = strlen(DebugStrings[nextDebugString]); - va_list va; - va_start( va, fmt ); - _vsnprintf(DebugStrings[nextDebugString]+len, 1024-len, fmt, va ); - DebugStrings[nextDebugString][1023] = 0; - va_end( va ); + _vsnprintf(DebugStrings[nextDebugString]+len, MaxStringLen-len, fmt, args); + DebugStrings[nextDebugString][MaxStringLen-1] = 0; - char *tmp = DebugStrings[nextDebugString]; - while (tmp && *tmp) + char *tmp = DebugStrings[nextDebugString]; + while (tmp && *tmp) + { + if (*tmp == '\r' || *tmp == '\n') { - if (*tmp == '\r' || *tmp == '\n') - { - *tmp = ' '; - } - ++tmp; + *tmp = ' '; } + ++tmp; + } - //DEBUG_LOG(("%s\n", DebugStrings[nextDebugString])); + //DEBUG_LOG(("%s\n", DebugStrings[nextDebugString])); - ++nextDebugString; - ++numDebugStrings; - if (nextDebugString == MaxStrings) - nextDebugString = 0; + ++nextDebugString; + ++numDebugStrings; + if (nextDebugString == MaxStrings) + nextDebugString = 0; +} - } +void addCRCDebugLine(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + addCRCDebugLineInternal(true, fmt, args); + va_end(args); +} + +void addCRCDebugLineNoCounter(const char *fmt, ...) +{ + // TheSuperHackers @feature helmutbuhler 04/09/2025 + // This version doesn't increase the lastCRCDebugIndex counter + // and can be used for logging lines that don't necessarily match up on all peers. + // (Otherwise the numbers would no longer match up and the diff would be very difficult to read) + va_list args; + va_start(args, fmt); + addCRCDebugLineInternal(false, fmt, args); + va_end(args); } void addCRCGenLine(const char *fmt, ...) { - if (dumped || !(IS_FRAME_OK_TO_LOG)) + if (!(IS_FRAME_OK_TO_LOG)) return; - static char buf[1024]; + static char buf[MaxStringLen]; va_list va; va_start( va, fmt ); - _vsnprintf(buf, 1024, fmt, va ); + _vsnprintf(buf, MaxStringLen, fmt, va ); va_end( va ); - buf[1023] = 0; + buf[MaxStringLen-1] = 0; addCRCDebugLine("%s", buf); //DEBUG_LOG(("%s", buf)); @@ -205,8 +269,8 @@ void addCRCDumpLine(const char *fmt, ...) /* va_list va; va_start( va, fmt ); - _vsnprintf(DumpStrings[nextDumpString], 1024, fmt, va ); - DumpStrings[nextDumpString][1023] = 0; + _vsnprintf(DumpStrings[nextDumpString], MaxStringLen, fmt, va ); + DumpStrings[nextDumpString][MaxStringLen-1] = 0; va_end( va ); ++nextDumpString; @@ -218,9 +282,6 @@ void addCRCDumpLine(const char *fmt, ...) void dumpVector3(const Vector3 *v, AsciiString name, AsciiString fname, Int line) { - if (dumped) - return; - if (!(IS_FRAME_OK_TO_LOG)) return; fname.toLower(); fname = getFname(fname); @@ -231,9 +292,6 @@ void dumpVector3(const Vector3 *v, AsciiString name, AsciiString fname, Int line void dumpCoord3D(const Coord3D *c, AsciiString name, AsciiString fname, Int line) { - if (dumped) - return; - if (!(IS_FRAME_OK_TO_LOG)) return; fname.toLower(); fname = getFname(fname); @@ -244,9 +302,6 @@ void dumpCoord3D(const Coord3D *c, AsciiString name, AsciiString fname, Int line void dumpMatrix3D(const Matrix3D *m, AsciiString name, AsciiString fname, Int line) { - if (dumped) - return; - if (!(IS_FRAME_OK_TO_LOG)) return; fname.toLower(); fname = getFname(fname); @@ -260,9 +315,6 @@ void dumpMatrix3D(const Matrix3D *m, AsciiString name, AsciiString fname, Int li void dumpReal(Real r, AsciiString name, AsciiString fname, Int line) { - if (dumped) - return; - if (!(IS_FRAME_OK_TO_LOG)) return; fname.toLower(); fname = getFname(fname); diff --git a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp index 791bf9a9e19..469fa8cca06 100644 --- a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp @@ -41,6 +41,8 @@ extern Int DX8Wrapper_PreserveFPU; Int TheCRCFirstFrameToLog = -1; UnsignedInt TheCRCLastFrameToLog = 0xffffffff; Bool g_keepCRCSaves = FALSE; +Bool g_saveDebugCRCPerFrame = FALSE; +AsciiString g_saveDebugCRCPerFrameDir; Bool g_crcModuleDataFromLogic = FALSE; Bool g_crcModuleDataFromClient = FALSE; Bool g_verifyClientCRC = FALSE; // verify that GameLogic CRC doesn't change from client @@ -154,6 +156,7 @@ Int parseFPUPreserve(char *args[], int argc) } #if defined(_DEBUG) || defined(_INTERNAL) + //============================================================================= //============================================================================= Int parseUseCSF(char *args[], int) @@ -194,6 +197,7 @@ Int parseNoMilCap(char *args[], int) } return 1; } +#endif // _DEBUG || _INTERNAL //============================================================================= //============================================================================= @@ -231,6 +235,22 @@ Int parseKeepCRCSave(char *args[], int argc) return 1; } +//============================================================================= +//============================================================================= +Int parseSaveDebugCRCPerFrame(char* args[], int argc) +{ +#ifdef DEBUG_CRC + if (argc > 1) + { + g_saveDebugCRCPerFrame = TRUE; + g_saveDebugCRCPerFrameDir = args[1]; + if (TheCRCFirstFrameToLog == -1) + TheCRCFirstFrameToLog = 0; + } +#endif + return 2; +} + //============================================================================= //============================================================================= Int parseCRCLogicModuleData(char *args[], int argc) @@ -320,6 +340,8 @@ Int parseNoDraw(char *args[], int argc) return 1; } +#if defined(_DEBUG) || defined(_INTERNAL) + //============================================================================= //============================================================================= Int parseLogToConsole(char *args[], int) @@ -1119,17 +1141,58 @@ static CommandLineParam params[] = { "-localMOTD", parseLocalMOTD }, { "-UseCSF", parseUseCSF }, { "-NoInputDisable", parseNoInputDisable }, +#endif +#ifdef DEBUG_CRC + // TheSuperHackers @info helmutbuhler 04/09/2025 + // The following arguments are useful for CRC debugging. + // Note that you need to have a debug or internal configuration build in order to use this. + // Release configuration also works if RELEASE_DEBUG_LOGGING is defined in Debug.h + // Also note that all players need to play in the same configuration, otherwise mismatch will + // occur almost immediately. + // Try this if you want to play the game and have useful debug information in case mismatch occurs: + // -ignoreAsserts -DebugCRCFromFrame 0 -VerifyClientCRC -LogObjectCRCs -NetCRCInterval 1 + // After mismatch occurs, you can examine the logfile and also reproduce the crc from the replay with this (and diff that with the log): + // -ignoreAsserts -DebugCRCFromFrame xxx -LogObjectCRCs -SaveDebugCRCPerFrame crc + + // After which frame to log crc logging. Call with 0 to log all frames and with -1 to log none (default). { "-DebugCRCFromFrame", parseDebugCRCFromFrame }, + + // Last frame to log { "-DebugCRCUntilFrame", parseDebugCRCUntilFrame }, + + // Save data involving CRC calculation to a binary file. (This isn't that useful.) { "-KeepCRCSaves", parseKeepCRCSave }, + + // TheSuperHackers @feature helmutbuhler 04/09/2025 + // Store CRC Debug Logging into a separate file for each frame. + // Pass the foldername after this where those files are to be stored. + // This is useful for replay analysis. + // Note that the passed folder is deleted if it already exists for every started game. + { "-SaveDebugCRCPerFrame", parseSaveDebugCRCPerFrame }, + { "-CRCLogicModuleData", parseCRCLogicModuleData }, { "-CRCClientModuleData", parseCRCClientModuleData }, - { "-ClientDeepCRC", parseClientDeepCRC }, + + // Verify that Game Logic CRC doesn't change during client update. + // Client update is only for visuals and not supposed to change the crc. + // (This is implemented using CRCVerification class in GameEngine::update) { "-VerifyClientCRC", parseVerifyClientCRC }, + + // Write out binary crc data pre and post client update to "clientPre.crc" and "clientPost.crc" + { "-ClientDeepCRC", parseClientDeepCRC }, + + // Log CRC of Objects and Weapons (See Object::crc and Weapon::crc) { "-LogObjectCRCs", parseLogObjectCRCs }, - { "-saveAllStats", parseSaveAllStats }, + + // Number of frames between each CRC check between all players in multiplayer games + // (if not all crcs are equal, mismatch occurs). { "-NetCRCInterval", parseNetCRCInterval }, + + // Number of frames between each CRC that is written to replay files in singleplayer games. { "-ReplayCRCInterval", parseReplayCRCInterval }, +#endif +#if (defined(_DEBUG) || defined(_INTERNAL)) + { "-saveAllStats", parseSaveAllStats }, { "-noDraw", parseNoDraw }, { "-nomilcap", parseNoMilCap }, { "-nofade", parseNoFade }, diff --git a/Generals/Code/GameEngine/Source/Common/System/Debug.cpp b/Generals/Code/GameEngine/Source/Common/System/Debug.cpp index caa3ce37cb8..24e60f228dd 100644 --- a/Generals/Code/GameEngine/Source/Common/System/Debug.cpp +++ b/Generals/Code/GameEngine/Source/Common/System/Debug.cpp @@ -46,12 +46,20 @@ #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine -// USER INCLUDES +// USER INCLUDES + +// TheSuperHackers @feature helmutbuhler 04/10/2025 +// Uncomment this to show normal logging stuff in the crc logging. +// This can be helpful for context, but can also clutter diffs because normal logs aren't necessarily +// deterministic or the same on all peers in multiplayer games. +//#define INCLUDE_DEBUG_LOG_IN_CRC_LOG + #define DEBUG_THREADSAFE #ifdef DEBUG_THREADSAFE #include "Common/CriticalSection.h" #endif #include "Common/Debug.h" +#include "Common/CRCDebug.h" #include "Common/Registry.h" #include "Common/SystemInfo.h" #include "Common/UnicodeString.h" @@ -236,6 +244,10 @@ static void doLogOutput(const char *buffer) { ::OutputDebugString(buffer); } + +#ifdef INCLUDE_DEBUG_LOG_IN_CRC_LOG + addCRCDebugLineNoCounter("%s", buffer); +#endif } #endif diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 21c79aaf08b..1d82f890e30 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -3364,10 +3364,6 @@ void Object::onDisabledEdge(Bool becomingDisabled) //------------------------------------------------------------------------------------------------- void Object::crc( Xfer *xfer ) { - // This is evil - we cast the const Matrix3D * to a Matrix3D * because the XferCRC class must use - // the same interface as the XferLoad class for save game restore. This only works because - // XferCRC does not modify its data. - #ifdef DEBUG_CRC // g_logObjectCRCs = TRUE; // Bool g_logAllObjects = TRUE; @@ -3376,7 +3372,7 @@ void Object::crc( Xfer *xfer ) Bool doLogging = g_logObjectCRCs /* && getControllingPlayer()->getPlayerType() == PLAYER_HUMAN */; if (doLogging) { - tmp.format("CRC of Object %d (%s), owned by player %d, ", m_id, getTemplate()->getName().str(), getControllingPlayer()->getPlayerIndex()); + tmp.format("CRC of Object %d (%s), owned by player %d, team: %d, ", m_id, getTemplate()->getName().str(), getControllingPlayer()->getPlayerIndex(), this->getTeam() ? this->getTeam()->getID() : TEAM_ID_INVALID); logString.concat(tmp); } #endif // DEBUG_CRC @@ -3390,6 +3386,9 @@ void Object::crc( Xfer *xfer ) } #endif // DEBUG_CRC + // This is evil - we cast the const Matrix3D * to a Matrix3D * because the XferCRC class must use + // the same interface as the XferLoad class for save game restore. This only works because + // XferCRC does not modify its data. xfer->xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); #ifdef DEBUG_CRC if (doLogging) @@ -3404,16 +3403,6 @@ void Object::crc( Xfer *xfer ) #endif // DEBUG_CRC -#ifdef DEBUG_CRC - if (doLogging) - { - const Matrix3D *mtx = getTransformMatrix(); - CRCDEBUG_LOG(("CRC of Object %d (%s), owned by player %d, ", m_id, getTemplate()->getName().str(), getControllingPlayer()->getPlayerIndex())); - DUMPMATRIX3D(mtx); - } -#endif //DEBUG_CRC - - xfer->xferUser(&m_id, sizeof(m_id)); #ifdef DEBUG_CRC if (doLogging) diff --git a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index e0086435cde..b007a64907b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -978,6 +978,15 @@ void GameLogic::startNewGame( Bool saveGame ) GetPrecisionTimer(&startTime64); #endif + // reset the frame counter + m_frame = 0; + +#ifdef DEBUG_CRC + // TheSuperHackers @info helmutbuhler 04/09/2025 + // Let CRC Logger know that a new game was started. + CRCDebugStartNewGame(); +#endif + if( saveGame == FALSE ) { @@ -1124,8 +1133,7 @@ void GameLogic::startNewGame( Bool saveGame ) if(m_loadScreen) updateLoadProgress(LOAD_PROGRESS_POST_PARTICLE_INI_LOAD); - // reset the frame counter - m_frame = 0; + DEBUG_ASSERTCRASH(m_frame == 0, ("framecounter expected to be 0 here\n")); // before loading the map, load the map.ini file in the same directory. loadMapINI( TheGlobalData->m_mapName ); @@ -3122,7 +3130,7 @@ void GameLogic::update( void ) Bool generateForMP = (isMPGameOrReplay && (m_frame % TheGameInfo->getCRCInterval()) == 0); #ifdef DEBUG_CRC Bool generateForSolo = isSoloGameOrReplay && ((m_frame && (m_frame%100 == 0)) || - (getFrame() > TheCRCFirstFrameToLog && getFrame() < TheCRCLastFrameToLog && ((m_frame % REPLAY_CRC_INTERVAL) == 0))); + (getFrame() >= TheCRCFirstFrameToLog && getFrame() < TheCRCLastFrameToLog && ((m_frame % REPLAY_CRC_INTERVAL) == 0))); #else Bool generateForSolo = isSoloGameOrReplay && ((m_frame % REPLAY_CRC_INTERVAL) == 0); #endif // DEBUG_CRC @@ -3135,14 +3143,14 @@ void GameLogic::update( void ) GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); msg->appendIntegerArgument( m_CRC ); msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - //DEBUG_LOG(("Appended CRC of %8.8X on frame %d\n", m_CRC, m_frame)); + DEBUG_LOG(("Appended CRC on frame %d: %8.8X\n", m_frame, m_CRC)); } else { GameMessage *msg = TheMessageStream->appendMessage( GameMessage::MSG_LOGIC_CRC ); msg->appendIntegerArgument( m_CRC ); msg->appendBooleanArgument( (TheRecorder && TheRecorder->getMode() == RECORDERMODETYPE_PLAYBACK) ); // playback CRC - //DEBUG_LOG(("Appended Playback CRC of %8.8X on frame %d\n", m_CRC, m_frame)); + DEBUG_LOG(("Appended Playback CRC on frame %d: %8.8X\n", m_frame, m_CRC)); } } @@ -3478,6 +3486,12 @@ UnsignedInt GameLogic::getCRC( Int mode, AsciiString deepCRCFileName ) { AsciiString crcName; #ifdef DEBUG_CRC + // TheSuperHackers @info helmutbuhler 04/09/2025 + // This allows you to save the binary data that is involved in the crc calculation + // to a binary file per frame. + // This was apparently used early in development and isn't that useful, because diffing + // that binary data is very difficult. The CRC logging is much easier to diff and also more + // granular than this because it can capture changes between two frames. if (isInGameLogicUpdate() && g_keepCRCSaves && m_frame < 5) { xferCRC = NEW XferDeepCRC; diff --git a/Generals/Code/GameEngine/Source/GameNetwork/Network.cpp b/Generals/Code/GameEngine/Source/GameNetwork/Network.cpp index 780c9f945a0..046ce40ce45 100644 --- a/Generals/Code/GameEngine/Source/GameNetwork/Network.cpp +++ b/Generals/Code/GameEngine/Source/GameNetwork/Network.cpp @@ -380,7 +380,8 @@ void Network::setSawCRCMismatch( void ) TheRecorder->logCRCMismatch(); // dump GameLogic random seed - DEBUG_LOG(("GameLogic frame = %d\n", TheGameLogic->getFrame())); + DEBUG_LOG(("Latest frame for mismatch = %d GameLogic frame = %d\n", + TheGameLogic->getFrame()-m_runAhead-1, TheGameLogic->getFrame())); DEBUG_LOG(("GetGameLogicRandomSeedCRC() = %d\n", GetGameLogicRandomSeedCRC())); // dump CRCs