From 238e14581892d4c9e3d49fc4ccd0a8a03121105b Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Thu, 4 Aug 2022 11:06:56 +0200 Subject: [PATCH] Implement ConVar change callbacks CUtlVector containing callbacks (you can have several callbacks for each ConVar). also, in 'ConVar::InternalSetValue' Valve and Respawn run V_atod (atof) on function param 'pszValue', but they also check if input param 'pszValue' is a nullptr (which in this case, 'pszNewValue' gets set to an empty string), but yet they deref 'pszValue' (nullptr or not) in 'SetColorFromString' and V_atod, is this correct behavior? For now I swapped these out with 'pszNewValue' for the very rare event the string happens to be nullptr. --- r5dev/engine/server/sv_rcon.cpp | 2 +- r5dev/tier0/platform.h | 9 +++ r5dev/tier1/IConVar.cpp | 121 +++++++++++++++++++------------- r5dev/tier1/IConVar.h | 28 +++----- r5dev/tier1/cvar.cpp | 57 ++++++++------- r5dev/tier1/cvar.h | 6 +- r5dev/vstdlib/callback.cpp | 13 ++-- r5dev/vstdlib/callback.h | 2 +- 8 files changed, 138 insertions(+), 100 deletions(-) diff --git a/r5dev/engine/server/sv_rcon.cpp b/r5dev/engine/server/sv_rcon.cpp index 26d3ddfb..f77c6077 100644 --- a/r5dev/engine/server/sv_rcon.cpp +++ b/r5dev/engine/server/sv_rcon.cpp @@ -486,7 +486,7 @@ void CRConServer::Execute(const cl_rcon::request& cl_request, bool bConVar) cons if (bConVar) { ConVar* pConVar = g_pCVar->FindVar(cl_request.requestbuf().c_str()); - if (pConVar) // Set value without running the callback. + if (pConVar) // Only run if this is a ConVar. { pConVar->SetValue(cl_request.requestval().c_str()); } diff --git a/r5dev/tier0/platform.h b/r5dev/tier0/platform.h index d0f32118..eb26ab1f 100644 --- a/r5dev/tier0/platform.h +++ b/r5dev/tier0/platform.h @@ -410,6 +410,15 @@ inline int64 CastPtrToInt64(const void* p) #endif +#define stackalloc_aligned( _size, _align ) (void*)( ( ((uintp)alloca( ALIGN_VALUE( ( _size ) + (_align ), ( _align ) ) )) + ( _align ) ) & ~_align ) + +// We should probably always just align to 16 bytes, stackalloc just causes too many problems without this behavior. Source2 does it already. +// #define stackalloc( _size ) stackalloc_aligned( _size, 16 ) + +#define stackfree( _p ) 0 +// two-argument ( type, #elements) stackalloc +#define StackAlloc( typ, nelements ) ( ( typ * ) stackalloc_aligned( ( nelements ) * sizeof(typ), 16 ) ) + #define NO_MALLOC_OVERRIDE //----------------------------------------------------------------------------- diff --git a/r5dev/tier1/IConVar.cpp b/r5dev/tier1/IConVar.cpp index c0a8271d..76dfd605 100644 --- a/r5dev/tier1/IConVar.cpp +++ b/r5dev/tier1/IConVar.cpp @@ -221,7 +221,8 @@ void ConVar::InitShipped(void) const #ifndef CLIENT_DLL ai_script_nodes_draw->SetValue(-1); #endif // !CLIENT_DLL - mp_gamemode->SetCallback(&MP_GameMode_Changed_f); + mp_gamemode->RemoveChangeCallback(mp_gamemode->m_fnChangeCallbacks[0]); + mp_gamemode->InstallChangeCallback(MP_GameMode_Changed_f, false); } //----------------------------------------------------------------------------- @@ -292,6 +293,10 @@ void ConVar::PurgeHostNames(void) const void ConVar::AddFlags(int nFlags) { m_pParent->m_nFlags |= nFlags; + +#ifdef ALLOW_DEVELOPMENT_CVARS + m_pParent->m_nFlags &= ~FCVAR_DEVELOPMENTONLY; +#endif } //----------------------------------------------------------------------------- @@ -534,15 +539,15 @@ void ConVar::InternalSetValue(const char* pszValue) pszNewValue = ""; } - if (!SetColorFromString(pszValue)) + if (!SetColorFromString(pszNewValue)) { // Not a color, do the standard thing - double dblValue = atof(pszValue); // Use double to avoid 24-bit restriction on integers and allow storing timestamps or dates in convars + double dblValue = atof(pszNewValue); // Use double to avoid 24-bit restriction on integers and allow storing timestamps or dates in convars float flNewValue = static_cast(dblValue); if (!IsFinite(flNewValue)) { - Warning(eDLL_T::ENGINE, "Warning: ConVar '%s' = '%s' is infinite, clamping value.\n", GetBaseName(), pszValue); + Warning(eDLL_T::ENGINE, "Warning: ConVar '%s' = '%s' is infinite, clamping value.\n", GetBaseName(), pszNewValue); flNewValue = FLT_MAX; } @@ -705,15 +710,6 @@ void ConVar::SetDefault(const char* pszDefault) assert(m_pszDefaultValue); } -//----------------------------------------------------------------------------- -// Purpose: sets the ConVar callback. -// Input : *pCallback - -//----------------------------------------------------------------------------- -void ConVar::SetCallback(void* pCallback) -{ - *m_Callback.m_ppCallback = *&pCallback; -} - //----------------------------------------------------------------------------- // Purpose: sets the ConVar color value from string. // Input : *pszValue - @@ -723,7 +719,7 @@ bool ConVar::SetColorFromString(const char* pszValue) bool bColor = false; // Try pulling RGBA color values out of the string. - int nRGBA[4]{}; + int nRGBA[4]; int nParamsRead = sscanf_s(pszValue, "%i %i %i %i", &(nRGBA[0]), &(nRGBA[1]), &(nRGBA[2]), &(nRGBA[3])); if (nParamsRead >= 3) @@ -766,52 +762,43 @@ bool ConVar::SetColorFromString(const char* pszValue) //----------------------------------------------------------------------------- void ConVar::ChangeStringValue(const char* pszTempVal) { - assert(!(m_nFlags & FCVAR_NEVER_AS_STRING)); + Assert(!(m_nFlags & FCVAR_NEVER_AS_STRING)); - char* pszOldValue = reinterpret_cast(_malloca(m_Value.m_iStringLength)); - if (pszOldValue != nullptr) - { - memcpy(pszOldValue, m_Value.m_pszString, m_Value.m_iStringLength); - } + char* pszOldValue = (char*)stackalloc(m_Value.m_iStringLength); + memcpy(pszOldValue, m_Value.m_pszString, m_Value.m_iStringLength); - if (pszTempVal) + int len = strlen(pszTempVal) + 1; + + if (len > m_Value.m_iStringLength) { - size_t len = strlen(pszTempVal) + 1; - if (len > m_Value.m_iStringLength) + if (m_Value.m_pszString) { - if (m_Value.m_pszString) - { - MemAllocSingleton()->Free(m_Value.m_pszString); - } - - m_Value.m_pszString = MemAllocSingleton()->Alloc(len); - m_Value.m_iStringLength = len; + MemAllocSingleton()->Free(m_Value.m_pszString); } - else if (!m_Value.m_pszString) - { - m_Value.m_pszString = MemAllocSingleton()->Alloc(len); - m_Value.m_iStringLength = len; - } - memmove(m_Value.m_pszString, pszTempVal, len); - /***** - !FIXME: - Respawn put additional code here which - seems to itterate over a 64bit integer - to call the callback several times (as many times as m_iCallbackCount?). - ******/ + m_Value.m_pszString = MemAllocSingleton()->Alloc(len); + m_Value.m_iStringLength = len; } - else + + memcpy(reinterpret_cast(m_Value.m_pszString), pszTempVal, len); + + // Invoke any necessary callback function + for (int i = 0; i < m_fnChangeCallbacks.Count(); ++i) { - m_Value.m_pszString = nullptr; + m_fnChangeCallbacks[i](reinterpret_cast(&m_pIConVarVFTable), pszOldValue, NULL); } - pszOldValue = nullptr; + if (g_pCVar) + { + g_pCVar->CallGlobalChangeCallbacks(this, pszOldValue); + } + + stackfree(pszOldValue); } //----------------------------------------------------------------------------- -// Purpose: changes the ConVar string value (this is faster than ChangeStringValue, -// only use if the new string is equal or lower than this->m_iStringLength). +// Purpose: changes the ConVar string value without calling the callback +// (Size of new string must be equal or lower than m_iStringLength!!!) // Input : *pszTempVal - flOldValue //----------------------------------------------------------------------------- void ConVar::ChangeStringValueUnsafe(const char* pszNewValue) @@ -819,6 +806,46 @@ void ConVar::ChangeStringValueUnsafe(const char* pszNewValue) m_Value.m_pszString = const_cast(pszNewValue); } +//----------------------------------------------------------------------------- +// Purpose: Install a change callback (there shouldn't already be one....) +// Input : callback - +// bInvoke - +//----------------------------------------------------------------------------- +void ConVar::InstallChangeCallback(FnChangeCallback_t callback, bool bInvoke /*=true*/) +{ + if (!callback) + { + Warning(eDLL_T::ENGINE, "%s: called with NULL callback, ignoring!!!\n", __FUNCTION__); + return; + } + + if (m_pParent->m_fnChangeCallbacks.Find(callback) != m_pParent->m_fnChangeCallbacks.InvalidIndex()) + { + // Same ptr added twice, sigh... + Warning(eDLL_T::ENGINE, "%s: ignoring duplicate change callback!!!\n", __FUNCTION__); + return; + } + + m_pParent->m_fnChangeCallbacks.AddToTail(callback); + + // Call it immediately to set the initial value... + if (bInvoke) + { + callback(reinterpret_cast(&m_pIConVarVFTable), m_Value.m_pszString, m_Value.m_fValue); + } + + sizeof(CUtlVector); +} + +//----------------------------------------------------------------------------- +// Purpose: Install a change callback (there shouldn't already be one....) +// Input : callback - +//----------------------------------------------------------------------------- +void ConVar::RemoveChangeCallback(FnChangeCallback_t callback) +{ + m_pParent->m_fnChangeCallbacks.FindAndRemove(callback); +} + //----------------------------------------------------------------------------- // Purpose: Checks if ConVar is registered. // Output : bool diff --git a/r5dev/tier1/IConVar.h b/r5dev/tier1/IConVar.h index 2e0c946a..e8476bf0 100644 --- a/r5dev/tier1/IConVar.h +++ b/r5dev/tier1/IConVar.h @@ -2,6 +2,7 @@ #include "tier1/cmd.h" #include "mathlib/color.h" #include "public/include/iconvar.h" +#include "tier1/utlvector.h" //----------------------------------------------------------------------------- // Purpose: A console variable @@ -58,12 +59,14 @@ public: const char* GetDefault(void) const; void SetDefault(const char* pszDefault); - void SetCallback(void* pCallback); bool SetColorFromString(const char* pszValue); void ChangeStringValue(const char* pszTempValue); void ChangeStringValueUnsafe(const char* pszNewValue); + void InstallChangeCallback(FnChangeCallback_t callback, bool bInvoke); + void RemoveChangeCallback(FnChangeCallback_t callback); + bool IsRegistered(void) const; bool IsCommand(void) const; static bool IsFlagSet(ConVar* pConVar, int nFlags); @@ -75,13 +78,6 @@ public: float m_fValue; int m_nValue; }; - struct CVCallback_t - { - void** m_ppCallback; - int64_t m_iFlags; - char m_Pad[8]; - int64_t m_iCallbackCount; - }; IConVar* m_pIConVarVFTable{}; //0x0040 ConVar* m_pParent {}; //0x0048 @@ -91,8 +87,9 @@ public: float m_fMinVal {}; //0x0074 bool m_bHasMax {}; //0x0078 float m_fMaxVal {}; //0x007C - CVCallback_t m_Callback {}; //0x0080 // <-- !FIXME: 'CUtlVector< FnChangeCallback_t > m_fnChangeCallbacks;' + CUtlVector m_fnChangeCallbacks; //0x0080 }; //Size: 0x00A0 +static_assert(sizeof(ConVar) == 0xA0); /* ==== ICONVAR ========================================================================================================================================================= */ inline CMemory p_IConVar_IsFlagSet; @@ -104,9 +101,6 @@ inline auto ConVar_SetInfo = p_ConVar_SetInfo.RCast(); -inline CMemory p_ConVar_ChangeStringValue; -inline auto ConVar_ChangeStringValue = p_IConVar_IsFlagSet.RCast(); - inline CMemory g_pConVarVFTable; inline CMemory g_pIConVarVFTable; @@ -121,10 +115,9 @@ class VConVar : public IDetour { virtual void GetAdr(void) const { - spdlog::debug("| FUN: IConVar::IsFlagSet : {:#18x} |\n", p_IConVar_IsFlagSet.GetPtr()); - spdlog::debug("| FUN: IConVar::SetInfo : {:#18x} |\n", p_ConVar_SetInfo.GetPtr()); - spdlog::debug("| FUN: IConVar::Register : {:#18x} |\n", p_ConVar_Register.GetPtr()); - spdlog::debug("| FUN: ConVar::ChangeStringValue : {:#18x} |\n", p_ConVar_ChangeStringValue.GetPtr()); + spdlog::debug("| FUN: ConVar::IsFlagSet : {:#18x} |\n", p_IConVar_IsFlagSet.GetPtr()); + spdlog::debug("| FUN: ConVar::SetInfo : {:#18x} |\n", p_ConVar_SetInfo.GetPtr()); + spdlog::debug("| FUN: ConVar::Register : {:#18x} |\n", p_ConVar_Register.GetPtr()); spdlog::debug("| VAR: g_pConVarVtable : {:#18x} |\n", g_pConVarVFTable.GetPtr()); spdlog::debug("| VAR: g_pIConVarVtable : {:#18x} |\n", g_pIConVarVFTable.GetPtr()); spdlog::debug("+----------------------------------------------------------------+\n"); @@ -138,12 +131,9 @@ class VConVar : public IDetour #elif defined (GAMEDLL_S2) || defined (GAMEDLL_S3) p_ConVar_Register = g_mGameDll.FindPatternSIMD(reinterpret_cast("\x48\x89\x5C\x24\x00\x48\x89\x6C\x24\x00\x48\x89\x74\x24\x00\x57\x48\x83\xEC\x40\xF3\x0F\x10\x84\x24\x00\x00\x00\x00"), "xxxx?xxxx?xxxx?xxxxxxxxxx????"); #endif - p_ConVar_ChangeStringValue = g_mGameDll.FindPatternSIMD(reinterpret_cast("\x40\x55\x41\x56\x41\x57\x48\x83\xEC\x30\x48\x8D\x6C\x24\x00\x4C\x8B\x41\x60"), "xxxxxxxxxxxxxx?xxxx"); - IConVar_IsFlagSet = p_IConVar_IsFlagSet.RCast(); /*48 8B 41 48 85 50 38*/ ConVar_SetInfo = p_ConVar_SetInfo.RCast(); /*40 53 48 83 EC 60 48 8B D9 C6 41 10 00 33 C9 48 8D 05 ? ? ? ? 48 89 4C 24 ? 0F 57 C0 48 89 4C 24 ? 48 89 03 48 8D 05 ? ? ? ? 48 89 43 40*/ ConVar_Register = p_ConVar_Register.RCast(); /*48 89 5C 24 ? 48 89 6C 24 ? 48 89 74 24 ? 57 48 83 EC 40 F3 0F 10 84 24 ? ? ? ?*/ - ConVar_ChangeStringValue = p_IConVar_IsFlagSet.RCast(); /*40 55 41 56 41 57 48 83 EC 30 48 8D 6C 24 ? 4C 8B 41 60*/ } virtual void GetVar(void) const { diff --git a/r5dev/tier1/cvar.cpp b/r5dev/tier1/cvar.cpp index 99f3496e..b4a10f51 100644 --- a/r5dev/tier1/cvar.cpp +++ b/r5dev/tier1/cvar.cpp @@ -214,6 +214,39 @@ ConCommand* CCVar::FindCommand(const char* pszCommandName) return CallVFunc(index, this, pszCommandName); } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCVar::CallGlobalChangeCallbacks(ConVar* pConVar, const char* pOldString) +{ + const int index = 23; + CallVFunc(index, this, pConVar, pOldString); +} + +//----------------------------------------------------------------------------- +// Purpose: deal with queued material system ConVars +//----------------------------------------------------------------------------- +bool CCVar::IsMaterialThreadSetAllowed(void) +{ + const int index = 35; + return CallVFunc(index, this); +} +void CCVar::QueueMaterialThreadSetValue(ConVar* pConVar, float flValue) +{ + const int index = 36; + CallVFunc(index, this, pConVar, flValue); +} +void CCVar::QueueMaterialThreadSetValue(ConVar* pConVar, int nValue) +{ + const int index = 37; + CallVFunc(index, this, pConVar, nValue); +} +void CCVar::QueueMaterialThreadSetValue(ConVar* pConVar, const char* pValue) +{ + const int index = 38; + CallVFunc(index, this, pConVar, pValue); +} + //----------------------------------------------------------------------------- // Purpose: iterates over all ConVars //----------------------------------------------------------------------------- @@ -243,29 +276,5 @@ unordered_map CCVar::DumpToMap(void) return allConVars; } -//----------------------------------------------------------------------------- -// Purpose: deal with queued material system ConVars -//----------------------------------------------------------------------------- -bool CCVar::IsMaterialThreadSetAllowed(void) -{ - const int index = 280; - return CallVFunc(index, this); -} -void CCVar::QueueMaterialThreadSetValue(ConVar* pConVar, float flValue) -{ - const int index = 288; - CallVFunc(index, this, pConVar, flValue); -} -void CCVar::QueueMaterialThreadSetValue(ConVar* pConVar, int nValue) -{ - const int index = 296; - CallVFunc(index, this, pConVar, nValue); -} -void CCVar::QueueMaterialThreadSetValue(ConVar* pConVar, const char* pValue) -{ - const int index = 304; - CallVFunc(index, this, pConVar, pValue); -} - /////////////////////////////////////////////////////////////////////////////// CCVar* g_pCVar = nullptr; diff --git a/r5dev/tier1/cvar.h b/r5dev/tier1/cvar.h index ca34e9f9..534346cd 100644 --- a/r5dev/tier1/cvar.h +++ b/r5dev/tier1/cvar.h @@ -180,13 +180,15 @@ public: ConCommandBase* FindCommandBase(const char* pszCommandName); // @0x1405983A0 in R5pc_r5launch_N1094_CL456479_2019_10_30_05_20_PM ConVar* FindVar(const char* pszVarName); // @0x1405983B0 in R5pc_r5launch_N1094_CL456479_2019_10_30_05_20_PM ConCommand* FindCommand(const char* pszCommandName); - CCVarIteratorInternal* FactoryInternalIterator(void); - unordered_map DumpToMap(void); + void CallGlobalChangeCallbacks(ConVar* pConVar, const char* pOldString); bool IsMaterialThreadSetAllowed(void); void QueueMaterialThreadSetValue(ConVar* pConVar, float flValue); void QueueMaterialThreadSetValue(ConVar* pConVar, int nValue); void QueueMaterialThreadSetValue(ConVar* pConVar, const char* pValue); + + CCVarIteratorInternal* FactoryInternalIterator(void); + unordered_map DumpToMap(void); }; /////////////////////////////////////////////////////////////////////////////// diff --git a/r5dev/vstdlib/callback.cpp b/r5dev/vstdlib/callback.cpp index a6740794..00556379 100644 --- a/r5dev/vstdlib/callback.cpp +++ b/r5dev/vstdlib/callback.cpp @@ -52,9 +52,9 @@ MP_GameMode_Changed_f ===================== */ -bool MP_GameMode_Changed_f(ConVar* pVTable) +void MP_GameMode_Changed_f(IConVar* pConVar, const char* pOldString, float flOldValue) { - return SetupGamemode(mp_gamemode->GetString()); + SetupGamemode(mp_gamemode->GetString()); } #ifndef DEDICATED @@ -780,7 +780,8 @@ void RCON_CmdQuery_f(const CCommand& args) return; } - RCONClient()->Send(RCONClient()->Serialize(args.ArgS(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND)); + string svCmdQuery = RCONClient()->Serialize(args.ArgS(), "", cl_rcon::request_t::SERVERDATA_REQUEST_EXECCOMMAND); + RCONClient()->Send(svCmdQuery); return; } else @@ -1025,11 +1026,11 @@ BHit_f */ void BHit_f(const CCommand& args) { -#if !defined (DEDICATED) && !defined (CLIENT_DLL) if (args.ArgC() != 9) return; - if (bhit_enable->GetBool() && sv_visualizetraces->GetBool()) +#ifndef DEDICATED + if (sv_visualizetraces->GetBool()) { Vector3D vecAbsStart; Vector3D vecAbsEnd; @@ -1065,5 +1066,5 @@ void BHit_f(const CCommand& args) Cbuf_AddText(Cbuf_GetCurrentPlayer(), szBuf, cmd_source_t::kCommandSrcCode); Cbuf_Execute(); } -#endif // !DEDICATED && !CLIENT_DLL +#endif // !DEDICATED } diff --git a/r5dev/vstdlib/callback.h b/r5dev/vstdlib/callback.h index ace80bca..7a20c075 100644 --- a/r5dev/vstdlib/callback.h +++ b/r5dev/vstdlib/callback.h @@ -12,7 +12,7 @@ inline CMemory p_DownloadPlaylists_f; inline auto _DownloadPlaylists_f = p_DownloadPlaylists_f.RCast(); /////////////////////////////////////////////////////////////////////////////// -bool MP_GameMode_Changed_f(ConVar* pVTable); +void MP_GameMode_Changed_f(IConVar* pConVar, const char* pOldString, float flOldValue); #ifndef DEDICATED void GameConsole_Invoke_f(const CCommand& args); void ServerBrowser_Invoke_f(const CCommand& args);