From d5ede3ef9fd322e1110624a15fb7736153698cc1 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sun, 22 Mar 2026 23:18:03 +0100 Subject: [PATCH 1/2] feat(crc): Write deep CRC snapshots to disk when game mismatches --- Core/GameEngine/Include/Common/GameDefines.h | 4 + Core/GameEngine/Include/Common/RandomValue.h | 4 + Core/GameEngine/Include/Common/Xfer.h | 4 + Core/GameEngine/Include/Common/XferDeepCRC.h | 13 ++ Core/GameEngine/Source/Common/RandomValue.cpp | 16 ++ .../Source/Common/System/XferCRC.cpp | 99 +++++++++++++ .../Source/GameClient/ClientInstance.cpp | 2 + .../GameEngine/Source/GameNetwork/Network.cpp | 2 + .../GameEngine/Include/GameLogic/GameLogic.h | 10 ++ .../GameEngine/Include/GameLogic/Object.h | 4 + .../GameEngine/Source/Common/Recorder.cpp | 4 + .../Source/GameLogic/System/GameLogic.cpp | 140 +++++++++++++++++- 12 files changed, 299 insertions(+), 3 deletions(-) diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index d688cc73ae4..57495bd85a8 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -112,6 +112,10 @@ #endif #endif +#if (!defined(DEEP_CRC_TO_MEMORY) && !defined(DEBUG_CRC)) +#define DEEP_CRC_TO_MEMORY 1 +#endif + #define MIN_DISPLAY_BIT_DEPTH 16 #define DEFAULT_DISPLAY_BIT_DEPTH 32 #define DEFAULT_DISPLAY_WIDTH 800 // The standard resolution this game was designed for diff --git a/Core/GameEngine/Include/Common/RandomValue.h b/Core/GameEngine/Include/Common/RandomValue.h index 8cc97d5d844..ef503900d83 100644 --- a/Core/GameEngine/Include/Common/RandomValue.h +++ b/Core/GameEngine/Include/Common/RandomValue.h @@ -35,4 +35,8 @@ extern void InitRandom( UnsignedInt seed ); extern UnsignedInt GetGameLogicRandomSeed(); ///< Get the seed (used for replays) extern UnsignedInt GetGameLogicRandomSeedCRC();///< Get the seed (used for CRCs) +#if DEEP_CRC_TO_MEMORY +AsciiString GetGameLogicalRandomSeeds(); +#endif + //-------------------------------------------------------------------------------------------------------------- diff --git a/Core/GameEngine/Include/Common/Xfer.h b/Core/GameEngine/Include/Common/Xfer.h index 617b8e75e38..009b6da1a77 100644 --- a/Core/GameEngine/Include/Common/Xfer.h +++ b/Core/GameEngine/Include/Common/Xfer.h @@ -178,6 +178,10 @@ class Xfer virtual void xferMatrix3D( Matrix3D* mtx ); virtual void xferMapName( AsciiString *mapNameData ); +#if DEEP_CRC_TO_MEMORY + virtual void xferLogString(const AsciiString& str) {} +#endif + protected: // this is the actual xfer implementation that each derived class should implement diff --git a/Core/GameEngine/Include/Common/XferDeepCRC.h b/Core/GameEngine/Include/Common/XferDeepCRC.h index 729e37bf1c8..d1b3f8b0346 100644 --- a/Core/GameEngine/Include/Common/XferDeepCRC.h +++ b/Core/GameEngine/Include/Common/XferDeepCRC.h @@ -32,6 +32,9 @@ // USER INCLUDES ////////////////////////////////////////////////////////////////////////////////// #include "Common/Xfer.h" #include "Common/XferCRC.h" +#if DEEP_CRC_TO_MEMORY +#include +#endif // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// class Snapshot; @@ -55,9 +58,19 @@ class XferDeepCRC : public XferCRC virtual void xferAsciiString( AsciiString *asciiStringData ) override; ///< xfer ascii string (need our own) virtual void xferUnicodeString( UnicodeString *unicodeStringData ) override; ///< xfer unicode string (need our own); +#if DEEP_CRC_TO_MEMORY + virtual void xferLogString(const AsciiString& str) override; + void changeXferMode(XferMode xferMode); +#endif + protected: virtual void xferImplementation( void *data, Int dataSize ) override; +#if DEEP_CRC_TO_MEMORY + std::vector* m_buffer; ///< pointer to buffer + size_t m_bufferIndex; ///< current index in buffer +#else FILE * m_fileFP; ///< pointer to file +#endif }; diff --git a/Core/GameEngine/Source/Common/RandomValue.cpp b/Core/GameEngine/Source/Common/RandomValue.cpp index d5317a52852..c874648b024 100644 --- a/Core/GameEngine/Source/Common/RandomValue.cpp +++ b/Core/GameEngine/Source/Common/RandomValue.cpp @@ -76,6 +76,22 @@ UnsignedInt GetGameLogicRandomSeedCRC() return c.get(); } +#if DEEP_CRC_TO_MEMORY +AsciiString GetGameLogicalRandomSeeds() +{ + AsciiString str; + str.format("%8.8X, %8.8X, %8.8X, %8.8X, %8.8X, %8.8X", + theGameLogicSeed[0], + theGameLogicSeed[1], + theGameLogicSeed[2], + theGameLogicSeed[3], + theGameLogicSeed[4], + theGameLogicSeed[5]); + + return str; +} +#endif + static void seedRandom(UnsignedInt SEED, UnsignedInt (&seed)[6]) { UnsignedInt ax; diff --git a/Core/GameEngine/Source/Common/System/XferCRC.cpp b/Core/GameEngine/Source/Common/System/XferCRC.cpp index 12019d29e84..9567ff9dfc2 100644 --- a/Core/GameEngine/Source/Common/System/XferCRC.cpp +++ b/Core/GameEngine/Source/Common/System/XferCRC.cpp @@ -36,6 +36,10 @@ #include "Common/Snapshot.h" #include "Utility/endian_compat.h" +#if DEEP_CRC_TO_MEMORY +#include "GameLogic/GameLogic.h" +#endif + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- XferCRC::XferCRC() @@ -175,8 +179,14 @@ UnsignedInt XferCRC::getCRC() XferDeepCRC::XferDeepCRC() { +#if DEEP_CRC_TO_MEMORY + m_xferMode = XFER_CRC; + m_buffer = nullptr; + m_bufferIndex = 0; +#else m_xferMode = XFER_SAVE; m_fileFP = nullptr; +#endif } @@ -185,6 +195,7 @@ XferDeepCRC::XferDeepCRC() XferDeepCRC::~XferDeepCRC() { +#if !DEEP_CRC_TO_MEMORY // warn the user if a file was left open if( m_fileFP != nullptr ) { @@ -193,6 +204,7 @@ XferDeepCRC::~XferDeepCRC() close(); } +#endif } @@ -202,8 +214,13 @@ XferDeepCRC::~XferDeepCRC() void XferDeepCRC::open( AsciiString identifier ) { +#if DEEP_CRC_TO_MEMORY + m_xferMode = XFER_CRC; +#else m_xferMode = XFER_SAVE; +#endif +#if !DEEP_CRC_TO_MEMORY // sanity, check to see if we're already open if( m_fileFP != nullptr ) { @@ -213,10 +230,26 @@ void XferDeepCRC::open( AsciiString identifier ) throw XFER_FILE_ALREADY_OPEN; } +#endif // call base class Xfer::open( identifier ); +#if DEEP_CRC_TO_MEMORY + m_buffer = &TheGameLogic->getCRCBuffer(); + + AsciiString str; + str.format("[ START OF DEEP CRC FRAME %d ]", TheGameLogic->getFrame()); + const UnsignedInt length = str.getLength(); + + while (m_bufferIndex + length >= m_buffer->size()) + { + m_buffer->resize(m_buffer->size() * 2); + } + + memcpy(&(*m_buffer)[m_bufferIndex], str.str(), length); + m_bufferIndex += length; +#else // open the file m_fileFP = fopen( identifier.str(), "w+b" ); if( m_fileFP == nullptr ) @@ -226,6 +259,7 @@ void XferDeepCRC::open( AsciiString identifier ) throw XFER_FILE_NOT_FOUND; } +#endif // initialize CRC to brand new one at zero m_crc = 0; @@ -238,6 +272,21 @@ void XferDeepCRC::open( AsciiString identifier ) void XferDeepCRC::close() { +#if DEEP_CRC_TO_MEMORY + AsciiString str; + str.format("[ END OF DEEP CRC FRAME %d ]", TheGameLogic->getFrame()); + const UnsignedInt length = str.getLength(); + + while (m_bufferIndex + length >= m_buffer->size()) + { + m_buffer->resize(m_buffer->size() * 2); + } + + memcpy(&(*m_buffer)[m_bufferIndex], str.str(), length); + m_bufferIndex += length; + + TheGameLogic->storeCRCBuffer(m_bufferIndex); +#else // sanity, if we don't have an open file we can do nothing if( m_fileFP == nullptr ) { @@ -246,10 +295,15 @@ void XferDeepCRC::close() throw XFER_FILE_NOT_OPEN; } +#endif +#if DEEP_CRC_TO_MEMORY + m_buffer = nullptr; +#else // close the file fclose( m_fileFP ); m_fileFP = nullptr; +#endif // erase the filename m_identifier.clear(); @@ -267,6 +321,15 @@ void XferDeepCRC::xferImplementation( void *data, Int dataSize ) return; } +#if DEEP_CRC_TO_MEMORY + while (m_bufferIndex + dataSize >= m_buffer->size()) + { + m_buffer->resize(m_buffer->size() * 2); + } + + memcpy(&(*m_buffer)[m_bufferIndex], data, dataSize); + m_bufferIndex += dataSize; +#else // sanity DEBUG_ASSERTCRASH( m_fileFP != nullptr, ("XferSave - file pointer for '%s' is null", m_identifier.str()) ); @@ -279,6 +342,7 @@ void XferDeepCRC::xferImplementation( void *data, Int dataSize ) throw XFER_WRITE_ERROR; } +#endif XferCRC::xferImplementation( data, dataSize ); @@ -290,6 +354,11 @@ void XferDeepCRC::xferImplementation( void *data, Int dataSize ) void XferDeepCRC::xferMarkerLabel( AsciiString asciiStringData ) { +#if DEEP_CRC_TO_MEMORY + XferCRC::xferMarkerLabel(asciiStringData); + return; +#endif + } // ------------------------------------------------------------------------------------------------ @@ -298,6 +367,11 @@ void XferDeepCRC::xferMarkerLabel( AsciiString asciiStringData ) void XferDeepCRC::xferAsciiString( AsciiString *asciiStringData ) { +#if DEEP_CRC_TO_MEMORY + XferCRC::xferAsciiString(asciiStringData); + return; +#endif + // sanity if( asciiStringData->getLength() > 16385 ) { @@ -323,6 +397,11 @@ void XferDeepCRC::xferAsciiString( AsciiString *asciiStringData ) void XferDeepCRC::xferUnicodeString( UnicodeString *unicodeStringData ) { +#if DEEP_CRC_TO_MEMORY + XferCRC::xferUnicodeString(unicodeStringData); + return; +#endif + // sanity if( unicodeStringData->getLength() > 255 ) { @@ -341,3 +420,23 @@ void XferDeepCRC::xferUnicodeString( UnicodeString *unicodeStringData ) xferUser( (void *)unicodeStringData->str(), sizeof( WideChar ) * len ); } + +#if DEEP_CRC_TO_MEMORY +void XferDeepCRC::xferLogString(const AsciiString& str) +{ + const UnsignedInt length = str.getLength(); + + while (m_bufferIndex + length >= m_buffer->size()) + { + m_buffer->resize(m_buffer->size() * 2); + } + + memcpy(&(*m_buffer)[m_bufferIndex], str.str(), length); + m_bufferIndex += length; +} + +void XferDeepCRC::changeXferMode(XferMode xferMode) +{ + m_xferMode = xferMode; +} +#endif diff --git a/Core/GameEngine/Source/GameClient/ClientInstance.cpp b/Core/GameEngine/Source/GameClient/ClientInstance.cpp index 7b06108866a..a852cb0b418 100644 --- a/Core/GameEngine/Source/GameClient/ClientInstance.cpp +++ b/Core/GameEngine/Source/GameClient/ClientInstance.cpp @@ -27,6 +27,8 @@ UnsignedInt ClientInstance::s_instanceIndex = 0; #if defined(RTS_MULTI_INSTANCE) Bool ClientInstance::s_isMultiInstance = true; +#elif DEEP_CRC_TO_MEMORY +Bool ClientInstance::s_isMultiInstance = true; #else Bool ClientInstance::s_isMultiInstance = false; #endif diff --git a/Core/GameEngine/Source/GameNetwork/Network.cpp b/Core/GameEngine/Source/GameNetwork/Network.cpp index 85b6563c18b..3871dd2b815 100644 --- a/Core/GameEngine/Source/GameNetwork/Network.cpp +++ b/Core/GameEngine/Source/GameNetwork/Network.cpp @@ -55,6 +55,8 @@ #if defined(DEBUG_CRC) Int NET_CRC_INTERVAL = 1; +#elif DEEP_CRC_TO_MEMORY +Int NET_CRC_INTERVAL = 1; #else Int NET_CRC_INTERVAL = 100; #endif diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index b1711c5afca..ab088de1882 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -405,6 +405,16 @@ class GameLogic : public SubsystemInterface, public Snapshot void xferObjectTOC( Xfer *xfer ); ///< save/load object TOC for current state of map void prepareLogicForObjectLoad(); ///< prepare engine for object data from game file +#if DEEP_CRC_TO_MEMORY + UnsignedInt m_crcBufferIndex; + std::vector m_crcWriteBuffer; + std::vector m_crcBuffers[64]; + +public: + std::vector& getCRCBuffer(); + void storeCRCBuffer(size_t size); + void writeCRCBuffersToDisk(UnsignedInt frame) const; +#endif }; // INLINE ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index bfdf333393f..f605615ef11 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -818,6 +818,10 @@ class Object : public Thing, public Snapshot Bool m_singleUseCommandUsed; Bool m_isReceivingDifficultyBonus; +#if DEEP_CRC_TO_MEMORY +public: + const UpgradeMaskType& getUpgrades() const { return m_objectUpgradesCompleted; } +#endif }; // deleteInstance is not meant to be used with Object in order to require the use of TheGameLogic->destroyObject() diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp index 8150064a028..c1692adbd6b 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Recorder.cpp @@ -1108,6 +1108,10 @@ void RecorderClass::handleCRCMessage(UnsignedInt newCRC, Int playerIndex, Bool f // playbackCRC, newCRC, TheGameLogic->getFrame()-m_crcInfo->GetQueueSize()-1, playerIndex)); if (TheGameLogic->getFrame() > 0 && newCRC != playbackCRC && !m_crcInfo->sawCRCMismatch()) { +#if DEEP_CRC_TO_MEMORY + TheGameLogic->writeCRCBuffersToDisk(TheGameLogic->getFrame() - m_crcInfo->GetQueueSize() - 1); +#endif + //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 diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 14a4b7ef7fe..267817f5e0a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -130,6 +130,9 @@ FILE *g_UT_commaLog=nullptr; extern void externalAddTree(Coord3D location, Real scale, Real angle, AsciiString name); #endif +#if DEEP_CRC_TO_MEMORY +#include "GameClient/ClientInstance.h" +#endif @@ -467,6 +470,19 @@ void GameLogic::reset() TheWeatherSetting = (WeatherSetting*) ws->deleteOverrides(); m_rankPointsToAddAtGameStart = 0; + +#if DEEP_CRC_TO_MEMORY + m_crcBufferIndex = 0; + + m_crcWriteBuffer.resize(1024 * 1024 * 8); + + { + for (size_t i = 0; i < ARRAY_SIZE(m_crcBuffers); ++i) + { + m_crcBuffers[i].resize(1024 * 1024); + } + } +#endif } static Object * placeObjectAtPosition(Int slotNum, AsciiString objectTemplateName, Coord3D& pos, Player *pPlayer, @@ -2619,6 +2635,11 @@ void GameLogic::processCommandList( CommandList *list ) player?player->getPlayerDisplayName().str():L"", crcIt->second)); } #endif // DEBUG_LOGGING + +#if DEEP_CRC_TO_MEMORY + TheGameLogic->writeCRCBuffersToDisk(TheGameLogic->getFrame() - TheNetwork->getRunAhead() - 1); +#endif + TheNetwork->setSawCRCMismatch(); } } @@ -4043,7 +4064,14 @@ UnsignedInt GameLogic::getCRC( Int mode, AsciiString deepCRCFileName ) XferCRC *xferCRC; AsciiString marker; - if (deepCRCFileName.isNotEmpty()) + +#if DEEP_CRC_TO_MEMORY + const Bool forceDeepCRC = TRUE; +#else + const Bool forceDeepCRC = FALSE; +#endif + + if (forceDeepCRC || deepCRCFileName.isNotEmpty()) { xferCRC = NEW XferDeepCRC; xferCRC->open(deepCRCFileName.str()); @@ -4145,9 +4173,58 @@ UnsignedInt GameLogic::getCRC( Int mode, AsciiString deepCRCFileName ) TheGameState->friend_xferSaveDataForCRC(xferCRC, SNAPSHOT_DEEPCRC_LOGICONLY); } - xferCRC->close(); + const UnsignedInt theCRC = xferCRC->getCRC(); + +#if DEEP_CRC_TO_MEMORY + AsciiString tmp; + tmp.format("[ frame %d: %8.8X, logical seeds: %s ]", m_frame, theCRC, GetGameLogicalRandomSeeds().str()); + + xferCRC->xferLogString(tmp); + + for (Int j = 0; j < ThePlayerList->getPlayerCount(); ++j) + { + if (Player* player = ThePlayerList->getNthPlayer(j)) + { + tmp.format("[ Player (%d) money: %d, energy: %d | %d, power sabotage: %d ]", + j, player->getMoney()->countMoney(), player->getEnergy()->getProduction(), player->getEnergy()->getConsumption(), player->getEnergy()->getPowerSabotagedTillFrame()); + + xferCRC->xferLogString(tmp); + } + } + + for (obj = m_objList; obj; obj=obj->getNextObject()) + { + XferCRC tmpXfer; + tmpXfer.open(""); + tmpXfer.xferUser(const_cast(obj->getTransformMatrix()), sizeof(Matrix3D)); + tmpXfer.close(); + + const UnsignedInt mtxCRC = tmpXfer.getCRC(); + + tmpXfer.open(""); + tmpXfer.xferUser(const_cast(&obj->getUpgrades()), sizeof(UpgradeMaskType)); + tmpXfer.close(); + + const UnsignedInt upgradeCRC = tmpXfer.getCRC(); + + tmp.format("[ CRC of object: %d (%s), player: %d, team: %d, health: %f, upgrades: %8.8X, pos: %f %f %f, mtx: %8.8X ]", + obj->getID(), obj->getTemplate()->getName().str(), obj->getControllingPlayer()->getPlayerIndex(), (obj->getTeam() ? obj->getTeam()->getID() : TEAM_ID_INVALID), + obj->getBodyModule()->getHealth(), upgradeCRC, obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z, mtxCRC); + + xferCRC->xferLogString(tmp); + } + + // disable for now because it's quite a bit of data, and it may not be necessary + /* + static_cast(xferCRC)->changeXferMode(XFER_SAVE); + + marker = "MARKER:GameSave"; + xferCRC->xferAsciiString(&marker); + TheGameState->friend_xferSaveDataForCRC(xferCRC, SNAPSHOT_DEEPCRC_LOGICONLY); + //*/ +#endif - UnsignedInt theCRC = xferCRC->getCRC(); + xferCRC->close(); delete xferCRC; xferCRC = nullptr; @@ -4795,6 +4872,63 @@ void GameLogic::prepareLogicForObjectLoad() } +#if DEEP_CRC_TO_MEMORY +std::vector& GameLogic::getCRCBuffer() +{ + return m_crcWriteBuffer; +} + +void GameLogic::storeCRCBuffer(size_t size) +{ + std::vector& vec = m_crcBuffers[m_crcBufferIndex++ % ARRAY_SIZE(m_crcBuffers)]; + + vec.clear(); + vec.insert(vec.begin(), m_crcWriteBuffer.begin(), m_crcWriteBuffer.begin() + size); +} + +void GameLogic::writeCRCBuffersToDisk(UnsignedInt frame) const +{ + AsciiString str; + const UnsignedInt timestamp = static_cast(time(nullptr)); + + const UnsignedInt id = rts::ClientInstance::getInstanceId(); + if (id == 1) + { + str.format("%scrc_buffer_%d.bin", TheGlobalData->getPath_UserData().str(), timestamp); + } + else + { + str.format("%scrc_buffer_%d_instance%d.bin", TheGlobalData->getPath_UserData().str(), timestamp, id); + } + + FILE* fp = fopen(str.str(), "wb"); + if (fp) + { + constexpr const char version[] = "[ DEEP CRC DATA (VERSION 0.0.3) ]"; + + if (fwrite(&version[0], ARRAY_SIZE(version) - 1, 1, fp) != 1) + { + // todo: handle error + } + + for (size_t i = 0; i < ARRAY_SIZE(m_crcBuffers); ++i) + { + const UnsignedInt index = (m_crcBufferIndex + i) % ARRAY_SIZE(m_crcBuffers); + if (fwrite(&m_crcBuffers[index][0], m_crcBuffers[index].size(), 1, fp) != 1) + { + // todo: handle error + } + } + + fclose(fp); + } + else + { + // todo: handle error + } +} +#endif + // ------------------------------------------------------------------------------------------------ /** Load/Save game logic to xfer * Version Info: From 2a438b81b1e626e2f10e62905d74e48c0a1f2d10 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 11 Apr 2026 23:13:43 +0200 Subject: [PATCH 2/2] Added pathfinding specific logging. --- .../Source/GameLogic/AI/AIPathfind.cpp | 5 + .../GameEngine/Include/GameLogic/GameLogic.h | 22 ++++ .../Include/GameLogic/Module/AIUpdate.h | 2 +- .../GameLogic/Object/Update/AIUpdate.cpp | 54 +++++++++ .../Source/GameLogic/System/GameLogic.cpp | 110 +++++++++++++++++- 5 files changed, 191 insertions(+), 2 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp index f6a63be76c2..89e2e887485 100644 --- a/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp +++ b/Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp @@ -5813,6 +5813,11 @@ Bool Pathfinder::queueForPath(ObjectID id) } #endif +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(0xFFFFFFFF, id); + TheGameLogic->addCRCPathfindingData(id, m_queuePRHead, m_queuePRTail); +#endif + /* Check & see if we are already queued. */ Int slot = m_queuePRHead; while (slot != m_queuePRTail) { diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h index ab088de1882..d4bacd5cd55 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/GameLogic.h @@ -406,11 +406,33 @@ class GameLogic : public SubsystemInterface, public Snapshot void prepareLogicForObjectLoad(); ///< prepare engine for object data from game file #if DEEP_CRC_TO_MEMORY +public: + struct PathFindingCRCData + { + ObjectID id; + Int queuePRHead; + Int m_queuePRTail; + Coord3D curPos; + Coord3D requestedPos1; + Coord3D requestedPos2; + }; + + struct PathFindingCallSites + { + UnsignedInt val; + ObjectID id; + }; + +private: UnsignedInt m_crcBufferIndex; std::vector m_crcWriteBuffer; std::vector m_crcBuffers[64]; + std::vector m_crcPathFindingData; + std::vector m_crcPathFindingCallSites; public: + void addCRCPathFindingCallSite(UnsignedInt val, ObjectID id); + void addCRCPathfindingData(ObjectID id, Int queuePRHead, Int m_queuePRTail); std::vector& getCRCBuffer(); void storeCRCBuffer(size_t size); void writeCRCBuffersToDisk(UnsignedInt frame) const; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h index a475e3d40b1..e7bf256fd93 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h @@ -650,7 +650,7 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface // this is intended for use ONLY by AIFollowPathState. void friend_setCurrentGoalPathIndex( Int index ) { m_nextGoalPathIndex = index; } -#ifdef DEBUG_LOGGING +#if (DEBUG_LOGGING || DEEP_CRC_TO_MEMORY) inline const Coord3D *friend_getRequestedDestination() const { return &m_requestedDestination; } inline const Coord3D *friend_getRequestedDestination2() const { return &m_requestedDestination2; } #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp index 76c94b122c3..7a94185936b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate.cpp @@ -478,6 +478,9 @@ will be processed when we get to the front of the pathfind queue. jba */ //------------------------------------------------------------------------------------------------- void AIUpdateInterface::requestPath( Coord3D *destination, Bool isFinalGoal ) { +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(1, getObject()->getID()); +#endif if (m_locomotorSet.getValidSurfaces() == 0) { DEBUG_CRASH(("Attempting to path immobile unit.")); @@ -493,6 +496,11 @@ void AIUpdateInterface::requestPath( Coord3D *destination, Bool isFinalGoal ) m_isSafePath = FALSE; if (canComputeQuickPath()) { computeQuickPath(destination); + +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(0xCC01DD01, getObject()->getID()); +#endif + return; } m_waitingForPath = TRUE; @@ -510,6 +518,11 @@ void AIUpdateInterface::requestPath( Coord3D *destination, Bool isFinalGoal ) m_isBlocked = FALSE; m_isBlockedAndStuck = FALSE; } + +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(0xCC01DD02, getObject()->getID()); +#endif + return; } TheAI->pathfinder()->queueForPath(getObject()->getID()); @@ -519,6 +532,10 @@ void AIUpdateInterface::requestPath( Coord3D *destination, Bool isFinalGoal ) //------------------------------------------------------------------------------------------------- void AIUpdateInterface::requestAttackPath( ObjectID victimID, const Coord3D* victimPos ) { +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(2, getObject()->getID()); +#endif + if (m_locomotorSet.getValidSurfaces() == 0) { DEBUG_CRASH(("Attempting to path immobile unit.")); } @@ -534,6 +551,11 @@ void AIUpdateInterface::requestAttackPath( ObjectID victimID, const Coord3D* vic //DEBUG_LOG(("%d Pathfind - repathing in less than 3 frames. Waiting 2 second",TheGameLogic->getFrame())); setQueueForPathTime(2*LOGICFRAMES_PER_SECOND); setLocomotorGoalNone(); + +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(0xCC02DD01, getObject()->getID()); +#endif + return; } TheAI->pathfinder()->queueForPath(getObject()->getID()); @@ -542,6 +564,10 @@ void AIUpdateInterface::requestAttackPath( ObjectID victimID, const Coord3D* vic //------------------------------------------------------------------------------------------------- void AIUpdateInterface::requestApproachPath( Coord3D *destination ) { +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(3, getObject()->getID()); +#endif + if (m_locomotorSet.getValidSurfaces() == 0) { DEBUG_CRASH(("Attempting to path immobile unit.")); } @@ -557,6 +583,11 @@ void AIUpdateInterface::requestApproachPath( Coord3D *destination ) /* Requesting path very quickly. Can cause a spin. */ //DEBUG_LOG(("%d Pathfind - repathing in less than 3 frames. Waiting 2 second",TheGameLogic->getFrame())); setQueueForPathTime(2*LOGICFRAMES_PER_SECOND); + +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(0xCC03DD01, getObject()->getID()); +#endif + return; } TheAI->pathfinder()->queueForPath(getObject()->getID()); @@ -566,6 +597,10 @@ void AIUpdateInterface::requestApproachPath( Coord3D *destination ) // Requests a safe path away from the repulsor. void AIUpdateInterface::requestSafePath( ObjectID repulsor ) { +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(4, getObject()->getID()); +#endif + if (repulsor != m_repulsor1) { m_repulsor2 = m_repulsor1; // save the prior repulsor. } @@ -581,6 +616,11 @@ void AIUpdateInterface::requestSafePath( ObjectID repulsor ) /* Requesting path very quickly. Can cause a spin. */ //DEBUG_LOG(("%d Pathfind - repathing in less than 3 frames. Waiting 2 second",TheGameLogic->getFrame())); setQueueForPathTime(2*LOGICFRAMES_PER_SECOND); + +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(0xCC04DD01, getObject()->getID()); +#endif + return; } TheAI->pathfinder()->queueForPath(getObject()->getID()); @@ -1001,6 +1041,10 @@ void AIUpdateInterface::friend_notifyStateMachineChanged() DECLARE_PERF_TIMER(AIUpdateInterface_update) UpdateSleepTime AIUpdateInterface::update() { +#if DEEP_CRC_TO_MEMORY + //TheGameLogic->addCRCPathFindingCallSite(5, getObject()->getID()); +#endif + //DEBUG_LOG(("AIUpdateInterface frame %d: %08lx",TheGameLogic->getFrame(),getObject())); USE_PERF_TIMER(AIUpdateInterface_update) @@ -1071,11 +1115,21 @@ UpdateSleepTime AIUpdateInterface::update() } else { +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(0xCC05DD02, getObject()->getID()); +#endif + UnsignedInt sleepForPathDelta = m_queueForPathFrame - now; if (sleepForPathDelta < subMachineSleep) subMachineSleep = UPDATE_SLEEP(sleepForPathDelta); } } + else + { +#if DEEP_CRC_TO_MEMORY + TheGameLogic->addCRCPathFindingCallSite(0xCC05DD01, getObject()->getID()); +#endif + } Object *obj = getObject(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 267817f5e0a..1d93ba66e5f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -2637,6 +2637,17 @@ void GameLogic::processCommandList( CommandList *list ) #endif // DEBUG_LOGGING #if DEEP_CRC_TO_MEMORY + // disabled for now because it's largely hidden behind the mismatch window + /* + TheInGameUI->message("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); + TheInGameUI->message("CRC from player %d (%ls) = %X", crcIt->first, + player ? player->getPlayerDisplayName().str() : L"", crcIt->second); + } + */ + TheGameLogic->writeCRCBuffersToDisk(TheGameLogic->getFrame() - TheNetwork->getRunAhead() - 1); #endif @@ -4873,6 +4884,51 @@ void GameLogic::prepareLogicForObjectLoad() } #if DEEP_CRC_TO_MEMORY +void GameLogic::addCRCPathFindingCallSite(UnsignedInt val, ObjectID id) +{ + PathFindingCallSites data; + data.val = val; + data.id = id; + + m_crcPathFindingCallSites.push_back(data); +} + +void GameLogic::addCRCPathfindingData(ObjectID id, Int queuePRHead, Int m_queuePRTail) +{ + PathFindingCRCData data; + data.id = id; + data.queuePRHead = queuePRHead; + data.m_queuePRTail = m_queuePRTail; + + Real r; + constexpr UnsignedInt u = 0xDEADC0DE; + + static_assert(sizeof(r) == sizeof(u)); + memcpy(&r, &u, sizeof(r)); + + if (Object* obj = TheGameLogic->findObjectByID(id)) + { + data.curPos = *obj->getPosition(); + + if (AIUpdateInterface* ai = obj->getAIUpdateInterface()) + { + data.requestedPos1 = *ai->friend_getRequestedDestination(); + data.requestedPos2 = *ai->friend_getRequestedDestination2(); + } + else + { + data.requestedPos1.set(r, r, r); + data.requestedPos2.set(r, r, r); + } + } + else + { + data.curPos.set(r, r, r); + } + + m_crcPathFindingData.push_back(data); +} + std::vector& GameLogic::getCRCBuffer() { return m_crcWriteBuffer; @@ -4880,6 +4936,58 @@ std::vector& GameLogic::getCRCBuffer() void GameLogic::storeCRCBuffer(size_t size) { + { + constexpr const char marker[] = "MARKER:PathfindingCallSites"; + constexpr size_t markerSize = ARRAY_SIZE(marker) - 1; + + while (size + markerSize >= m_crcWriteBuffer.size()) + { + m_crcWriteBuffer.resize(m_crcWriteBuffer.size() * 2); + } + + memcpy(&m_crcWriteBuffer[size], &marker[0], markerSize); + size += markerSize; + + constexpr size_t dataSize = sizeof(m_crcPathFindingCallSites[0]); + while (size + dataSize * m_crcPathFindingCallSites.size() >= m_crcWriteBuffer.size()) + { + m_crcWriteBuffer.resize(m_crcWriteBuffer.size() * 2); + } + + for (size_t i = 0; i < m_crcPathFindingCallSites.size(); ++i, size += dataSize) + { + memcpy(&m_crcWriteBuffer[size], &m_crcPathFindingCallSites[0], dataSize); + } + + m_crcPathFindingCallSites.clear(); + } + + { + constexpr const char marker[] = "MARKER:CustomPathfindingData"; + constexpr size_t markerSize = ARRAY_SIZE(marker) - 1; + + while (size + markerSize >= m_crcWriteBuffer.size()) + { + m_crcWriteBuffer.resize(m_crcWriteBuffer.size() * 2); + } + + memcpy(&m_crcWriteBuffer[size], &marker[0], markerSize); + size += markerSize; + + constexpr size_t dataSize = sizeof(m_crcPathFindingData[0]); + while (size + dataSize * m_crcPathFindingData.size() >= m_crcWriteBuffer.size()) + { + m_crcWriteBuffer.resize(m_crcWriteBuffer.size() * 2); + } + + for (size_t i = 0; i < m_crcPathFindingData.size(); ++i, size += dataSize) + { + memcpy(&m_crcWriteBuffer[size], &m_crcPathFindingData[0], dataSize); + } + + m_crcPathFindingData.clear(); + } + std::vector& vec = m_crcBuffers[m_crcBufferIndex++ % ARRAY_SIZE(m_crcBuffers)]; vec.clear(); @@ -4904,7 +5012,7 @@ void GameLogic::writeCRCBuffersToDisk(UnsignedInt frame) const FILE* fp = fopen(str.str(), "wb"); if (fp) { - constexpr const char version[] = "[ DEEP CRC DATA (VERSION 0.0.3) ]"; + constexpr const char version[] = "[ DEEP CRC DATA (VERSION 0.0.4) ]"; if (fwrite(&version[0], ARRAY_SIZE(version) - 1, 1, fp) != 1) {