Fix >190FPS input system/simulation problems

Function 'CL_Move' has been fully rebuild in the SDK. Originally, the game checked if the delta time exceeded an amount defined by an immediate value, and dropped usercmd's if that was the case. This logic has been replaced with a more dynamic solution, and the console variable regulating this ('fps_input_max') is set to 200.0 by default (the same as the fix applied in the Season 9.1 Genesis update). This function also has been slightly optimized by removing duplicate operations that were performed in the original function. A second fix has been applied to 'CInput::JoyStickApplyMovement' that was also found changed in the Season 9.1 Genesis executable. In that function, an extraneous clamp was performed on the frame time causing viewstick problems when usercmd's get dropped in CL_Move.
This commit is contained in:
Kawe Mazidjatari 2023-06-03 21:20:23 +02:00
parent b567d8d74f
commit 1afa75fec3
10 changed files with 208 additions and 12 deletions

View File

@ -21,6 +21,7 @@ ConVar* debug_draw_box_depth_test = nullptr;
ConVar* developer = nullptr;
ConVar* fps_max = nullptr;
ConVar* fps_input_max = nullptr;
ConVar* staticProp_defaultBuildFrustum = nullptr;
ConVar* staticProp_no_fade_scalar = nullptr;
@ -34,7 +35,10 @@ ConVar* hostname = nullptr;
ConVar* hostdesc = nullptr;
ConVar* hostip = nullptr;
ConVar* hostport = nullptr;
ConVar* host_hasIrreversibleShutdown = nullptr;
ConVar* host_timescale = nullptr;
ConVar* mp_gamemode = nullptr;
ConVar* rcon_address = nullptr;
@ -128,6 +132,9 @@ ConVar* bhit_abs_origin = nullptr;
ConVar* cl_rcon_request_sendlogs = nullptr;
ConVar* cl_quota_stringCmdsPerSecond = nullptr;
ConVar* cl_cmdrate = nullptr;
ConVar* cl_move_use_dt = nullptr;
ConVar* cl_notify_invert_x = nullptr;
ConVar* cl_notify_invert_y = nullptr;
ConVar* cl_notify_offset_x = nullptr;
@ -372,6 +379,8 @@ void ConVar_StaticInit(void)
serverbrowser_hideEmptyServers = ConVar::StaticCreate("serverbrowser_hideEmptyServers", "0", FCVAR_RELEASE, "Hide empty servers in the server browser", false, 0.f, false, 0.f, nullptr, nullptr);
serverbrowser_mapFilter = ConVar::StaticCreate("serverbrowser_mapFilter", "0", FCVAR_RELEASE, "Filter servers by map in the server browser", false, 0.f, false, 0.f, nullptr, nullptr);
serverbrowser_gamemodeFilter = ConVar::StaticCreate("serverbrowser_gamemodeFilter", "0", FCVAR_RELEASE, "Filter servers by gamemode in the server browser", false, 0.f, false, 0.f, nullptr, nullptr);
fps_input_max = ConVar::StaticCreate("fps_input_max", "200.0", FCVAR_RELEASE, "Max movement updates ran per second.", false, 0.f, false, 0.f, nullptr, nullptr);
#endif // !DEDICATED
//-------------------------------------------------------------------------
// FILESYSTEM |
@ -431,6 +440,8 @@ void ConVar_InitShipped(void)
fps_max = g_pCVar->FindVar("fps_max");
fs_showAllReads = g_pCVar->FindVar("fs_showAllReads");
#ifndef DEDICATED
cl_cmdrate = g_pCVar->FindVar("cl_cmdrate");
cl_move_use_dt = g_pCVar->FindVar("cl_move_use_dt");
cl_threaded_bone_setup = g_pCVar->FindVar("cl_threaded_bone_setup");
#endif // !DEDICATED
single_frame_shutdown_for_reload = g_pCVar->FindVar("single_frame_shutdown_for_reload");
@ -461,6 +472,7 @@ void ConVar_InitShipped(void)
hostip = g_pCVar->FindVar("hostip");
hostport = g_pCVar->FindVar("hostport");
host_hasIrreversibleShutdown = g_pCVar->FindVar("host_hasIrreversibleShutdown");
host_timescale = g_pCVar->FindVar("host_timescale");
net_usesocketsforloopback = g_pCVar->FindVar("net_usesocketsforloopback");
#ifndef CLIENT_DLL
sv_stats = g_pCVar->FindVar("sv_stats");

