From f386e5fb030acf148f7d038c6c2a09808ab7425d Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 13 Jun 2026 20:26:19 +0200 Subject: [PATCH 1/5] Added test code. --- .../GameLogic/System/GameLogicDispatch.cpp | 26 +++++++++++++++++++ .../Source/GameNetwork/NetworkUtil.cpp | 17 +++++++++--- .../GameEngine/Include/Common/MessageStream.h | 2 ++ .../Source/GameLogic/System/GameLogic.cpp | 12 +++++++++ 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 7a202c7f8f7..3aea9252b2a 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -422,6 +422,32 @@ 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 = 100'000; // 10 * 10'000 + + 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 d48280759b6..b9ab82adee4 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -25,6 +25,8 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#include "GameClient/GameText.h" +#include "GameClient/InGameUI.h" #include "GameNetwork/networkutil.h" #ifdef DEBUG_LOGGING @@ -102,10 +104,17 @@ 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() +{ + if (s_commandID == MAXUINT16) + { + const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( + "NETWORK:COMMANDIDOVERFLOW", L"The command ID is about to overflow"); + TheInGameUI->message(mismatchDetailsStr); + } + + return s_commandID++; } /** diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h index fc7945a3156..c3a204c4e3c 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h @@ -614,6 +614,8 @@ 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..bb2228ac053 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3738,6 +3738,18 @@ void GameLogic::update() TheTerrainLogic->UPDATE(); } + if (m_frame >= 100 && getGameMode() != GAME_SHELL && getGameMode() != GAME_NONE) { + static int interval = 1; + static int count = 10; + 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 + ((m_frame - 100) % 10'000) * count); + //msg->appendIntegerArgument(m_frame); + } + } + } + // force CRC calculation, so we can keep a cache of the last N CRCs. We do this right where the recorder // would be getting the CRC anyway, so replays can get the CRCs from the exact instant in time as the original. Bool isMPGameOrReplay = (TheRecorder && TheRecorder->isMultiplayer() && getGameMode() != GAME_SHELL && getGameMode() != GAME_NONE); From 009e621ec0895b0c773626728815d706acfeb231 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 13 Jun 2026 20:26:48 +0200 Subject: [PATCH 2/5] Revert "Added test code." This reverts commit f386e5fb030acf148f7d038c6c2a09808ab7425d. --- .../GameLogic/System/GameLogicDispatch.cpp | 26 ------------------- .../Source/GameNetwork/NetworkUtil.cpp | 17 +++--------- .../GameEngine/Include/Common/MessageStream.h | 2 -- .../Source/GameLogic/System/GameLogic.cpp | 12 --------- 4 files changed, 4 insertions(+), 53 deletions(-) diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 3aea9252b2a..7a202c7f8f7 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -422,32 +422,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 = 100'000; // 10 * 10'000 - - 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 b9ab82adee4..d48280759b6 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -25,8 +25,6 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine -#include "GameClient/GameText.h" -#include "GameClient/InGameUI.h" #include "GameNetwork/networkutil.h" #ifdef DEBUG_LOGGING @@ -104,17 +102,10 @@ UnsignedInt ResolveIP(AsciiString host) /** * Returns the next network command ID. */ -static UnsignedShort s_commandID = 0; -UnsignedShort GenerateNextCommandID() -{ - if (s_commandID == MAXUINT16) - { - const UnicodeString mismatchDetailsStr = TheGameText->FETCH_OR_SUBSTITUTE( - "NETWORK:COMMANDIDOVERFLOW", L"The command ID is about to overflow"); - TheInGameUI->message(mismatchDetailsStr); - } - - return s_commandID++; +UnsignedShort GenerateNextCommandID() { + static UnsignedShort commandID = 64000; + ++commandID; + return commandID; } /** diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h index c3a204c4e3c..fc7945a3156 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MessageStream.h @@ -614,8 +614,6 @@ 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 bb2228ac053..622aeb5da10 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp @@ -3738,18 +3738,6 @@ void GameLogic::update() TheTerrainLogic->UPDATE(); } - if (m_frame >= 100 && getGameMode() != GAME_SHELL && getGameMode() != GAME_NONE) { - static int interval = 1; - static int count = 10; - 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 + ((m_frame - 100) % 10'000) * count); - //msg->appendIntegerArgument(m_frame); - } - } - } - // force CRC calculation, so we can keep a cache of the last N CRCs. We do this right where the recorder // would be getting the CRC anyway, so replays can get the CRCs from the exact instant in time as the original. Bool isMPGameOrReplay = (TheRecorder && TheRecorder->isMultiplayer() && getGameMode() != GAME_SHELL && getGameMode() != GAME_NONE); From a6705c5c4f544d55c106d4a364d4ba7ba8a488e1 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 13 Jun 2026 20:19:08 +0200 Subject: [PATCH 3/5] Added new command id sorting code. --- .../Source/GameNetwork/NetCommandList.cpp | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp index 579b8578c72..ed890033cc4 100644 --- a/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetCommandList.cpp @@ -122,6 +122,18 @@ void NetCommandList::reset() { m_lastMessageInserted = nullptr; } +static bool isCommandIdNewer(UnsignedShort newVal, UnsignedShort oldVal) +{ +#if RETAIL_COMPATIBLE_NETWORKING + return newVal > oldVal; +#else + // TheSuperHackers @info Caball009 14/06/2026 Ensure messages are sorted + // chronologically by including a command id overflow check. + const UnsignedShort diff = newVal - oldVal; + return diff != 0 && diff < 0x8000; +#endif +} + /** * 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. @@ -157,10 +169,10 @@ NetCommandRef * NetCommandList::addMessage(NetCommandRef *&msg) { NetCommandRef *theNext = m_lastMessageInserted->getNext(); if ((m_lastMessageInserted->getCommand()->getNetCommandType() == msg->getCommand()->getNetCommandType()) && (m_lastMessageInserted->getCommand()->getPlayerID() == msg->getCommand()->getPlayerID()) && - (m_lastMessageInserted->getCommand()->getID() < msg->getCommand()->getID()) && + isCommandIdNewer(msg->getCommand()->getID(), m_lastMessageInserted->getCommand()->getID()) && ((theNext == nullptr) || ((theNext->getCommand()->getNetCommandType() > msg->getCommand()->getNetCommandType()) || (theNext->getCommand()->getPlayerID() > msg->getCommand()->getPlayerID()) || - (theNext->getCommand()->getID() > msg->getCommand()->getID())))) { + isCommandIdNewer(theNext->getCommand()->getID(), msg->getCommand()->getID())))) { // Make sure this command isn't already in the list. if (isEqualCommandMsg(m_lastMessageInserted->getCommand(), msg->getCommand())) { @@ -281,7 +293,10 @@ NetCommandRef * NetCommandList::addMessage(NetCommandRef *&msg) { // Find the position within the player's section based on the command ID. // If the command type doesn't require a command ID, sort by whatever it should be sorted by. - while ((tempmsg != nullptr) && (msg->getCommand()->getNetCommandType() == tempmsg->getCommand()->getNetCommandType()) && (msg->getCommand()->getPlayerID() == tempmsg->getCommand()->getPlayerID()) && (msg->getCommand()->getSortNumber() > tempmsg->getCommand()->getSortNumber())) { + while (tempmsg != nullptr + && msg->getCommand()->getNetCommandType() == tempmsg->getCommand()->getNetCommandType() + && msg->getCommand()->getPlayerID() == tempmsg->getCommand()->getPlayerID() + && isCommandIdNewer(msg->getCommand()->getSortNumber(), tempmsg->getCommand()->getSortNumber())) { tempmsg = tempmsg->getNext(); } From d4b3a300a80284e9587ee2be6e9055015bb275f6 Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 13 Jun 2026 20:51:32 +0200 Subject: [PATCH 4/5] Put some 'NET_CRC_INTERVAL' code behind macro. --- Core/GameEngine/Source/GameNetwork/Network.cpp | 2 +- Generals/Code/GameEngine/Source/Common/CommandLine.cpp | 2 +- GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/Network.cpp b/Core/GameEngine/Source/GameNetwork/Network.cpp index b5501e32e26..39670637d32 100644 --- a/Core/GameEngine/Source/GameNetwork/Network.cpp +++ b/Core/GameEngine/Source/GameNetwork/Network.cpp @@ -53,7 +53,7 @@ #include "GameClient/MessageBox.h" -#if defined(DEBUG_CRC) +#if defined(DEBUG_CRC) && !RETAIL_COMPATIBLE_NETWORKING Int NET_CRC_INTERVAL = 1; #else Int NET_CRC_INTERVAL = 100; diff --git a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp index 543576d78fc..8289418703a 100644 --- a/Generals/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/Generals/Code/GameEngine/Source/Common/CommandLine.cpp @@ -314,7 +314,7 @@ Int parseLogObjectCRCs(char *args[], int argc) //============================================================================= Int parseNetCRCInterval(char *args[], int argc) { -#ifdef DEBUG_CRC +#if defined(DEBUG_CRC) && !RETAIL_COMPATIBLE_NETWORKING if (argc > 1) { NET_CRC_INTERVAL = atoi(args[1]); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index 543576d78fc..8289418703a 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -314,7 +314,7 @@ Int parseLogObjectCRCs(char *args[], int argc) //============================================================================= Int parseNetCRCInterval(char *args[], int argc) { -#ifdef DEBUG_CRC +#if defined(DEBUG_CRC) && !RETAIL_COMPATIBLE_NETWORKING if (argc > 1) { NET_CRC_INTERVAL = atoi(args[1]); From 60e1229ebcc16006212e39936025bf5ceadbbede Mon Sep 17 00:00:00 2001 From: Caball009 <82909616+Caball009@users.noreply.github.com> Date: Sat, 13 Jun 2026 22:18:47 +0200 Subject: [PATCH 5/5] Changed network command id starting value. --- Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp index d48280759b6..b709e670d7e 100644 --- a/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp +++ b/Core/GameEngine/Source/GameNetwork/NetworkUtil.cpp @@ -102,10 +102,10 @@ 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++; } /**