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