diff --git a/Core/GameEngine/Include/GameNetwork/networkutil.h b/Core/GameEngine/Include/GameNetwork/networkutil.h index 944e2ce0a60..7775bf92f16 100644 --- a/Core/GameEngine/Include/GameNetwork/networkutil.h +++ b/Core/GameEngine/Include/GameNetwork/networkutil.h @@ -30,6 +30,7 @@ UnsignedInt AssembleIp(UnsignedByte a, UnsignedByte b, UnsignedByte c, UnsignedByte d); UnsignedInt ResolveIP(AsciiString host); UnsignedShort GenerateNextCommandID(); +void ResetCommandID(); Bool DoesCommandRequireACommandID(NetCommandType type); Bool CommandRequiresAck(NetCommandMsg *msg); Bool CommandRequiresDirectSend(NetCommandMsg *msg); diff --git a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp index b6a834c987b..2e43fa86d62 100644 --- a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp @@ -125,6 +125,10 @@ void NetCommandList::reset() { /** * Insert sorts msg. Assumes that all the previous message inserts were done using this function. * The message is sorted in based first on command type, then player id, and then command id. + * + * TheSuperHackers @info Caball009 20/05/2026 The command id may overflow and the sorting in this function + * does not check against that. Changing the sorting implementation to deal with overflows is not retail compatible, + * unlike resetting the command id. */ NetCommandRef * NetCommandList::addMessage(NetCommandMsg *cmdMsg) { if (cmdMsg == nullptr) { diff --git a/Core/GameEngine/Source/GameNetwork/Network.cpp b/Core/GameEngine/Source/GameNetwork/Network.cpp index 5afa4c6d743..83c94f5678c 100644 --- a/Core/GameEngine/Source/GameNetwork/Network.cpp +++ b/Core/GameEngine/Source/GameNetwork/Network.cpp @@ -37,6 +37,7 @@ #include "Common/Player.h" #include "Common/PlayerList.h" #include "GameNetwork/NetworkInterface.h" +#include "GameNetwork/networkutil.h" #include "GameNetwork/udp.h" #include "GameNetwork/Transport.h" #include "strtok_r.h" @@ -201,6 +202,7 @@ class Network : public NetworkInterface Int m_frameRate; Int m_lastExecutionFrame; ///< The highest frame number that a command could have been executed on. Int m_lastFrameCompleted; + Int m_lastFrameResetCommandID; ///< The last frame the network command ID was reset Bool m_didSelfSlug; __int64 m_perfCountFreq; ///< The frequency of the performance counter. @@ -273,6 +275,8 @@ Network::Network() m_conMgr = nullptr; m_messageWindow = nullptr; + m_lastFrameResetCommandID = 0; + #if defined(RTS_DEBUG) m_networkOn = TRUE; #endif @@ -331,6 +335,7 @@ void Network::init() m_frameRate = 30; m_lastExecutionFrame = m_runAhead - 1; // subtract 1 since we're starting on frame 0 m_lastFrameCompleted = m_runAhead - 1; // subtract 1 since we're starting on frame 0 + m_lastFrameResetCommandID = 0; m_frameDataReady = FALSE; m_isStalling = FALSE; m_didSelfSlug = FALSE; @@ -356,6 +361,7 @@ void Network::init() DEBUG_LOG(("FRAME_DATA_LENGTH = %d", FRAME_DATA_LENGTH)); DEBUG_LOG(("FRAMES_TO_KEEP = %d", FRAMES_TO_KEEP)); + ResetCommandID(); #if defined(RTS_DEBUG) m_networkOn = TRUE; @@ -697,6 +703,19 @@ void Network::update() } #endif + const UnsignedInt frame = TheGameLogic->getFrame(); + if (m_localStatus == NETLOCALSTATUS_INGAME) { + if (frame + m_runAhead > m_lastExecutionFrame) { + if (frame >= m_lastFrameResetCommandID + MAX_FRAMES_AHEAD) { + // TheSuperHackers @bugfix Caball009 20/05/2026 Reset the network command id to prevent it from overflowing. + // This prevents commands from being sorted incorrectly, which can cause spurious mismatches at low CRC intervals + // and the disconnect screen from popping up. + m_lastFrameResetCommandID = frame; + ResetCommandID(); + } + } + } + GetCommandsFromCommandList(); // Remove commands from TheCommandList and send them to the connection manager. if (m_conMgr != nullptr) { if (m_localStatus == NETLOCALSTATUS_INGAME) { @@ -713,11 +732,11 @@ void Network::update() endOfGameCheck(); } - if (AllCommandsReady(TheGameLogic->getFrame())) { // If all the commands are ready for the next frame... + if (AllCommandsReady(frame)) { // If all the commands are ready for the next frame... m_conMgr->handleAllCommandsReady(); -// DEBUG_LOG(("Network::update - frame %d is ready", TheGameLogic->getFrame())); +// DEBUG_LOG(("Network::update - frame %d is ready", frame)); if (timeForNewFrame()) { // This needs to come after any other pre-frame execution checks as this changes the timing variables. - RelayCommandsToCommandList(TheGameLogic->getFrame()); // Put the commands for the next frame on TheCommandList. + RelayCommandsToCommandList(frame); // Put the commands for the next frame on TheCommandList. m_frameDataReady = TRUE; // Tell the GameEngine to run the commands for the new frame. } } diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index 174599cc97c..0ff84fb852a 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -110,10 +110,15 @@ UnsignedInt ResolveIP(AsciiString host) /** * Returns the next network command ID. */ -UnsignedShort GenerateNextCommandID() { - static UnsignedShort commandID = 64000; - ++commandID; - return commandID; +static UnsignedShort commandID = 0; +UnsignedShort GenerateNextCommandID() +{ + return ++commandID; +} + +void ResetCommandID() +{ + commandID = 0; } /**