From 3d56286b4545c4f57c733383c456027057071a5b Mon Sep 17 00:00:00 2001 From: slipher Date: Thu, 9 Apr 2026 13:48:14 -0300 Subject: [PATCH 1/2] Drop on impossible server command sequence nums Have the client drop the connection in these cases: - A gamestate that decreases the sequence number. This should be impossible because the netchan guarantees that packets can't be reordered, although they may be dropped. - A server command which a sequence number higher than the next one. This should be impossible because the server re-sends all unacknowledged commands together each time. If the number jumped by more than one the client would end up re-executing old garbage from the buffer. --- src/engine/client/cl_cgame.cpp | 3 ++- src/engine/client/cl_parse.cpp | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/engine/client/cl_cgame.cpp b/src/engine/client/cl_cgame.cpp index d716c90b0b..d4e3b65c1b 100644 --- a/src/engine/client/cl_cgame.cpp +++ b/src/engine/client/cl_cgame.cpp @@ -137,7 +137,8 @@ void CL_ConfigstringModified( Cmd::Args& csCmd ) /* =================== CL_HandleServerCommand -CL_GetServerCommand + +Returns true if the command should be passed to the cgame =================== */ bool CL_HandleServerCommand(Str::StringRef text, std::string& newText) { diff --git a/src/engine/client/cl_parse.cpp b/src/engine/client/cl_parse.cpp index 2169c114fd..45e8bfad66 100644 --- a/src/engine/client/cl_parse.cpp +++ b/src/engine/client/cl_parse.cpp @@ -408,7 +408,14 @@ void CL_ParseGamestate( msg_t *msg ) CL_ClearState(); // a gamestate always marks a server command sequence - clc.serverCommandSequence = MSG_ReadLong( msg ); + int commandNum = MSG_ReadLong( msg ); + + if ( commandNum < clc.serverCommandSequence ) + { + Sys::Drop( "Gamestate moved serverCommandSequence backward" ); + } + + clc.serverCommandSequence = commandNum; // trash any commands from previous game clc.lastExecutedServerCommand = clc.serverCommandSequence; @@ -499,6 +506,13 @@ void CL_ParseCommandString( msg_t *msg ) return; } + if ( clc.serverCommandSequence + 1 != seq ) + { + Sys::Drop( "Out-of-sequence server command: expected %d, got %d", + clc.serverCommandSequence + 1, seq ); + return; + } + clc.serverCommandSequence = seq; index = seq & ( MAX_RELIABLE_COMMANDS - 1 ); From e6dde63910aec447c4226ebdbb1ed65bf10beb0f Mon Sep 17 00:00:00 2001 From: slipher Date: Thu, 9 Apr 2026 18:34:29 -0300 Subject: [PATCH 2/2] CL_FillServerCommand: remove demo special case A special case to allow demos to request bad command ranges is not needed. The gamestate generated when starting the demo sets the serverCommandSequence number appropriately, so the demo replay will be able to request the correct range. --- src/engine/client/cl_cgame.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/engine/client/cl_cgame.cpp b/src/engine/client/cl_cgame.cpp index d4e3b65c1b..51875f0ba4 100644 --- a/src/engine/client/cl_cgame.cpp +++ b/src/engine/client/cl_cgame.cpp @@ -256,13 +256,6 @@ void CL_FillServerCommands(std::vector& commands, int start, int en // if we have irretrievably lost a reliable command, drop the connection if ( start <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { - // when a demo record was started after the client got a whole bunch of - // reliable commands then the client never got those first reliable commands - if ( clc.demoplaying ) - { - return; - } - Sys::Drop( "CL_FillServerCommand: a reliable command was cycled out" ); }