diff --git a/Core/GameEngine/Include/GameNetwork/networkutil.h b/Core/GameEngine/Include/GameNetwork/networkutil.h index 722c59fd7ff..8a9cea28d07 100644 --- a/Core/GameEngine/Include/GameNetwork/networkutil.h +++ b/Core/GameEngine/Include/GameNetwork/networkutil.h @@ -30,6 +30,8 @@ UnsignedInt AssembleIp(UnsignedByte a, UnsignedByte b, UnsignedByte c, UnsignedByte d); UnsignedInt ResolveIP(AsciiString host); UnsignedShort GenerateNextCommandID(); +UnsignedShort GetCommandID(); +void ResetCommandID(); Bool DoesCommandRequireACommandID(NetCommandType type); Bool CommandRequiresAck(const NetCommandMsg *msg); Bool CommandRequiresDirectSend(const NetCommandMsg *msg); diff --git a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp index 579b8578c72..e0157281b30 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 06/06/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 b5501e32e26..354c611220f 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" @@ -356,6 +357,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; @@ -456,6 +458,7 @@ Bool Network::isMessageTypeWithinNetworkRange(GameMessage::Type type) { void Network::GetCommandsFromCommandList() { GameMessage *msg = TheCommandList->getFirstMessage(); GameMessage *next = nullptr; + while (msg != nullptr) { next = msg->next(); if (isMessageTypeWithinNetworkRange(msg->getType())) { // Is this something we should be sending to the other players? @@ -702,6 +705,18 @@ void Network::update() } #endif + const UnsignedInt frame = TheGameLogic->getFrame(); + + if (m_localStatus == NETLOCALSTATUS_INGAME) { + if (frame + m_runAhead > m_lastExecutionFrame) { + // TheSuperHackers @bugfix Caball009 06/06/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. + if (GetCommandID() >= 64000) { + ResetCommandID(); + } + } + } + GetCommandsFromCommandList(); // Remove commands from TheCommandList and send them to the connection manager. if (m_conMgr != nullptr) { if (m_localStatus == NETLOCALSTATUS_INGAME) { @@ -718,11 +733,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 d48280759b6..3f8883be13f 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -102,10 +102,20 @@ UnsignedInt ResolveIP(AsciiString host) /** * Returns the next network command ID. */ -UnsignedShort GenerateNextCommandID() { - static UnsignedShort commandID = 64000; - ++commandID; - return commandID; +static UnsignedShort s_commandID = 0; +UnsignedShort GenerateNextCommandID() +{ + return s_commandID++; +} + +UnsignedShort GetCommandID() +{ + return s_commandID; +} + +void ResetCommandID() +{ + s_commandID = 0; } /**