diff --git a/AMBuilder b/AMBuilder
index 033d025c..f359a5d9 100644
--- a/AMBuilder
+++ b/AMBuilder
@@ -50,6 +50,7 @@ for sdk_target in MMSPlugin.sdk_targets:
'src/utils/weapon.cpp',
'src/utils/hud_manager.cpp',
'src/utils/utils.cpp',
+ 'src/utils/vscript_function.cpp',
'src/cs2_sdk/entity/services.cpp',
'src/cs2_sdk/schema.cpp',
'src/ctimer.cpp',
diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj
index 61b6b5a9..c5a4d480 100644
--- a/CS2Fixes.vcxproj
+++ b/CS2Fixes.vcxproj
@@ -221,6 +221,7 @@
+
@@ -294,6 +295,7 @@
+
diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters
index 68bb9f35..a15370b7 100644
--- a/CS2Fixes.vcxproj.filters
+++ b/CS2Fixes.vcxproj.filters
@@ -188,6 +188,9 @@
Source Files\utils
+
+ Source Files\utils
+
Source Files\cs2_sdk\entity
@@ -217,6 +220,9 @@
Header Files\utils
+
+ Header Files\utils
+
Header Files\cs2_sdk
diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc
index f18794d8..a521bf67 100644
--- a/gamedata/cs2fixes.jsonc
+++ b/gamedata/cs2fixes.jsonc
@@ -108,13 +108,6 @@
"windows": "40 55 53 56 57 41 54 48 8D 6C 24 ? 48 81 EC ? ? ? ? 4D 8B E0",
"linux": "55 66 0F EF C0 48 89 E5 41 57 41 56 41 55 49 89 FD 31 FF"
},
- // Should be xref'd right above "flGravity", takes a float arg
- "CBaseEntity::SetGravityScale":
- {
- "library": "server",
- "windows": "48 89 5C 24 ? 57 48 83 EC ? F3 0F 10 81 ? ? ? ? 48 8B F9 0F 29 74 24 ? 0F 28 F1 0F 2E C6 7A ? 74 ? BA ? ? ? ? 41 B8 ? ? ? ? 48 81 C1 ? ? ? ? E8 ? ? ? ? 48 8B CF F3 0F 11 B7 ? ? ? ? E8 ? ? ? ? 48 8B 5C 24 ? 0F 28 74 24 ? 48 83 C4 ? 5F C3 CC CC CC CC CC 48 89 5C 24",
- "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 53 48 89 FB 48 81 EC ? ? ? ? 0F 2E 87 ? ? ? ? 7A ? 75 ? 48 81 C4 ? ? ? ? 5B 41 5C 41 5D 41 5E 41 5F 5D C3 0F 1F 40 ? 31 C9 BE ? ? ? ? 66 0F EF C9 F3 0F 11 85 ? ? ? ? 48 8D BD ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 0F 29 8D ? ? ? ? 4C 8D A5 ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? C7 85 ? ? ? ? ? ? ? ? 66 89 8D ? ? ? ? E8 ? ? ? ? 48 8B 85 ? ? ? ? 48 8D 15 ? ? ? ? 83 85 ? ? ? ? ? F3 0F 10 85 ? ? ? ? C7 00 ? ? ? ? 48 8B 03 48 8B 80 ? ? ? ? 48 39 D0 0F 85 ? ? ? ? 8B 95 ? ? ? ? 4C 8D 7B ? 85 D2 0F 85 ? ? ? ? 80 BB ? ? ? ? ? 75"
- },
// "Game System %s is defined twice!\n"
// Note that this signature points to the instruction with sm_pFirst which is the first qword referenced in the function.
"IGameSystem_InitAllSystems_pFirst":
@@ -199,15 +192,6 @@
"windows": "48 89 5C 24 10 57 48 83 EC 30 48 8B DA 48 8B F9 48 85 C9",
"linux": "48 85 FF 74 ? 55 48 89 E5 41 55 41 54 49 89 FC"
},
- // Look for "SetEntityName", that will be the vscript binding definition
- // Scroll a bit down and you'll find something like this (note the offset): *(_QWORD *)(v453 + 64) = sub_1807B0350;
- // that function is just a jump to the one we want
- "CEntityIdentity_SetEntityName":
- {
- "library": "server",
- "windows": "48 89 5C 24 10 57 48 83 EC 20 48 8B D9 4C 8B C2",
- "linux": "55 48 89 F2 48 89 E5 41 55 41 54 53"
- },
// "Error - cannot add bots after game is over."
"BotNavIgnore":
{
@@ -215,14 +199,6 @@
"windows": "0F 84 ? ? ? ? 80 B8 ? ? ? ? 00 0F 84 ? ? ? ? 80 3D ? ? ? ? 00 74 15",
"linux": "0F 84 ? ? ? ? 44 0F B6 B8 ? ? ? ? 45 84 FF 0F 84"
},
- // next to "soundname", in windows it's the last referenced sub while in linux it's right after
- // this is a vscript binding though so it may be removed in the future?
- "CBaseEntity_EmitSoundParams":
- {
- "library": "server",
- "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 48 8B EC 48 81 EC ? ? ? ? 33 C0",
- "linux": "48 B8 ? ? ? ? ? ? ? ? 55 0F 28 D0"
- },
// "ParticleEffect", found in a function with 9 arguments
"DispatchParticleEffect":
{
@@ -401,12 +377,6 @@
"windows": 53,
"linux": 52
},
- // String: "%s<%i><%s><%s>" ChangeTeam() CTMDBG..."
- "CCSPlayerController_ChangeTeam":
- {
- "windows": 103,
- "linux": 102
- },
"CBaseEntity::Use":
{
"windows": 145,
@@ -432,18 +402,6 @@
"windows": 163,
"linux": 162
},
- // For these two, look for the names, you'll find vscript bindings
- // Scroll down to where the var + 64 gets set to a function, that calls the offset we want
- "IsPlayerPawn":
- {
- "windows": 169,
- "linux": 168
- },
- "IsPlayerController":
- {
- "windows": 170,
- "linux": 169
- },
"CollisionRulesChanged":
{
"windows": 186,
diff --git a/src/addresses.cpp b/src/addresses.cpp
index 6a0fe6d7..5fae6ed6 100644
--- a/src/addresses.cpp
+++ b/src/addresses.cpp
@@ -21,6 +21,10 @@
#include "gameconfig.h"
#include "utils/module.h"
+#include "entityinstance.h"
+#include "tier1/strtools.h"
+#include "vscript/ivscript.h"
+
#include "tier0/memdbgon.h"
#define RESOLVE_SIG(gameConfig, name, variable) \
@@ -29,6 +33,14 @@
return false; \
Message("Found %s at 0x%p\n", name, variable);
+#define RESOLVE_SF(scriptDesc, funcName, variable) \
+ if (!variable.Initialize(GetVScriptFunction(scriptDesc, funcName))) \
+ return false; \
+ if (variable.IsVirtual()) \
+ Message("Found %s::%s at vtable index %i\n", scriptDesc->m_pszClassname, funcName, variable.GetOffset()); \
+ else \
+ Message("Found %s::%s at 0x%p\n", scriptDesc->m_pszClassname, funcName, variable.GetPtr());
+
bool addresses::Initialize(CGameConfig* g_GameConfig)
{
modules::engine = new CModule(ROOTBIN, "engine2");
@@ -51,7 +63,6 @@ bool addresses::Initialize(CGameConfig* g_GameConfig)
#endif
RESOLVE_SIG(g_GameConfig, "SetGroundEntity", addresses::SetGroundEntity);
- RESOLVE_SIG(g_GameConfig, "CBaseEntity::SetGravityScale", addresses::SetGravityScale);
RESOLVE_SIG(g_GameConfig, "CCSPlayerController_SwitchTeam", addresses::CCSPlayerController_SwitchTeam);
RESOLVE_SIG(g_GameConfig, "CBasePlayerController_SetPawn", addresses::CBasePlayerController_SetPawn);
RESOLVE_SIG(g_GameConfig, "CBaseModelEntity_SetModel", addresses::CBaseModelEntity_SetModel);
@@ -62,8 +73,6 @@ bool addresses::Initialize(CGameConfig* g_GameConfig)
RESOLVE_SIG(g_GameConfig, "CGameRules_TerminateRound", addresses::CGameRules_TerminateRound);
RESOLVE_SIG(g_GameConfig, "CreateEntityByName", addresses::CreateEntityByName);
RESOLVE_SIG(g_GameConfig, "DispatchSpawn", addresses::DispatchSpawn);
- RESOLVE_SIG(g_GameConfig, "CEntityIdentity_SetEntityName", addresses::CEntityIdentity_SetEntityName);
- RESOLVE_SIG(g_GameConfig, "CBaseEntity_EmitSoundParams", addresses::CBaseEntity_EmitSoundParams);
RESOLVE_SIG(g_GameConfig, "DispatchParticleEffect", addresses::DispatchParticleEffect);
RESOLVE_SIG(g_GameConfig, "CBaseEntity_EmitSoundFilter", addresses::CBaseEntity_EmitSoundFilter);
RESOLVE_SIG(g_GameConfig, "CBaseEntity_SetMoveType", addresses::CBaseEntity_SetMoveType);
@@ -101,3 +110,25 @@ bool addresses::InitializeBanMap(CGameConfig* g_GameConfig)
Message("Found %s at 0x%p\n", "CCSGameRules__sm_mapGcBanInformation", addresses::sm_mapGcBanInformation);
return true;
}
+
+bool addresses::InitializeVScriptFunctions()
+{
+ void* pCBaseEntityVTable = modules::server->FindVirtualTable("CBaseEntity");
+ if (!pCBaseEntityVTable)
+ {
+ Message("Failed to find CBaseEntity vtable\n");
+ return false;
+ }
+
+ // GetScriptDesc ignores this, so the vtable pointer is sufficient here.
+ ScriptClassDesc_t* pCBaseEntityScriptDesc = reinterpret_cast(reinterpret_cast(&pCBaseEntityVTable)->GetScriptDesc());
+
+ RESOLVE_SF(pCBaseEntityScriptDesc, "SetGravity", SetGravityScale);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "SetEntityName", ScriptSetEntityName);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "EmitSoundParams", ScriptEmitSoundParams);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "SetTeam", ChangeTeam);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "IsPlayerPawn", IsPlayerPawn);
+ RESOLVE_SF(pCBaseEntityScriptDesc, "IsPlayerController", IsPlayerController);
+
+ return true;
+}
diff --git a/src/addresses.h b/src/addresses.h
index 18a43c00..8cb5ead1 100644
--- a/src/addresses.h
+++ b/src/addresses.h
@@ -21,6 +21,7 @@
#include "platform.h"
#include "stdint.h"
#include "utils/module.h"
+#include "utils/vscript_function.h"
#include "utlstring.h"
#include "variant.h"
@@ -74,11 +75,11 @@ namespace addresses
{
bool Initialize(CGameConfig* g_GameConfig);
bool InitializeBanMap(CGameConfig* g_GameConfig);
+ bool InitializeVScriptFunctions();
inline CUtlMap* sm_mapGcBanInformation;
inline void(FASTCALL* SetGroundEntity)(CBaseEntity* ent, CBaseEntity* ground, CBaseEntity* unk3);
- inline void(FASTCALL* SetGravityScale)(CBaseEntity*, float);
inline void(FASTCALL* CCSPlayerController_SwitchTeam)(CCSPlayerController* pController, uint32 team);
inline void(FASTCALL* CBasePlayerController_SetPawn)(CBasePlayerController* pController, CCSPlayerPawn* pPawn, bool a3, bool a4, bool a5, bool a6);
inline void(FASTCALL* CBaseModelEntity_SetModel)(CBaseModelEntity* pModel, const char* pszModel);
@@ -97,8 +98,6 @@ namespace addresses
inline void(FASTCALL* CGameRules_TerminateRound)(CGameRules* pGameRules, float delay, unsigned int reason, int64 a4);
inline CBaseEntity*(FASTCALL* CreateEntityByName)(const char* className, int iForceEdictIndex);
inline void(FASTCALL* DispatchSpawn)(CBaseEntity* pEntity, CEntityKeyValues* pEntityKeyValues);
- inline void(FASTCALL* CEntityIdentity_SetEntityName)(CEntityIdentity* pEntity, const char* pName);
- inline void(FASTCALL* CBaseEntity_EmitSoundParams)(CBaseEntity* pEntity, const char* pszSound, int nPitch, float flVolume, float flDelay);
inline int(FASTCALL* DispatchParticleEffect)(const char* pszParticleName, int iAttachType, CBaseEntity* pEntity,
char iAttachmentPoint, CUtlSymbolLarge iAttachmentName, bool bResetAllParticlesOnEntity, int nSplitScreenPlayerSlot, IRecipientFilter* a7, byte* a8);
inline StartSoundEventInfo(FASTCALL* CBaseEntity_EmitSoundFilter)(IRecipientFilter& filter, CEntityIndex ent, const EmitSound_t& params);
@@ -108,4 +107,11 @@ namespace addresses
inline void(FASTCALL* CCSPlayer_WeaponServices_EquipWeapon)(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon);
inline void(FASTCALL* GetSpawnGroups)(CSpawnGroupMgrGameSystem* pSpawnGroupMgr, CUtlVector* pList);
inline void(FASTCALL* CBasePlayerPawn_SnapViewAngles)(CBasePlayerPawn* pPawn, QAngle* pAngles);
-} // namespace addresses
\ No newline at end of file
+
+ inline CVScriptFunction SetGravityScale;
+ inline CVScriptFunction ScriptSetEntityName;
+ inline CVScriptFunction ScriptEmitSoundParams;
+ inline CVScriptFunction ChangeTeam;
+ inline CVScriptFunction IsPlayerPawn;
+ inline CVScriptFunction IsPlayerController;
+} // namespace addresses
diff --git a/src/cs2_sdk/entity/cbaseentity.h b/src/cs2_sdk/entity/cbaseentity.h
index bcf1955f..6d3b3a93 100644
--- a/src/cs2_sdk/entity/cbaseentity.h
+++ b/src/cs2_sdk/entity/cbaseentity.h
@@ -165,7 +165,7 @@ class CBaseEntity : public CEntityInstance
void SetName(const char* pName)
{
- addresses::CEntityIdentity_SetEntityName(m_pEntity, pName);
+ addresses::ScriptSetEntityName(this, pName);
}
void TakeDamage(CTakeDamageInfo& info)
@@ -196,14 +196,12 @@ class CBaseEntity : public CEntityInstance
bool IsPawn()
{
- static int offset = g_GameConfig->GetOffset("IsPlayerPawn");
- return CALL_VIRTUAL(bool, offset, this);
+ return addresses::IsPlayerPawn(this);
}
bool IsController()
{
- static int offset = g_GameConfig->GetOffset("IsPlayerController");
- return CALL_VIRTUAL(bool, offset, this);
+ return addresses::IsPlayerController(this);
}
void AcceptInput(const char* pInputName, variant_t value = variant_t(""), CEntityInstance* pActivator = nullptr, CEntityInstance* pCaller = nullptr)
@@ -226,7 +224,7 @@ class CBaseEntity : public CEntityInstance
// Emit a sound event
void EmitSound(const char* pszSound, int nPitch = 100, float flVolume = 1.0, float flDelay = 0.0)
{
- addresses::CBaseEntity_EmitSoundParams(this, pszSound, nPitch, flVolume, flDelay);
+ addresses::ScriptEmitSoundParams(this, pszSound, nPitch, flVolume, flDelay);
}
StartSoundEventInfo EmitSoundFilter(IRecipientFilter& filter, const char* pszSound, float flVolume = 1.0, float flPitch = 1.0)
diff --git a/src/cs2_sdk/entity/ccsplayercontroller.h b/src/cs2_sdk/entity/ccsplayercontroller.h
index cc994b9d..5376c666 100644
--- a/src/cs2_sdk/entity/ccsplayercontroller.h
+++ b/src/cs2_sdk/entity/ccsplayercontroller.h
@@ -84,8 +84,7 @@ class CCSPlayerController : public CBasePlayerController
void ChangeTeam(int iTeam)
{
- static int offset = g_GameConfig->GetOffset("CCSPlayerController_ChangeTeam");
- CALL_VIRTUAL(void, offset, this, iTeam);
+ addresses::ChangeTeam(this, iTeam);
}
void SwitchTeam(int iTeam)
diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp
index 6dee2ab3..cd0f306a 100644
--- a/src/cs2fixes.cpp
+++ b/src/cs2fixes.cpp
@@ -204,6 +204,9 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
if (!addresses::Initialize(g_GameConfig))
bRequiredInitLoaded = false;
+ if (!addresses::InitializeVScriptFunctions())
+ bRequiredInitLoaded = false;
+
if (!InitPatches(g_GameConfig))
bRequiredInitLoaded = false;
diff --git a/src/utils/virtual.h b/src/utils/virtual.h
index 32c696a7..8440e434 100644
--- a/src/utils/virtual.h
+++ b/src/utils/virtual.h
@@ -19,6 +19,7 @@
#pragma once
#include "platform.h"
+#include "tier0/dbg.h"
#define CALL_VIRTUAL(retType, idx, ...) \
vmt::CallVirtual(idx, __VA_ARGS__)
diff --git a/src/utils/vscript_function.cpp b/src/utils/vscript_function.cpp
new file mode 100644
index 00000000..5519b81a
--- /dev/null
+++ b/src/utils/vscript_function.cpp
@@ -0,0 +1,47 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#include "vscript_function.h"
+#include "entityclass.h"
+#include "entitysystem.h"
+
+void* GetVScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName)
+{
+ // https://github.com/Wend4r/sourcesdk/blob/758e43823f02fcd40498d33a42cd93243258fe1e/public/vscript/ivscript.h#L280
+ struct ScriptFunctionBindingCurrent_t
+ {
+ ScriptFuncDescriptor_t m_desc;
+ ScriptClassDesc_t* m_pClassDesc;
+ ScriptBindingFunc_t m_pfnBinding;
+ void* m_pFunction;
+ ScriptFuncBindingFlags_t m_flags;
+ };
+
+ const auto& functionBindings = *reinterpret_cast*>(&pScriptDesc->m_FunctionBindings);
+ FOR_EACH_VEC(functionBindings, i)
+ {
+ auto& functionBinding = functionBindings.Element(i);
+ if (V_strcmp(functionBinding.m_desc.m_pszScriptName, pszFuncName) != 0)
+ continue;
+
+ return functionBinding.m_pFunction;
+ }
+
+ return nullptr;
+}
diff --git a/src/utils/vscript_function.h b/src/utils/vscript_function.h
new file mode 100644
index 00000000..fe990fd1
--- /dev/null
+++ b/src/utils/vscript_function.h
@@ -0,0 +1,102 @@
+/**
+ * =============================================================================
+ * CS2Fixes
+ * Copyright (C) 2023-2026 Source2ZE
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include "platform.h"
+#include "virtual.h"
+
+struct ScriptClassDesc_t;
+class CBaseEntity;
+class Vector;
+class QAngle;
+
+void* GetVScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName);
+
+template
+class CVScriptFunction
+{
+public:
+ CVScriptFunction() :
+ m_pFunction(nullptr), m_bVirtual(false) {}
+
+ inline bool Initialize(void* pFunction)
+ {
+ if (!pFunction)
+ return false;
+
+#ifdef PLATFORM_LINUX
+ // Itanium ABI: a pointer may encode the virtual index
+ uintptr_t ptr = reinterpret_cast(pFunction);
+ if (ptr & 1)
+ {
+ m_bVirtual = true;
+ m_iOffset = static_cast((ptr - 1) >> 3);
+
+ return true;
+ }
+#endif
+
+ m_pFunction = reinterpret_cast(pFunction);
+
+ return true;
+ }
+
+ inline Ret operator()(Class* pThis, Args... args)
+ {
+ if (m_bVirtual)
+ {
+ auto pFunction = vmt::GetVMethod(m_iOffset, pThis);
+
+ return pFunction(pThis, args...);
+ }
+
+ return m_pFunction(pThis, args...);
+ }
+
+ inline bool IsVirtual() const
+ {
+ return m_bVirtual;
+ }
+
+ inline void* GetPtr() const
+ {
+ if (IsVirtual())
+ return nullptr;
+
+ return reinterpret_cast(m_pFunction);
+ }
+
+ inline int GetOffset() const
+ {
+ if (IsVirtual())
+ return m_iOffset;
+
+ return -1;
+ }
+
+protected:
+ union
+ {
+ Ret(FASTCALL* m_pFunction)(Class*, Args...);
+ int m_iOffset;
+ };
+
+ bool m_bVirtual;
+};