From d312597c91ddbc889e5817115bd18f2bb64a03bb Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Sat, 28 Sep 2024 12:43:11 +0200 Subject: [PATCH] Engine: rework class var command and netmsg design New command '_setClassVarClient' only sets the class var on the client, similar to how '_setClassVarServer' only sets it on the server. Command 'set' still has its old behavior, setting it on the server and client. The netmsg SVC_SetClassVar now only runs the _setClassVarClient handler, and if the netmsg was successfully sent, the code runs the _setClassVarServer handler directly instead of relying on the client to NET_StringMsg it in. This is a lot more reliable, secure and performant than the previous approach and drops the requirement of setting sv_cheats to function. --- src/common/callback.cpp | 29 ++++++++++++++++- src/common/callback.h | 17 ++++++++-- src/common/global.cpp | 3 ++ src/game/server/vscript_server.cpp | 50 +++++++++++++++++++++++------- 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/src/common/callback.cpp b/src/common/callback.cpp index 3a27f899..58b6c99e 100644 --- a/src/common/callback.cpp +++ b/src/common/callback.cpp @@ -23,6 +23,7 @@ #endif // !DEDICATED #include "engine/client/client.h" #include "engine/net.h" +#include "engine/cmd.h" #include "engine/host_cmd.h" #include "engine/host_state.h" #include "engine/enginetrace.h" @@ -256,6 +257,33 @@ void LanguageChanged_f(IConVar* pConVar, const char* pOldString, float flOldValu } #ifndef DEDICATED +void setClassVarClient_f(const CCommand& args) +{ + v__setClassVarClient_f(args); +} + +static ConCommand _setClassVarClient("_setClassVarClient", setClassVarClient_f, "Set a class var on the client", FCVAR_DEVELOPMENTONLY|FCVAR_CLIENTDLL|FCVAR_CHEAT|FCVAR_SERVER_CAN_EXECUTE); + +/* +===================== +Set_f + + Set a class var on the server & client. +===================== +*/ +void Set_f(const CCommand& args) +{ + v__setClassVarClient_f(args); + + const char* key = args.Arg(1); + const char* val = args.Arg(2); + + char buf[256]; + V_snprintf(buf, sizeof(buf), "_setClassVarServer %s \"%s\"\n", key, val); + + Cbuf_AddText(ECommandTarget_t::CBUF_FIRST_PLAYER, buf, cmd_source_t::kCommandSrcInvalid); +} + /* ===================== Mat_CrossHair_f @@ -583,7 +611,6 @@ void UIScript_Reset_f() } #endif // !DEDICATED - void VCallback::Detour(const bool bAttach) const { DetourSetup(&v__Cmd_Exec_f, &Cmd_Exec_f, bAttach); diff --git a/src/common/callback.h b/src/common/callback.h index 89dfe958..2426dbc7 100644 --- a/src/common/callback.h +++ b/src/common/callback.h @@ -4,12 +4,16 @@ inline bool(*v_SetupGamemode)(const char* pszPlayList); /* ==== CONCOMMANDCALLBACK ============================================================================================================================================== */ inline void(*v__Cmd_Exec_f)(const CCommand& args); -inline bool(*v__setClassVarServer_f)(const CCommand& args); -inline bool(*v__setClassVarClient_f)(const CCommand& args); +inline void(*v__setClassVarServer_f)(const CCommand& args); +inline void(*v__setClassVarClient_f)(const CCommand& args); #ifndef DEDICATED inline void(*v__UIScript_Reset_f)(); #endif // !DEDICATED +#ifndef CLIENT_DLL +inline int* g_nCommandClientIndex = nullptr; +#endif // !CLIENT_DLL + /////////////////////////////////////////////////////////////////////////////// void MP_GameMode_Changed_f(IConVar* pConVar, const char* pOldString, float flOldValue, ChangeUserData_t pUserData); #ifndef CLIENT_DLL @@ -25,6 +29,7 @@ void GFX_NVN_Changed_f(IConVar* pConVar, const char* pOldString, float flOldValu #endif // !DEDICATED void LanguageChanged_f(IConVar* pConVar, const char* pOldString, float flOldValue, ChangeUserData_t pUserData); #ifndef DEDICATED +void Set_f(const CCommand& args); void Mat_CrossHair_f(const CCommand& args); void Line_f(const CCommand& args); void Sphere_f(const CCommand& args); @@ -36,6 +41,7 @@ void CVHelp_f(const CCommand& args); void CVList_f(const CCommand& args); void CVDiff_f(const CCommand& args); void CVFlag_f(const CCommand& args); + /////////////////////////////////////////////////////////////////////////////// class VCallback : public IDetour { @@ -59,7 +65,12 @@ class VCallback : public IDetour g_GameDll.FindPatternSIMD("40 55 41 54 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 45 33 E4 48 8D 0D").GetPtr(v__UIScript_Reset_f); #endif // !DEDICATED } - virtual void GetVar(void) const { } + virtual void GetVar(void) const + { +#ifndef CLIENT_DLL + CMemory(v__setClassVarServer_f).FindPatternSelf("8B 05").ResolveRelativeAddressSelf(2, 6).GetPtr(g_nCommandClientIndex); +#endif // !CLIENT_DLL + } virtual void GetCon(void) const { } virtual void Detour(const bool bAttach) const; }; diff --git a/src/common/global.cpp b/src/common/global.cpp index 13dae4a4..faed4cfc 100644 --- a/src/common/global.cpp +++ b/src/common/global.cpp @@ -385,6 +385,7 @@ void ConCommand_InitShipped(void) //------------------------------------------------------------------------- // CLIENT DLL | ConCommand* give = g_pCVar->FindCommand("give"); + ConCommand* set = g_pCVar->FindCommand("set"); #endif // !DEDICATED help->m_fnCommandCallback = CVHelp_f; @@ -403,6 +404,7 @@ void ConCommand_InitShipped(void) #ifndef DEDICATED mat_crosshair->m_fnCommandCallback = Mat_CrossHair_f; give->m_fnCompletionCallback = Game_Give_f_CompletionFunc; + set->m_fnCommandCallback = Set_f; #endif // !DEDICATED /// ------------------------------------------------------ [ FLAG REMOVAL ] @@ -474,6 +476,7 @@ void ConCommand_PurgeShipped(void) "getpos_bind", "connect", "silent_connect", + "set", "ping", "gameui_activate", "gameui_hide", diff --git a/src/game/server/vscript_server.cpp b/src/game/server/vscript_server.cpp index a69176b7..986edde8 100644 --- a/src/game/server/vscript_server.cpp +++ b/src/game/server/vscript_server.cpp @@ -19,6 +19,7 @@ #include #include #include "player.h" +#include /* ===================== @@ -279,16 +280,8 @@ namespace VScriptCode //----------------------------------------------------------------------------- // Purpose: sets a class var on the server and each client - // TODO: currently, this relies on the client running _secClassVarServer - // for its player class vars to be adjusted on the server as well. Not really - // exploitable on the client as omitting this will prevent the server from - // setting it in their player instance effectively screwing up the prediction, - // but it might be nice to run it from the server and propagate all changes to - // each client individually from an architectural point of view. - // Furthermore (only for SetClassVarSynced, not the individual one from - // PlayerStruct) it might also be good to research potential ways to track - // class var changes and sync them back to clients connecting after this has - // been called. + // TODO: it might also be good to research potential ways to track class var + // changes and sync them back to clients connecting after this has been called. //----------------------------------------------------------------------------- SQRESULT SetClassVarSynced(HSQUIRRELVM v) { @@ -310,8 +303,17 @@ namespace VScriptCode SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR); } + const char* pArgs[3] = { + "_setClassVarServer", + key, + val + }; + SVC_SetClassVar msg(key, val); + const CCommand cmd((int)V_ARRAYSIZE(pArgs), pArgs, cmd_source_t::kCommandSrcCode); + bool failure = false; + const int oldIdx = *g_nCommandClientIndex; for (int i = 0; i < gpGlobals->maxClients; i++) { @@ -321,10 +323,17 @@ namespace VScriptCode if (client->GetSignonState() != SIGNONSTATE::SIGNONSTATE_FULL) continue; - if (!client->SendNetMsgEx(&msg, false, true, false)) + if (client->SendNetMsgEx(&msg, false, true, false)) + { + *g_nCommandClientIndex = client->GetUserID(); + v__setClassVarServer_f(cmd); + } + else // Not all clients have their class var set. failure = true; } + *g_nCommandClientIndex = oldIdx; + sq_pushbool(v, !failure); SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } @@ -364,8 +373,25 @@ namespace VScriptCode SVC_SetClassVar msg(key, val); const bool success = client->SendNetMsgEx(&msg, false, true, false); - sq_pushbool(v, success); + if (success) + { + const char* pArgs[3] = { + "_setClassVarServer", + key, + val + }; + + const CCommand cmd((int)V_ARRAYSIZE(pArgs), pArgs, cmd_source_t::kCommandSrcCode); + const int oldIdx = *g_nCommandClientIndex; + + *g_nCommandClientIndex = client->GetUserID(); + v__setClassVarServer_f(cmd); + + *g_nCommandClientIndex = oldIdx; + } + + sq_pushbool(v, success); SCRIPT_CHECK_AND_RETURN(v, SQ_OK); } }