diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj
index 61b6b5a9..e64a54be 100644
--- a/CS2Fixes.vcxproj
+++ b/CS2Fixes.vcxproj
@@ -294,6 +294,7 @@
+
@@ -303,4 +304,4 @@
-
\ No newline at end of file
+
diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters
index 68bb9f35..b59b89e5 100644
--- a/CS2Fixes.vcxproj.filters
+++ b/CS2Fixes.vcxproj.filters
@@ -217,6 +217,9 @@
Header Files\utils
+
+ Header Files\utils
+
Header Files\cs2_sdk
@@ -428,4 +431,4 @@
Header Files
-
\ No newline at end of file
+
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..553db14d 100644
--- a/src/addresses.cpp
+++ b/src/addresses.cpp
@@ -29,6 +29,14 @@
return false; \
Message("Found %s at 0x%p\n", name, variable);
+#define RESOLVE_SF(className, funcName, variable) \
+ if (!variable.Initialize(GetScriptFunction(className, funcName))) \
+ return false; \
+ if (variable.IsVirtual()) \
+ Message("Found %s::%s at vtable index %i\n", className, funcName, variable.GetOffset()); \
+ else \
+ Message("Found %s::%s at 0x%p\n", className, funcName, variable.GetPtr());
+
bool addresses::Initialize(CGameConfig* g_GameConfig)
{
modules::engine = new CModule(ROOTBIN, "engine2");
@@ -51,7 +59,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 +69,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 +106,18 @@ bool addresses::InitializeBanMap(CGameConfig* g_GameConfig)
Message("Found %s at 0x%p\n", "CCSGameRules__sm_mapGcBanInformation", addresses::sm_mapGcBanInformation);
return true;
}
+
+bool addresses::InitializeScriptFunctions()
+{
+ ExecuteOnce
+ (
+ RESOLVE_SF("CBaseEntity", "SetGravity", SetGravityScale);
+ RESOLVE_SF("CBaseEntity", "SetEntityName", ScriptSetEntityName);
+ RESOLVE_SF("CBaseEntity", "EmitSoundParams", ScriptEmitSoundParams);
+ RESOLVE_SF("CBaseEntity", "SetTeam", ChangeTeam);
+ RESOLVE_SF("CBaseEntity", "IsPlayerPawn", IsPlayerPawn);
+ RESOLVE_SF("CBaseEntity", "IsPlayerController", IsPlayerController);
+ )
+
+ return true;
+}
diff --git a/src/addresses.h b/src/addresses.h
index 18a43c00..e112f24f 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/script_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 InitializeScriptFunctions();
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 CScriptFunction SetGravityScale;
+ inline CScriptFunction ScriptSetEntityName;
+ inline CScriptFunction ScriptEmitSoundParams;
+ inline CScriptFunction ChangeTeam;
+ inline CScriptFunction IsPlayerPawn;
+ inline CScriptFunction 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..85c5e169 100644
--- a/src/cs2fixes.cpp
+++ b/src/cs2fixes.cpp
@@ -388,6 +388,9 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
g_pEntitySystem = GameEntitySystem();
g_pEntitySystem->AddListenerEntity(g_pEntityListener);
+ if (!addresses::InitializeScriptFunctions())
+ Panic("Failed to resolve one or more script functions");
+
g_playerManager->OnLateLoad();
g_pPanoramaVoteHandler->Reset();
@@ -614,6 +617,9 @@ void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISou
RegisterEventListeners();
+ if (!addresses::InitializeScriptFunctions())
+ Panic("Failed to resolve one or more script functions");
+
if (g_bHasTicked)
RemoveTimers(TIMERFLAG_MAP);
diff --git a/src/utils/script_function.h b/src/utils/script_function.h
new file mode 100644
index 00000000..73f97d88
--- /dev/null
+++ b/src/utils/script_function.h
@@ -0,0 +1,106 @@
+/**
+ * =============================================================================
+ * 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"
+
+template
+class CScriptFunction
+{
+public:
+ 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)
+ {
+#ifdef PLATFORM_LINUX
+ if (m_bVirtual)
+ {
+ auto pFunction = vmt::GetVMethod(m_iOffset, pThis);
+
+ return pFunction(pThis, args...);
+ }
+#endif
+
+ return m_pFunction(pThis, args...);
+ }
+
+ inline bool IsVirtual() const
+ {
+#ifdef PLATFORM_LINUX
+ return m_bVirtual;
+#endif
+
+ return false;
+ }
+
+ inline void* GetPtr() const
+ {
+#ifdef PLATFORM_LINUX
+ if (IsVirtual())
+ return nullptr;
+#endif
+
+ return reinterpret_cast(m_pFunction);
+ }
+
+ inline int GetOffset() const
+ {
+#ifdef PLATFORM_LINUX
+ if (IsVirtual())
+ return m_iOffset;
+#endif
+
+ return -1;
+ }
+
+private:
+ union
+ {
+ Ret(FASTCALL* m_pFunction)(Class*, Args...);
+#ifdef PLATFORM_LINUX
+ int m_iOffset;
+#endif
+ };
+
+#ifdef PLATFORM_LINUX
+ bool m_bVirtual = false;
+#endif
+};
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
index b2114219..6d5f3ba8 100644
--- a/src/utils/utils.cpp
+++ b/src/utils/utils.cpp
@@ -20,6 +20,8 @@
#include "utils.h"
#include "../cs2fixes.h"
#include "../gameconfig.h"
+#include "entitysystem.h"
+#include "entityclass.h"
void Message(const char* msg, ...)
{
@@ -93,4 +95,34 @@ ISteamHTTP* GetSteamHTTP()
return SteamGameServerHTTP();
else
return SteamHTTP();
-}
\ No newline at end of file
+}
+
+void* GetScriptFunction(const char* pszClassName, const char* pszFuncName)
+{
+ auto& entClassesByCPPClassname = g_pEntitySystem->m_entClassesByCPPClassname;
+ auto nIndex = entClassesByCPPClassname.Find(pszClassName);
+ if (nIndex != entClassesByCPPClassname.InvalidIndex())
+ {
+ // 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*>(&(reinterpret_cast(entClassesByCPPClassname.Element(nIndex)->m_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/utils.h b/src/utils/utils.h
index 24d16c66..8b2612e9 100644
--- a/src/utils/utils.h
+++ b/src/utils/utils.h
@@ -36,4 +36,6 @@ uint32 GetSoundEventHash(const char* pszSoundEventName);
std::string StringToLower(std::string strValue);
ISteamUGC* GetSteamUGC();
-ISteamHTTP* GetSteamHTTP();
\ No newline at end of file
+ISteamHTTP* GetSteamHTTP();
+
+void* GetScriptFunction(const char* pszClassName, const char* pszFuncName);