Hi
Is there any way for making this work on cs:s v34?
Help pleaSE!
This should already support the latest version of CS:S.
If you're not using the latest version of CS:S maybe it's time to connect to steam and update to the latest version?
Hi
Is there any way for making this work on cs:s v34?
Help pleaSE!
Heck, SourceMod itself doesn't support v34. It's missing interfaces, many features used in current SM and more importantly - lots of code isn't even compatible. Same goes for gamedata.
I myself tried v34 CS:S to see what the hype is about, but it ended up being a buggy, unoptimized mess that has nothing but Russian servers with some version of SM from 7 years ago and lots of cheaters.
Just get it on Steam when Christmas sales happen. It'll be dirt cheap.
I've been fiddling with extending DHooks to not only support hooking of virtual functions, but non-virtual functions as well. The dynamic detour support in this version is based on Ayuto's DynamicHooks library. Huge thank you for that nice piece of engineering!
This build adds support for detouring any functions through SourcePawn.
Detours in plugins Setting up detours in a plugin is very similar to current virtual hooks. Instead of DHookCreate you'd use DHookCreateDetour. This time you'll pass the address to the function you want to detour instead of the vtable offset though and specify the calling convention the function uses.
Instead of hooking single entities you'll just enable the detour globally (DHookEnableDetour). Your detour is disabled automatically on plugin unload.
/** * Creates a detour * * @param funcaddr The address of the function to detour. * Can be Address_Null if you want to load the address from gamedata using DHookSetFromConf. * @param callConv Calling convention of the function. * @param returnType Type of the return value. * @param thisType Type of this pointer or ignore (ignore can be used if not needed) * * @return Setup handle for the detour. * @error Failed to create detour setup handle. */ native Handle DHookCreateDetour(Address funcaddr, CallingConvention callConv, ReturnType returntype, ThisPointerType thisType);
/** * Load details for a vhook or detour from a gamedata file. * * @param setup Hook setup handle to set the offset or address on. * @param gameconf GameConfig handle * @param source Whether to look in Offsets or Signatures. * @param name Name of the property to find. * * @return True on success, false if nothing was found. * @error Invalid setup or gamedata handle. */ native bool DHookSetFromConf(Handle setup, Handle gameconf, SDKFuncConfSource source, const char[] name);
/** * Enable the detour of the function described in the hook setup handle. * * @param setup Hook setup handle * @param post True to make the hook a post hook. (If you need to change the retunr value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) * @param callback Callback function * * @return True if detour was enabled, false otherwise. * @error Hook handle is not setup for a detour. */ native bool DHookEnableDetour(Handle setup, bool post, DHookCallback callback);
/** * Disable the detour of the function described in the hook setup handle. * * @param setup Hook setup handle * @param post True to disable a post hook. * @param callback Callback function * * @return True if detour was disabled, false otherwise. * @error Hook handle is not setup for a detour or function is not detoured. */ native bool DHookDisableDetour(Handle setup, bool post, DHookCallback callback);
Examples Replicate PTaH's detour on IClient::ExecuteStringCommand: (why is this a detour instead of a vtable hook again?)
Spoiler
PHP Code:
#include <sdktools> #include <dhooks>
// Hardcode offset for demonstration. // IClient::GetPlayerSlot() in CS:GO // windows #define GetPlayerSlotOffs 16
// Setup detour on IClient::ExecuteStringCommand. hExecuteStringCommand = DHookCreateDetour(Address_Null, CallConv_THISCALL, ReturnType_Bool, ThisPointer_Address); if (!hExecuteStringCommand) SetFailState("Failed to setup detour for ExecuteStringCommand");
// Load the address of the function from PTaH's signature gamedata file. if (!DHookSetFromConf(hExecuteStringCommand, hGameData, SDKConf_Signature, "ExecuteStringCommand")) SetFailState("Failed to load ExecuteStringCommand signature from gamedata");
// Add all parameters. DHookAddParam(hExecuteStringCommand, HookParamType_CharPtr);
// Add a pre hook on the function. if (!DHookEnableDetour(hExecuteStringCommand, false, Detour_OnExecuteStringCommand)) SetFailState("Failed to detour ExecuteStringCommand.");
// And a post hook. if (!DHookEnableDetour(hExecuteStringCommand, true, Detour_OnExecuteStringCommand_Post)) SetFailState("Failed to detour ExecuteStringCommand post.");
// Setup quick hack to get the client index of the IClient this pointer in the detour callback. StartPrepSDKCall(SDKCall_Raw); PrepSDKCall_SetVirtual(GetPlayerSlotOffs); PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); hGetPlayerSlot = EndPrepSDKCall(); }
public MRESReturn Detour_OnExecuteStringCommand(Address pThis, Handle hReturn, Handle hParams) { int client = SDKCall(hGetPlayerSlot, pThis) + 1;
char sBuffer[512]; DHookGetParamString(hParams, 1, sBuffer, sizeof(sBuffer)); PrintToServer("ExecuteStringCommand called on client %d: %s", client, sBuffer); return MRES_Ignored; }
public MRESReturn Detour_OnExecuteStringCommand_Post(Address pThis, Handle hReturn, Handle hParams) { int client = SDKCall(hGetPlayerSlot, pThis) + 1;
char sBuffer[512]; DHookGetParamString(hParams, 1, sBuffer, sizeof(sBuffer)); PrintToServer("ExecuteStringCommand post called on client %d (ret %d): %s", client, DHookGetReturn(hReturn), sBuffer); }
In the above example you'll notice the use of the new DHookSetFromConf native to simplify hook setup similar to how SDKCalls in SDKTools are created.
g_hTeleport = DHookCreate(0, HookType_Entity, ReturnType_Void, ThisPointer_CBaseEntity, DHooks_OnTeleport); if(g_hTeleport == null) SetFailState("Failed to setup hook for Teleport");
if (!DHookSetFromConf(g_hTeleport, hGameData, SDKConf_Virtual, "Teleport")) SetFailState("Failed to load Teleport offset from gamedata"); delete hGameData;
This allows you to use the "Signatures" section in gamedata directly without the need to wrap it in an "Address" for use with the GameConfGetAddress native for detours.
Detouring member functions
Member functions usually need a pointer to the instance of their class to access the right member variables and stuff. On Windows there's a special calling convention thiscall which specifies that the this-pointer should be passed in the ecx register. On linux the this-pointer is passed as an implicit first argument on the stack before all the other arguments using the cdecl calling convention.
To save you from having to configure the hook differently on linux and windows the thiscall calling convention actually means "cdecl with implicit first |this| argument" on linux. So you don't have to add another parameter yourself when detouring on linux, but can just use CallConv_THISCALL and use the this-pointer just like you would with virtual hooks (e.g. passing ThisPointer_Address to DHookCreateDetour and choosing the right callback prototype including the Address pThis parameter).
Accessing parameter values in post-hooks Some compiler optimizations can cause the memory locations of the arguments to be reused and overwritten in the function if the compiler comes to the conclusion that the argument isn't needed anymore (mostly used on Windows). This can cause problems in post-hooks, because the real argument values aren't available anymore and you find "garbage" values instead of the expected arguments. Some pointer types can even cause a crash when DHooks tries to dereference them and the value doesn't point to valid memory anymore. If you experience problems like this, the current workaround would be to save the arguments in a pre-hook and use them in the post-hook instead of calling DHooksGetParam*.
Parameter values are saved before calling the original function and restored before the post callback since detours15.
Custom calling conventions and LTCG Some functions can be optimized to expect arguments in random registers instead of the stack. This optimization can save stack operations since the callee just expects the argument to be in the same register the caller already uses to store the value. The caller doesn't have to push the argument on the stack, but can just keep it in the register. You'll often notice this in the CS:GO binaries on windows.
Since the used register can be arbitary and the compiler doesn't have to follow any standardized calling conventions for functions that aren't called externally, the DHookAddParam native received a new parameter to define the register the parameter is passed in.
Spoiler
PHP Code:
enum DHookRegister { // Don't change the register and use the default for the calling convention. DHookRegister_Default,
/* Adds param to a hook setup * * @param setup Setup handle to add the param to. * @param type Param type * @param size Used for Objects (not Object ptr) to define the size of the object. * @param flag Used to change the pass type. * @param custom_register The register this argument is passed in instead of the stack. * * @error Invalid setup handle or too many params added (request upping the max in thread) * @noreturn */ native void DHookAddParam(Handle setup, HookParamType type, int size=-1, DHookPassFlag flag=DHookPass_ByVal, DHookRegister custom_register=DHookRegister_Default);
You can detour CCSGameRules::TerminateRound (like the cstrike extension does) like this:
Spoiler
PHP Code:
#include <sdktools> #include <dhooks>
Handle hTerminateRoundDetour;
public void OnPluginStart() { Handle hGameData = LoadGameConfigFile("sm-cstrike.games"); if (!hGameData) { SetFailState("Failed to load sm-cstrike gamedata."); return; }
hTerminateRoundDetour = DHookCreateDetour(Address_Null, CallConv_THISCALL, ReturnType_Void, ThisPointer_Ignore); if (!hTerminateRoundDetour) SetFailState("Failed to setup detour for TerminateRound");
if (!DHookSetFromConf(hTerminateRoundDetour, hGameData, SDKConf_Signature, "TerminateRound")) SetFailState("Failed to load TerminateRound signature from gamedata"); delete hGameData;
public MRESReturn Detour_OnTerminateRound_Post(Handle hParams) { PrintToServer("TerminateRound post called. delay %f, reason %d", DHookGetParam(hParams, 1), DHookGetParam(hParams, 2)); return MRES_Ignored; }
This works on windows, since the first float parameter is passed in register xmm1 (the this-pointer is passed in ecx by default, so no action needed for that):
PHP Code:
char __userpurge TerminateRound(int a1@<ecx>, float a2@<xmm1>, int *a3)
That optimization isn't used in the linux build though, so we'd need a way to define different function signatures per platform. That's why there's a new "Functions" section in gamedata files where you can define all the above including parts only valid on single platforms.
"Functions" in gamedata You can define function signatures in this new section in gamedata files parsed by DHooks2 now.
Since we don't know which gamedata file is being parsed when looking at the "Functions" section, all functions are cached globally. So the "Functions" section is parsed automatically when loading gamedata files using LoadGameConfigFile (or through an extension) without any additional action needed.
First you create a new subsection preferably named after the function you want to hook/detour.
The section name is used in the new DHookCreateFromConf native.
DHookCreateFromConf native
Spoiler
PHP Code:
/** * Setup a detour or hook for a function as described in a "Functions" section in gamedata. * * @param gameconf GameConfig handle * @param name Name of the function in the gamedata to load. * * @return Setup handle for the detour or INVALID_HANDLE if offset/signature/address wasn't found. * @error Failed to create detour setup handle, invalid gamedata handle, invalid callback function or failed to find function in gamedata. */ native Handle DHookCreateFromConf(Handle gameconf, const char[] name);
Required key values
Then you reference an "offset", "signature" or "address" section from the gamedata file who's handle will be passed to DHookCreateFromConf. Depending on if you specify an offset or not you'll setup a virtual hook or a detour.
If you setup a detour set the calling convention in the "callconv" key: cdecl, thiscall, stdcall or fastcall.
If you setup a virtual hook set the hook type in the "hooktype" key: entity, gamerules or raw. This corresponds to the HookType enum in the include.
If you setup a virtual hook or a detour using the thiscall calling convention decide what to do with the this-pointer in the "this" key: ignore, entity, address. This corresponds to the ThisPointerType enum in the include.
Lastly you have to specify the return type of the function in the "return" key. The ReturnType enum values are mapped to the lowercase version of the enum attributes.
ReturnType_Void: "void"
ReturnType_Int: "int"
ReturnType_Bool: "bool"
ReturnType_Float: "float"
ReturnType_String: "string"
ReturnType_StringPtr: "stringptr"
ReturnType_CharPtr: "charptr"
ReturnType_Vector: "vector"
ReturnType_VectorPtr: "vectorptr"
ReturnType_CBaseEntity: "cbaseentity"
ReturnType_Edict: "edict"
Defining function arguments
If your function has some arguments list them in an "arguments" subsection. Each argument will get its own subsection. The argument section name isn't used anywhere yet, but it might be useful to name the section after the argument it describes ;)
There is only one required key value for an argument just like with DHookAddParam: the "type". The values are derived from the HookParamType enum again just like the ReturnType above by using the latter part in lowercase.
HookParamType_Int: "int"
HookParamType_Bool: "bool"
HookParamType_Float: "float"
HookParamType_String: "string"
HookParamType_StringPtr: "stringptr"
HookParamType_CharPtr: "charptr"
HookParamType_VectorPtr: "vectorptr"
HookParamType_CBaseEntity: "cbaseentity"
HookParamType_ObjectPtr: "objectptr"
HookParamType_Edict: "edict"
HookParamType_Object: "object"
Optionally you can add the following keys to further specify the argument:
"size" - Used for Objects (not Object ptr) to define the size of the object.
"flags" - Used to change the pass type. The accepted values map to the PASSFLAG_* defines. You can put multiple flags in one line.
PASSFLAG_BYVAL: "byval" (default)
PASSFLAG_BYREF: "byref"
PASSFLAG_ODTOR: "odtor"
PASSFLAG_OCTOR: "octor"
PASSFLAG_OASSIGNOP: "oassignop"
PASSFLAG_OCOPYCTOR: "ocopyctor"
PASSFLAG_OUNALIGN: "ounalign"
"register" - The register this argument is passed in instead of the stack.
Platform specific settings Every subsection or keyvalue inside the "Functions" section can be enclosed by a "windows", "linux" or "mac" section to make the content only valid if the file is parsed on that platform. For the above TerminateRound detour example, you could specify the first parameter to be passed in the custom register only on windows like:
public void OnPluginStart() { Handle hGameData = LoadGameConfigFile("sm-cstrike.games"); if (!hGameData) { SetFailState("Failed to load sm-cstrike gamedata."); return; }
// Load the gamedata file briefly, we only want to cache the "Functions" section. Handle hFunctionSigs = LoadGameConfigFile("function-def.games"); if (!hFunctionSigs) SetFailState("Failed to load function-def gamedata"); delete hFunctionSigs;
// Create a detour as described in the "TerminateRound" functions section in function-def.games. // Use the sm-cstrike gamedata handle to lookup the signature. hTerminateRoundDetour = DHookCreateFromConf(hGameData, "TerminateRound"); if (!hTerminateRoundDetour) SetFailState("Failed to setup detour for TerminateRound"); delete hGameData;
if (!DHookEnableDetour(hTerminateRoundDetour, false, Detour_OnTerminateRound)) SetFailState("Failed to detour TerminateRound.");
if (!DHookEnableDetour(hTerminateRoundDetour, true, Detour_OnTerminateRound_Post)) SetFailState("Failed to detour TerminateRound post.");
public MRESReturn Detour_OnTerminateRound_Post(Handle hParams) { PrintToServer("TerminateRound post called. delay %f, reason %d", DHookGetParam(hParams, 1), DHookGetParam(hParams, 2)); return MRES_Ignored; }
Game specific settings
You can change settings or add parameters for single games/engines as well. Imagine we'd want to hook CBaseAnimating::Teleport. In most games, the signature is
Attention: Make sure to download the right file based on the SourceMod version installed on your server. SourceMod 1.10+ had a breaking change in the IBinTools interface and required a recompiled build. Use the *-sm110.zip if you run SourceMod 1.10+.