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: