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.
This commit is contained in:
Kawe Mazidjatari 2024-09-28 12:43:11 +02:00
parent 432cb923cc
commit d312597c91
4 changed files with 83 additions and 16 deletions

View File

@ -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);

View File

@ -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;
};

View File

@ -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",

View File

@ -19,6 +19,7 @@
#include <engine/host_state.h>
#include <networksystem/hostmanager.h>
#include "player.h"
#include <common/callback.h>
/*
=====================
@ -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);
}
}