View File

@ -12,6 +12,7 @@ extern ConVar* debug_draw_box_depth_test;
extern ConVar* developer;
extern ConVar* fps_max;
extern ConVar* fps_input_max;
extern ConVar* staticProp_defaultBuildFrustum;
extern ConVar* staticProp_no_fade_scalar;
@ -25,7 +26,9 @@ extern ConVar* hostname;
extern ConVar* hostdesc;
extern ConVar* hostip;
extern ConVar* hostport;
extern ConVar* host_hasIrreversibleShutdown;
extern ConVar* host_timescale;
extern ConVar* mp_gamemode;
@ -119,6 +122,9 @@ extern ConVar* bhit_abs_origin;
extern ConVar* cl_rcon_request_sendlogs;
extern ConVar* cl_quota_stringCmdsPerSecond;
extern ConVar* cl_cmdrate;
extern ConVar* cl_move_use_dt;
extern ConVar* cl_notify_invert_x;
extern ConVar* cl_notify_invert_y;
extern ConVar* cl_notify_offset_x;

View File

@ -117,6 +117,7 @@ add_sources( SOURCE_GROUP "Client"
"client/cdll_engine_int.h"
"client/cl_ents_parse.cpp"
"client/cl_ents_parse.h"
"client/cl_main.cpp"
"client/cl_main.h"
"client/cl_splitscreen.cpp"
"client/cl_splitscreen.h"

View File

@ -0,0 +1,137 @@
//=============================================================================//
//
// Purpose:
//
//=============================================================================//
#include "engine/host.h"
#include "clientstate.h"
#include "cl_splitscreen.h"
#include "cl_main.h"
#include "engine/net.h"
#include "cdll_engine_int.h"
static float s_lastMovementCall = 0.0;
static float s_LastFrameTime = 0.0;
//-----------------------------------------------------------------------------
// Purpose: run client's movement frame
//-----------------------------------------------------------------------------
void H_CL_Move()
{
CClientState* cl = GetBaseLocalClient();
if (!cl->IsConnected())
return;
if (!v_Host_ShouldRun())
return;
int commandTick = -1;
if (cl->m_CurrFrameSnapshot)
commandTick = cl->m_CurrFrameSnapshot->m_TickUpdate.m_nCommandTick;
bool sendPacket = true;
float netTime = float(*g_pNetTime);
CNetChan* chan = cl->m_NetChannel;
if (cl->m_flNextCmdTime <= (0.5 / cl_cmdrate->GetFloat()) + netTime)
sendPacket = chan->CanPacket();
else if (g_pClientState->m_nOutgoingCommandNr - (commandTick+1) < 15 || host_timescale->GetFloat() == 1.0)
sendPacket = false;
if (cl->IsActive())
{
float timeNow = float(Plat_FloatTime());
int outCommandNr = g_pClientState->m_nOutgoingCommandNr;
bool isPaused = g_pClientState->IsPaused();
int nextCommandNr = isPaused ? outCommandNr : outCommandNr+1;
FOR_EACH_SPLITSCREEN_PLAYER(i)
{
if (g_pSplitScreenMgr->IsDisconnecting(i))
continue;
float frameTime = 0.0f;
if (cl_move_use_dt->GetBool())
{
float timeScale;
float deltaTime;
if (isPaused)
{
timeScale = 1.0f;
frameTime = timeNow - s_lastMovementCall;
deltaTime = frameTime;
}
else
{
timeScale = host_timescale->GetFloat();
frameTime = cl->m_flFrameTime + s_LastFrameTime;
deltaTime = frameTime / timeScale;
}
if (deltaTime > 0.1f)
frameTime = timeScale * 0.1f;
// This check originally was 'time < 0.0049999999', but
// that caused problems when the framerate was above 190.
if ((1.0 / fps_input_max->GetFloat()) > deltaTime)
{
s_LastFrameTime = frameTime;
return;
}
s_LastFrameTime = 0.0;
}
//else if (isPaused)
//{
// // This hlClient virtual call just returns false.
//}
// Create and store usercmd structure.
g_pHLClient->CreateMove(nextCommandNr, frameTime, !isPaused);
g_pClientState->m_nOutgoingCommandNr = nextCommandNr;
}
CL_RunPrediction();
if (sendPacket)
{
CL_SendMove();
CLC_ClientTick tickMsg(cl->m_nDeltaTick, cl->m_nStringTableAckTick);
chan->SendNetMsg(tickMsg, false, false);
chan->SendDatagram(nullptr);
// Use full update rate when active.
float delta = netTime - float(g_pClientState->m_flNextCmdTime);
float commandInterval = (1.0f / cl_cmdrate->GetFloat()) - 0.001f;
float maxDelta = 0.0f;
if (delta >= 0.0f)
maxDelta = fminf(commandInterval, delta);
g_pClientState->m_flNextCmdTime = double(commandInterval + netTime - maxDelta);
}
else // Choke the packet...
chan->SetChoked();
s_lastMovementCall = timeNow;
}
}
void VCL_Main::Attach() const
{
DetourAttach(&CL_Move, &H_CL_Move);
}
void VCL_Main::Detach() const
{
DetourDetach(&CL_Move, &H_CL_Move);
}

View File

@ -1,38 +1,56 @@
#pragma once
//-------------------------------------------------------------------------
// RUNTIME: CL_CLEARSTATE
//-------------------------------------------------------------------------
inline CMemory p_CL_ClearState;
inline auto CL_ClearState = p_CL_ClearState.RCast<int(*)(void)>();
inline CMemory p_CL_Move;
inline auto CL_Move = p_CL_Move.RCast<void(*)(void)>();
inline CMemory p_CL_SendMove;
inline auto CL_SendMove = p_CL_SendMove.RCast<void(*)(void)>();
inline CMemory p_CL_EndMovie;
inline auto CL_EndMovie = p_CL_EndMovie.RCast<int(*)(void)>();
inline CMemory p_CL_ClearState;
inline auto CL_ClearState = p_CL_ClearState.RCast<int(*)(void)>();
inline CMemory p_CL_RunPrediction;
inline auto CL_RunPrediction = p_CL_RunPrediction.RCast<void(*)(void)>();
///////////////////////////////////////////////////////////////////////////////
class VCL_Main : public IDetour
{
virtual void GetAdr(void) const
{
LogFunAdr("CL_Move", p_CL_Move.GetPtr());
LogFunAdr("CL_SendMove", p_CL_SendMove.GetPtr());
LogFunAdr("CL_EndMovie", p_CL_EndMovie.GetPtr());
LogFunAdr("CL_ClearState", p_CL_ClearState.GetPtr());
LogFunAdr("CL_RunPrediction", p_CL_RunPrediction.GetPtr());
}
virtual void GetFun(void) const
{
#if defined (GAMEDLL_S0) || defined (GAMEDLL_S1)
p_CL_ClearState = g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 1D ?? ?? ?? ??");
p_CL_Move = g_GameDll.FindPatternSIMD("40 53 48 81 EC ?? ?? ?? ?? 83 3D ?? ?? ?? ?? ?? 0F B6 DA");
p_CL_SendMove = g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 55 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ??");
p_CL_EndMovie = g_GameDll.FindPatternSIMD("48 8B C4 48 83 EC 68 80 3D ?? ?? ?? ?? ??");
p_CL_ClearState = g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 1D ?? ?? ?? ??");
p_CL_RunPrediction = g_GameDll.FindPatternSIMD("4C 8B DC 48 83 EC 58 83 3D ?? ?? ?? ?? ??");
#elif defined (GAMEDLL_S2) || defined (GAMEDLL_S3)
p_CL_ClearState = g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? 48 8B 01");
p_CL_Move = g_GameDll.FindPatternSIMD("48 81 EC ?? ?? ?? ?? 83 3D ?? ?? ?? ?? ?? 44 0F 29 5C 24 ??");
p_CL_SendMove = g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ??");
p_CL_EndMovie = g_GameDll.FindPatternSIMD("48 83 EC 28 80 3D ?? ?? ?? ?? ?? 74 7B");
p_CL_ClearState = g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? 48 8B 01");
p_CL_RunPrediction = g_GameDll.FindPatternSIMD("48 83 EC 48 83 3D ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 3D ?? ?? ?? ?? ??");
#endif
CL_ClearState = p_CL_ClearState.RCast<int(*)(void)>(); /*48 89 5C 24 ?? 48 89 74 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 0D ?? ?? ?? ?? 48 8B 01*/
CL_EndMovie = p_CL_EndMovie.RCast<int(*)(void)>(); /*48 83 EC 28 80 3D ?? ?? ?? ?? ?? 74 7B*/
CL_Move = p_CL_Move.RCast<void(*)(void)>();
CL_SendMove = p_CL_SendMove.RCast<void(*)(void)>();
CL_EndMovie = p_CL_EndMovie.RCast<int(*)(void)>();
CL_ClearState = p_CL_ClearState.RCast<int(*)(void)>();
CL_RunPrediction = p_CL_RunPrediction.RCast<void(*)(void)>();
}
virtual void GetVar(void) const { }
virtual void GetCon(void) const { }
virtual void Attach(void) const { }
virtual void Detach(void) const { }
virtual void Attach(void) const;
virtual void Detach(void) const;
};
///////////////////////////////////////////////////////////////////////////////

View File

@ -119,6 +119,11 @@ private:
#define IS_VALID_SPLIT_SCREEN_SLOT( i ) ( g_pSplitScreenMgr->IsValidSplitScreenSlot( i ) )
#endif
inline CClientState* GetBaseLocalClient()
{
return g_pClientState;
}
class VSplitScreen : public IDetour
{
virtual void GetAdr(void) const

View File

@ -126,7 +126,7 @@ public:
int dword18CD0;
int field_18CD4;
float m_flFrameTime;
int outgoing_command;
int m_nOutgoingCommandNr;
int current_movement_sequence_number;
char gap18CE4[4];
__int64 qword18CE8;

View File

@ -327,6 +327,15 @@ bool CNetChan::SendNetMsg(INetMessage& msg, bool bForceReliable, bool bVoice)
return true;
}
//-----------------------------------------------------------------------------
// Purpose: increments choked packet count
//-----------------------------------------------------------------------------
void CNetChan::SetChoked(void)
{
m_nOutSequenceNr++; // Sends to be done since move command use sequence number.
m_nChokedPackets++;
}
//-----------------------------------------------------------------------------
// Purpose: sets the remote frame times
// Input : flFrameTime -

View File

@ -133,6 +133,7 @@ public:
static void _Shutdown(CNetChan* pChan, const char* szReason, uint8_t bBadRep, bool bRemoveNow);
static bool _ProcessMessages(CNetChan* pChan, bf_read* pMsg);
void SetChoked();
void SetRemoteFramerate(float flFrameTime, float flFrameTimeStdDeviation);
void SetRemoteCPUStatistics(uint8_t nStats);
//-----------------------------------------------------------------------------

View File

@ -51,6 +51,13 @@
0x1477876: 'FIELD_INTERVALFIELD_MODELINDEX\x00\x00' --> 'FIELD_INTERVAL\x00FIELD_MODELINDEX\x00';
0x1318C00: 0x0000000000 --> 0x7792474101; // Add new entry in 'g_FieldTypes', this entry points to the 'FIELD_MODELINDEX' string we separated from 'FIELD_INTERVAL'.
// In 'CInput::JoyStickApplyMovement' an extraneous 'fmin' clamp is performed on the frame time. BinDiff revealed that this was no longer performed on
// the 'Season 9.1 Genesis' executable. Further testing revealed that patching out just this clamp fixes the controller view stick problems when usercmd's
// get dropped in CL_Move.
0x6FD0E1: "movaps xmm0, xmm6" // Move frame time directly into the register that originally contained the clamped frame time.
0x6FD0EE: "nop (x4)" // Nop 'minss xmm0, xmm6' (extraneous clamp).
0x6FD114: "nop (x3)" // Nop 'movaps xmm6, xmm0' (extraneous move operation).
/////////////////////////////
/////////////////////////////
//// Exploitable defects ////