View Single Post
SourceMod Plugin Approver
Join Date: Aug 2008
Location: Germany
Old 04-21-2018 , 06:58   Re: DHooks (Dynamic Hooks - Dev Preview)
Reply With Quote #589

DHooks is now included with SourceMod 1.11.6820. Previous versions are unsupported.

Please use SourceMod's issue tracker on GitHub for any new issues.

Experimental dynamic detour support

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.


Replicate PTaH's detour on IClient::ExecuteStringCommand: (why is this a detour instead of a vtable hook again?)

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.

Instead of
PHP Code:
Handle hGameData LoadGameConfigFile("");
hGameData == null)
SetFailState("Failed to load sdktools gamedata.");

int iOffset GameConfGetOffset(hGameData"Teleport");
delete hGameData;
iOffset == -1)
SetFailState("Failed to find Teleport offset in gamedata");

g_hTeleport DHookCreate(iOffsetHookType_EntityReturnType_VoidThisPointer_CBaseEntityDHooks_OnTeleport);
g_hTeleport == null)
SetFailState("Failed to setup hook for Teleport"); 
you can grab the offset and pass it to the hook in one go:

PHP Code:
Handle hGameData LoadGameConfigFile("");
hGameData == null)
SetFailState("Failed to load sdktools gamedata.");
g_hTeleport DHookCreate(0HookType_EntityReturnType_VoidThisPointer_CBaseEntityDHooks_OnTeleport);
g_hTeleport == null)
SetFailState("Failed to setup hook for Teleport");

if (!
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.


You can detour CCSGameRules::TerminateRound (like the cstrike extension does) like this:

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


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.
    • 8-bit General purpose registers: al, cl, dl, bl, ah, ch, dh, bh
    • 16-bit General purpose registers: ax, cx, dx, bx, sp, bp, si, di
    • 32-bit General purpose registers: eax, ecx, edx, ebx, esp, ebp, esi, edi
    • 64-bit MM (MMX) registers: mm0, mm1, mm2, mm3, mm4, mm5, mm6, mm7
    • 128-bit XMM registers: xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7
    • 16-bit Segment registers: cs, ss, ds, es, fs, gs
    • 80-bit FPU registers: st0
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:
        "type"  "float"
            "register"  "xmm1"
        "type"    "int"
The TerminateRound detour can be rewritten like this:


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
PHP Code:
void CBaseAnimating::Teleport( const Vector  *newPosition, const QAngle *newAngles, const Vector *newVelocity 
CS:GO added another parameter though
PHP Code:
void  CBaseAnimating::Teleport( const Vector *newPosition, const QAngle  *newAngles, const Vector *newVelocitybool notsolid 

Hardcoding the hook it could look something like this:

You can move that setup into your gamedata file like this:

Later sections in the file add to the already parsed setup.

Ayuto - DynamicHooks library
BAILOPAN - Help with assembly and converting DynamicHooks to SourcePawn's MacroAssembler
Dr!fter - DHooks!

Old repository

Note: To build yourself, you'll need to update amtl in sourcemod 1.9 to include this PR. SourceMod 1.10+ already includes the change.
~/sourcemod> cd public/amtl
~/sourcemod/public/amtl>git checkout ad5439b
No Mac or x64 support yet!

2.2.0-detours1 - 21.04.2018:
  • Initial release
2.2.0-detours2 - 22.04.2018:
  • Fix overriding function return value (thanks SlidyBat)
2.2.0-detours3 - 20.05.2018:
  • Don't call sourcepawn callbacks when detour function is called from a different thread
  • Fix setting up virtual hooks through "Functions" section in gamedata
2.2.0-detours4 - 06.06.2018:
  • Fix detouring |thiscall| functions with additional parameters on linux (thanks Ryan.)
2.2.0-detours5 - 08.08.2018:
  • Fix adding arguments multiple times when reparsing the "Functions" section (thanks Silvers)
  • Fix not removing all plugin callbacks from detour list on plugin unload
2.2.0-detours6 - 19.09.2018:
  • Fix detouring functions returning a float
2.2.0-detours7 - 21.11.2018:
  • Fix crash on unaligned SSE instructions (thanks Kailo)
  • Fix crash when trying to decode NULL this-pointer
2.2.0-detours8 - 03.08.2019:
  • Add |fastcall| calling convention support
  • Fix changing of charptr and vectorptr returns and parameters (thanks Silvers)
2.2.0-detours9 - 19.08.2019
  • Pull fix for changing vector returns from upstream
2.2.0-detours10 - 21.04.2020
  • Add |this|-ptr save and restore from pre to post hooks on linux
  • Fix parsing of platform specific sections in "Functions" gamedata (thanks vanz)
2.2.0-detours11 - 28.05.2020
  • Fix recursive calls when the ESP register is reused (thanks L'In20Cible Ayuto/DynamicHooks#4)
  • Fix skipping and overriding return values of recursive calls (thanks ldesgoui #3)
2.2.0-detours12 - 29.05.2020
  • Fix typo preventing "odtor" argument flag parsing in gamedata (thanks Alienmario)
2.2.0-detours13 - 30.05.2020
  • Fix parsing of multiple argument flags in gamedata (thanks Alienmario)
2.2.0-detours14 - 09.06.2020
  • Fix regression crash with post-only detours from recursive call fix in detours11 (Thanks Crasher_3637)
  • Include in include file for DHookSetFromConf native (Thanks ClaudiuHKS #4)
  • Fix crash when trying to call hook removal callback on unloaded plugin (Thanks FortyTwoFortyTwo Drifter321#3)
2.2.0-detours14a - 09.06.2020
  • More regression fixes for post-only detours (thanks Silvers)
2.2.0-detours15 - 17.10.2020
  • Add methodmap API
  • Save all arguments before calling the original function (thanks nosoop)
  • Fix regression when unloading plugins with entity vhooks on mapchange leading to a crash
  • Print nicer error message on wrong callback signature
2.2.0-detours16 - 28.01.2021
  • Fix crash on server shutdown
  • Switch to std::vector and std::string for SourceMod 1.11 / AMTL update compatibility
  • Enable frame pointer on linux for better crash stack traces
  • Include:
    • Fix INVALID_HOOK_ID using incorrect value
    • Avoid reference to GameData methodmap for backwards compatibility
2.2.0-detours17 - 30.06.2021
  • Allow returning NULL for CBaseEntity return types by setting the return value to -1 (thanks arthurdead)
  • Allow specifying object argument size in hexadecimal in gamedata similar to SM 1.11
  • Fix SourceHook context leak when superceding virtual hooked functions (thanks nosoop and BotoX Drifter321#2)
    • This fixes a long standing issue with memory leaks, crashes and lag on map change
  • Fix crash on CentOS7+ / SELinux with "allow_execheap" disabled (thanks vintagepc #22)
  • Fix DHookIsNullParam checking wrong parameter for detour setups with parameters passed in custom registers (thanks GAMMACASE #15)
  • Fix crash due to mismatching new[] + delete[] pair of return value buffer (thanks bottiger)
1.11.6820 - 17.11.2021:
  • DHooks is now included with SourceMod 1.11.6820
  • Added new native "DHookParam.GetAddress" to get the address of a pointer parameter. (Thanks LuqS #24)
  • Fix error reporting on invalid entity (Thanks FortyTwoFortyTwo #25)

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 * if you run SourceMod 1.10+.
Attached Files
File Type: zip (619.6 KB, 24855 views)
File Type: zip (613.3 KB, 5248 views)

Last edited by Peace-Maker; 11-17-2021 at 08:39. Reason: Announce merge into SourceMod
Peace-Maker is offline