From 002c37939e8fc87acc5c42c2ddf24c92492c92d4 Mon Sep 17 00:00:00 2001 From: komashchenko Date: Fri, 7 Aug 2026 20:48:44 +0300 Subject: [PATCH 1/4] Replace some gamedata lookups with VScript function bindings --- CS2Fixes.vcxproj | 3 +- CS2Fixes.vcxproj.filters | 5 +- gamedata/cs2fixes.jsonc | 42 --------- src/addresses.cpp | 26 +++++- src/addresses.h | 14 ++- src/cs2_sdk/entity/cbaseentity.h | 10 +-- src/cs2_sdk/entity/ccsplayercontroller.h | 3 +- src/cs2fixes.cpp | 6 ++ src/utils/script_function.h | 106 +++++++++++++++++++++++ src/utils/utils.cpp | 34 +++++++- src/utils/utils.h | 4 +- 11 files changed, 192 insertions(+), 61 deletions(-) create mode 100644 src/utils/script_function.h 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..26ed142e --- /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 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); From 4408a49bc321adf8adbb5f90013f2711b069676d Mon Sep 17 00:00:00 2001 From: komashchenko Date: Fri, 7 Aug 2026 22:23:18 +0300 Subject: [PATCH 2/4] Fix function pointer conversion --- src/utils/script_function.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/script_function.h b/src/utils/script_function.h index 26ed142e..73f97d88 100644 --- a/src/utils/script_function.h +++ b/src/utils/script_function.h @@ -78,7 +78,7 @@ class CScriptFunction return nullptr; #endif - return m_pFunction; + return reinterpret_cast(m_pFunction); } inline int GetOffset() const From 80832262195b4aa9743c3a20f82d566c05977487 Mon Sep 17 00:00:00 2001 From: komashchenko Date: Thu, 13 Aug 2026 19:17:04 +0300 Subject: [PATCH 3/4] Resolve Teleport and VScript bindings during plugin initialization --- AMBuilder | 1 + CS2Fixes.vcxproj | 3 +- CS2Fixes.vcxproj.filters | 5 +- gamedata/cs2fixes.jsonc | 5 - src/addresses.cpp | 53 ++++++---- src/addresses.h | 15 +-- src/cs2_sdk/entity/cbaseentity.h | 3 +- src/cs2fixes.cpp | 11 +- src/utils/script_function.cpp | 168 +++++++++++++++++++++++++++++++ src/utils/script_function.h | 36 +++---- src/utils/utils.cpp | 34 +------ src/utils/utils.h | 4 +- src/utils/virtual.h | 1 + 13 files changed, 246 insertions(+), 93 deletions(-) create mode 100644 src/utils/script_function.cpp diff --git a/AMBuilder b/AMBuilder index 033d025c..21856d09 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/script_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 e64a54be..d3ed45a3 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -221,6 +221,7 @@ + @@ -304,4 +305,4 @@ - + \ No newline at end of file diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index b59b89e5..8ccb3357 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -188,6 +188,9 @@ Source Files\utils + + Source Files\utils + Source Files\cs2_sdk\entity @@ -431,4 +434,4 @@ Header Files - + \ No newline at end of file diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc index a521bf67..5f19eb74 100644 --- a/gamedata/cs2fixes.jsonc +++ b/gamedata/cs2fixes.jsonc @@ -397,11 +397,6 @@ "windows": 150, "linux": 149 }, - "Teleport": - { - "windows": 163, - "linux": 162 - }, "CollisionRulesChanged": { "windows": 186, diff --git a/src/addresses.cpp b/src/addresses.cpp index 553db14d..52705234 100644 --- a/src/addresses.cpp +++ b/src/addresses.cpp @@ -21,6 +21,9 @@ #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,13 +32,13 @@ 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()); +#define RESOLVE_SF(scriptDesc, funcName, variable) \ + if (!variable.Initialize(GetScriptFunction(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) { @@ -107,17 +110,33 @@ bool addresses::InitializeBanMap(CGameConfig* g_GameConfig) return true; } -bool addresses::InitializeScriptFunctions() +bool addresses::InitializeVScriptFunctions() { - 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); - ) + 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()); + if (!pCBaseEntityScriptDesc) + { + Message("Failed to find CBaseEntity script description\n"); + return false; + } + + 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); + + if (!Teleport.Initialize(GetScriptFunction(pCBaseEntityScriptDesc, "SetOrigin"))) + return false; + Message("Found %s::Teleport through SetOrigin at vtable index %i\n", pCBaseEntityScriptDesc->m_pszClassname, Teleport.GetOffset()); return true; } diff --git a/src/addresses.h b/src/addresses.h index e112f24f..ea1e9cdd 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -75,7 +75,7 @@ namespace addresses { bool Initialize(CGameConfig* g_GameConfig); bool InitializeBanMap(CGameConfig* g_GameConfig); - bool InitializeScriptFunctions(); + bool InitializeVScriptFunctions(); inline CUtlMap* sm_mapGcBanInformation; @@ -108,10 +108,11 @@ namespace addresses inline void(FASTCALL* GetSpawnGroups)(CSpawnGroupMgrGameSystem* pSpawnGroupMgr, CUtlVector* pList); inline void(FASTCALL* CBasePlayerPawn_SnapViewAngles)(CBasePlayerPawn* pPawn, QAngle* pAngles); - inline CScriptFunction SetGravityScale; - inline CScriptFunction ScriptSetEntityName; - inline CScriptFunction ScriptEmitSoundParams; - inline CScriptFunction ChangeTeam; - inline CScriptFunction IsPlayerPawn; - inline CScriptFunction IsPlayerController; + inline CVScriptFunction SetGravityScale; + inline CVScriptFunction ScriptSetEntityName; + inline CVScriptFunction ScriptEmitSoundParams; + inline CVScriptFunction ChangeTeam; + inline CVScriptFunction IsPlayerPawn; + inline CVScriptFunction IsPlayerController; + inline CVScriptTeleportFunction Teleport; } // namespace addresses diff --git a/src/cs2_sdk/entity/cbaseentity.h b/src/cs2_sdk/entity/cbaseentity.h index 6d3b3a93..bcf7a63b 100644 --- a/src/cs2_sdk/entity/cbaseentity.h +++ b/src/cs2_sdk/entity/cbaseentity.h @@ -175,8 +175,7 @@ class CBaseEntity : public CEntityInstance void Teleport(const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity) { - static int offset = g_GameConfig->GetOffset("Teleport"); - CALL_VIRTUAL(void, offset, this, pPosition, pAngles, pVelocity); + addresses::Teleport(this, pPosition, pAngles, pVelocity); } void SetCollisionGroup(StandardCollisionGroups_t nCollisionGroup) diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 85c5e169..bc963fea 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; @@ -279,7 +282,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool SH_MANUALHOOK_RECONFIGURE(OnTakeDamage_Alive, offset, 0, 0); g_iOnTakeDamageAliveId = SH_ADD_MANUALDVPHOOK(OnTakeDamage_Alive, pCCSPlayerPawnVTable, SH_MEMBER(this, &CS2Fixes::Hook_OnTakeDamage_Alive), false); - offset = g_GameConfig->GetOffset("Teleport"); + offset = addresses::Teleport.GetOffset(); if (offset == -1) { snprintf(error, maxlen, "Failed to find Teleport\n"); @@ -388,9 +391,6 @@ 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(); @@ -617,9 +617,6 @@ 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.cpp b/src/utils/script_function.cpp new file mode 100644 index 00000000..85ec99e9 --- /dev/null +++ b/src/utils/script_function.cpp @@ -0,0 +1,168 @@ +/** + * ============================================================================= + * 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 "script_function.h" +#include "entitysystem.h" +#include "entityclass.h" + +void* GetScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName) +{ + if (!pScriptDesc) + return nullptr; + + // 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 (!functionBinding.m_desc.m_pszScriptName || V_strcmp(functionBinding.m_desc.m_pszScriptName, pszFuncName) != 0) + continue; + + return functionBinding.m_pFunction; + } + + return nullptr; +} + +int ExtractTeleportIndexFromSetOrigin(void* pSetOrigin) +{ + if (!pSetOrigin) + return -1; + + constexpr int MaxScanSize = 0x40; + + uint8* pCode = static_cast(pSetOrigin); + + for (int i = 0; i + 9 <= MaxScanSize; i++) + { + int p = i; + uint8 rex = 0; + + // Optional x86-64 REX prefix. + if ((pCode[p] & 0xF0) == 0x40) + rex = pCode[p++]; + + // Pattern #1: + // + // mov rax, [this] + // ... + // call [rax + offset] + // + // or: + // + // jmp [rax + offset] + // + // FF /2 = CALL + // FF /4 = JMP + if (pCode[p] == 0xFF) + { + uint8 modrm = pCode[p + 1]; + + int mod = modrm >> 6; + int op = (modrm >> 3) & 7; + + // mod == 2 means [register + disp32]. + // rm == 4 would require a SIB byte. + if (mod == 2 && (op == 2 || op == 4) && (modrm & 7) != 4) + { + int32 disp = *reinterpret_cast(pCode + p + 2); + + if (disp >= 0 && (disp & 7) == 0) + return disp >> 3; + } + } + + // Pattern #2: + // + // mov rax, [rax + offset] + // jmp rax + // + // or: + // + // mov rax, [rax + offset] + // call rax + // + // Require REX.W because we are loading a 64-bit function pointer. + if ((rex & 8) && pCode[p] == 0x8B) + { + uint8 modrm = pCode[p + 1]; + + int mod = modrm >> 6; + int dst = ((modrm >> 3) & 7) | ((rex & 4) ? 8 : 0); + int base = (modrm & 7) | ((rex & 1) ? 8 : 0); + + // mov reg, [same_reg + disp32] + if (mod == 2 && dst == base && (modrm & 7) != 4) + { + int32 disp = *reinterpret_cast(pCode + p + 2); + + if (disp < 0 || (disp & 7) != 0) + continue; + + // The MOV instruction is 7 bytes: + // REX + 8B + ModRM + disp32 + int next = i + 7; + + uint8 nextRex = 0; + + if ((pCode[next] & 0xF0) == 0x40) + nextRex = pCode[next++]; + + if (pCode[next] != 0xFF) + continue; + + uint8 nextModrm = pCode[next + 1]; + + int nextMod = nextModrm >> 6; + int nextOp = (nextModrm >> 3) & 7; + int nextReg = (nextModrm & 7) | ((nextRex & 1) ? 8 : 0); + + // mod == 3 means CALL/JMP directly through a register. + // + // FF /2 = CALL reg + // FF /4 = JMP reg + if (nextMod == 3 && (nextOp == 2 || nextOp == 4) && nextReg == dst) + return disp >> 3; + } + } + } + + return -1; +} + +bool CVScriptTeleportFunction::Initialize(void* pSetOrigin) +{ + int iOffset = ExtractTeleportIndexFromSetOrigin(pSetOrigin); + if (iOffset == -1) + return false; + + m_iOffset = iOffset; + m_bVirtual = true; + + return true; +} diff --git a/src/utils/script_function.h b/src/utils/script_function.h index 73f97d88..544a387c 100644 --- a/src/utils/script_function.h +++ b/src/utils/script_function.h @@ -22,10 +22,20 @@ #include "platform.h" #include "virtual.h" +struct ScriptClassDesc_t; +class CBaseEntity; +class Vector; +class QAngle; + +void* GetScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName); + template -class CScriptFunction +class CVScriptFunction { public: + CVScriptFunction() : + m_pFunction(nullptr), m_bVirtual(false) { } + inline bool Initialize(void* pFunction) { if (!pFunction) @@ -50,57 +60,49 @@ class CScriptFunction 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: +protected: union { Ret(FASTCALL* m_pFunction)(Class*, Args...); -#ifdef PLATFORM_LINUX int m_iOffset; -#endif }; -#ifdef PLATFORM_LINUX - bool m_bVirtual = false; -#endif + bool m_bVirtual; +}; + +class CVScriptTeleportFunction final : public CVScriptFunction +{ +public: + bool Initialize(void* pSetOrigin); }; diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 6d5f3ba8..b2114219 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -20,8 +20,6 @@ #include "utils.h" #include "../cs2fixes.h" #include "../gameconfig.h" -#include "entitysystem.h" -#include "entityclass.h" void Message(const char* msg, ...) { @@ -95,34 +93,4 @@ ISteamHTTP* GetSteamHTTP() return SteamGameServerHTTP(); else return SteamHTTP(); -} - -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; -} +} \ No newline at end of file diff --git a/src/utils/utils.h b/src/utils/utils.h index 8b2612e9..24d16c66 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -36,6 +36,4 @@ uint32 GetSoundEventHash(const char* pszSoundEventName); std::string StringToLower(std::string strValue); ISteamUGC* GetSteamUGC(); -ISteamHTTP* GetSteamHTTP(); - -void* GetScriptFunction(const char* pszClassName, const char* pszFuncName); +ISteamHTTP* GetSteamHTTP(); \ No newline at end of file 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__) From 4a6eed317927024f5a33950e4b9701a2bf986e06 Mon Sep 17 00:00:00 2001 From: komashchenko Date: Thu, 13 Aug 2026 22:58:11 +0300 Subject: [PATCH 4/4] Remove redundant checks --- src/addresses.cpp | 5 ----- src/utils/script_function.cpp | 5 +---- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/addresses.cpp b/src/addresses.cpp index 52705234..83493cfc 100644 --- a/src/addresses.cpp +++ b/src/addresses.cpp @@ -121,11 +121,6 @@ bool addresses::InitializeVScriptFunctions() // GetScriptDesc ignores this, so the vtable pointer is sufficient here. ScriptClassDesc_t* pCBaseEntityScriptDesc = reinterpret_cast(reinterpret_cast(&pCBaseEntityVTable)->GetScriptDesc()); - if (!pCBaseEntityScriptDesc) - { - Message("Failed to find CBaseEntity script description\n"); - return false; - } RESOLVE_SF(pCBaseEntityScriptDesc, "SetGravity", SetGravityScale); RESOLVE_SF(pCBaseEntityScriptDesc, "SetEntityName", ScriptSetEntityName); diff --git a/src/utils/script_function.cpp b/src/utils/script_function.cpp index 85ec99e9..208603cb 100644 --- a/src/utils/script_function.cpp +++ b/src/utils/script_function.cpp @@ -23,9 +23,6 @@ void* GetScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName) { - if (!pScriptDesc) - return nullptr; - // https://github.com/Wend4r/sourcesdk/blob/758e43823f02fcd40498d33a42cd93243258fe1e/public/vscript/ivscript.h#L280 struct ScriptFunctionBindingCurrent_t { @@ -40,7 +37,7 @@ void* GetScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName) FOR_EACH_VEC(functionBindings, i) { auto& functionBinding = functionBindings.Element(i); - if (!functionBinding.m_desc.m_pszScriptName || V_strcmp(functionBinding.m_desc.m_pszScriptName, pszFuncName) != 0) + if (V_strcmp(functionBinding.m_desc.m_pszScriptName, pszFuncName) != 0) continue; return functionBinding.m_pFunction;