From 52d5e821782c0af5f1716f44d1fbb3b33cd721b5 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 19 May 2026 22:04:16 +0200 Subject: [PATCH 1/3] Added test code. --- .../GameLogic/System/GameLogicDispatch.cpp | 22 +++++++++++++++++++ .../Source/GameNetwork/NetworkUtil.cpp | 10 +++++++++ .../GameEngine/Include/Common/MessageStream.h | 2 +- .../Source/GameLogic/System/GameLogic.cpp | 12 ++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index c002ff8f239..b2c5e762db4 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -420,6 +420,28 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) GameMessage::Type msgType = msg->getType(); switch( msgType ) { + case GameMessage::MSG_TEST_SEQUENTIAL_ORDER: + { + static int s_seq[MAX_PLAYER_COUNT] = { 0 }; + static int count = 250; + + if (s_seq[msg->getPlayerIndex()] == count) { + s_seq[msg->getPlayerIndex()] = 0; + } + + const int seq = msg->getArgument(0)->integer; + if (s_seq[msg->getPlayerIndex()] + 1 != seq) { + const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( + "GUI:SequentialOrderMismatch", L"Frame:%d Player:%ls --- Expected seq %d, Actual seq %d!"); + TheInGameUI->message(mismatchDetailsStr, TheGameLogic->getFrame(), msgPlayer->getPlayerDisplayName().str(), s_seq[msg->getPlayerIndex()] + 1, seq); + + s_seq[msg->getPlayerIndex()] = seq; + } else { + s_seq[msg->getPlayerIndex()] += 1; + } + } + break; + //--------------------------------------------------------------------------------------------- case GameMessage::MSG_NEW_GAME: { diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index 174599cc97c..1850b07ab04 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -26,6 +26,8 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "GameNetwork/networkutil.h" +#include "GameClient/GameText.h" +#include "GameClient/InGameUI.h" // TheSuperHackers @tweak Mauller 26/08/2025 reduce the minimum runahead from 10 // This lets network games run at latencies down to 133ms when the network conditions allow @@ -113,6 +115,14 @@ UnsignedInt ResolveIP(AsciiString host) UnsignedShort GenerateNextCommandID() { static UnsignedShort commandID = 64000; ++commandID; + + if (commandID == MAXUINT16) + { + const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( + "NETWORK:COMMANDIDOVERFLOW", L"The command ID is about to overflow"); + TheInGameUI->message(mismatchDetailsStr); + } + return commandID; } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h index 0a08d98486d..c3a204c4e3c 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h @@ -614,7 +614,7 @@ class GameMessage : public MemoryPoolObject MSG_DEBUG_KILL_OBJECT, #endif - + MSG_TEST_SEQUENTIAL_ORDER, //********************************************************************************************************* MSG_END_NETWORK_MESSAGES = 1999, ///< MARKER TO DELINEATE MESSAGES THAT GO OVER THE NETWORK diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index 622aeb5da10..c61b74da20b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3750,6 +3750,18 @@ void GameLogic::update() Bool generateForSolo = isSoloGameOrReplay && ((m_frame % REPLAY_CRC_INTERVAL) == 0); #endif // DEBUG_CRC + // + static int interval = 1; + static int count = 250; + if (interval > 0 && m_frame % interval == 0) { + for (int i = 1; i <= count; ++i) { + GameMessage* msg = TheMessageStream->appendMessage(GameMessage::MSG_TEST_SEQUENTIAL_ORDER); + msg->appendIntegerArgument(i); + //msg->appendIntegerArgument(m_frame); + } + } + // + if (generateForSolo || generateForMP) { m_CRC = getCRC( CRC_RECALC ); From 058e1f18e7197674eef2c144a604889e42348f5b Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 19 May 2026 22:04:36 +0200 Subject: [PATCH 2/3] Revert "Added test code." This reverts commit 52d5e821782c0af5f1716f44d1fbb3b33cd721b5. --- .../GameLogic/System/GameLogicDispatch.cpp | 22 ------------------- .../Source/GameNetwork/NetworkUtil.cpp | 10 --------- .../GameEngine/Include/Common/MessageStream.h | 2 +- .../Source/GameLogic/System/GameLogic.cpp | 12 ---------- 4 files changed, 1 insertion(+), 45 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index b2c5e762db4..c002ff8f239 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -420,28 +420,6 @@ void GameLogic::logicMessageDispatcher( GameMessage *msg, void *userData ) GameMessage::Type msgType = msg->getType(); switch( msgType ) { - case GameMessage::MSG_TEST_SEQUENTIAL_ORDER: - { - static int s_seq[MAX_PLAYER_COUNT] = { 0 }; - static int count = 250; - - if (s_seq[msg->getPlayerIndex()] == count) { - s_seq[msg->getPlayerIndex()] = 0; - } - - const int seq = msg->getArgument(0)->integer; - if (s_seq[msg->getPlayerIndex()] + 1 != seq) { - const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( - "GUI:SequentialOrderMismatch", L"Frame:%d Player:%ls --- Expected seq %d, Actual seq %d!"); - TheInGameUI->message(mismatchDetailsStr, TheGameLogic->getFrame(), msgPlayer->getPlayerDisplayName().str(), s_seq[msg->getPlayerIndex()] + 1, seq); - - s_seq[msg->getPlayerIndex()] = seq; - } else { - s_seq[msg->getPlayerIndex()] += 1; - } - } - break; - //--------------------------------------------------------------------------------------------- case GameMessage::MSG_NEW_GAME: { diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index 1850b07ab04..174599cc97c 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -26,8 +26,6 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "GameNetwork/networkutil.h" -#include "GameClient/GameText.h" -#include "GameClient/InGameUI.h" // TheSuperHackers @tweak Mauller 26/08/2025 reduce the minimum runahead from 10 // This lets network games run at latencies down to 133ms when the network conditions allow @@ -115,14 +113,6 @@ UnsignedInt ResolveIP(AsciiString host) UnsignedShort GenerateNextCommandID() { static UnsignedShort commandID = 64000; ++commandID; - - if (commandID == MAXUINT16) - { - const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( - "NETWORK:COMMANDIDOVERFLOW", L"The command ID is about to overflow"); - TheInGameUI->message(mismatchDetailsStr); - } - return commandID; } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h index c3a204c4e3c..0a08d98486d 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h @@ -614,7 +614,7 @@ class GameMessage : public MemoryPoolObject MSG_DEBUG_KILL_OBJECT, #endif - MSG_TEST_SEQUENTIAL_ORDER, + //********************************************************************************************************* MSG_END_NETWORK_MESSAGES = 1999, ///< MARKER TO DELINEATE MESSAGES THAT GO OVER THE NETWORK diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp index c61b74da20b..622aeb5da10 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3750,18 +3750,6 @@ void GameLogic::update() Bool generateForSolo = isSoloGameOrReplay && ((m_frame % REPLAY_CRC_INTERVAL) == 0); #endif // DEBUG_CRC - // - static int interval = 1; - static int count = 250; - if (interval > 0 && m_frame % interval == 0) { - for (int i = 1; i <= count; ++i) { - GameMessage* msg = TheMessageStream->appendMessage(GameMessage::MSG_TEST_SEQUENTIAL_ORDER); - msg->appendIntegerArgument(i); - //msg->appendIntegerArgument(m_frame); - } - } - // - if (generateForSolo || generateForMP) { m_CRC = getCRC( CRC_RECALC ); From d11b6f7410c036b356e58f73f0ba003aff903eac Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Tue, 19 May 2026 22:19:41 +0200 Subject: [PATCH 3/3] Added code to reset command id. --- .../Include/GameNetwork/networkutil.h | 1 + .../Source/GameNetwork/NetCommandList.cpp | 4 +++ .../GameEngine/Source/GameNetwork/Network.cpp | 25 ++++++++++++++++--- .../Source/GameNetwork/NetworkUtil.cpp | 13 +++++++--- 4 files changed, 36 insertions(+), 7 deletions(-) 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; } /**