SourceHook

SourceMod will contain something like MetaMod. We have decided to call it “SourceHook” (and not SMMeta as initially planned by one of our devlopers that can’t think of good names < -- me).
SourceHook is able to hook virtual functions from interfaces. I wanted to make adding support for new interfaces easy, so each interface has its own header file, like this one for IVEngineServer. All supported interface header files are included in sh_supportedinterfaces.h.

So, as you can probably guess, SourceHook #defines things SH_FUNCDECL_BEGIN, SH_FUNCDECL_END, and SH_FUNCDECL4_void to some stuff, and then includes sh_supportedinterfaces.h. Actually, that file is included many times. For each interface, SourceHook generates a lot of stuff. Most shint_*.h header files define the SH_FUNCECL* macros to something, then include the interface headers. The things that are generated are things like a vtable struct, the hook functions, typedefs, the hook lists, a registration interface, etc. For each parameter count, there are 4 versions of the macros. Here are some examples:

SH_FUNCDECL2_void(function, attributes, parameter1_type, parameter2_type)
The only not self-explaining thing here is attributes, which can be const, or SH_NOATTRIB.

SH_FUNCDECL2(function, attributes, return_type, parameter1_type, parameter2_type)

SH_FUNCDECL_vafmt2_void(function, attributes, parameter1_type, parameter2_type)
This declares a function of the form: function(parameter1_type p1, parameter2_type p2, const char *format, …)
In other words, this is used for functions like ClientCommand, which use printf-style formatting.

SH_FUNCDECL_vafmt2(function, attributes, return_type, parameter1_type, parameter2_type)

Writing all these macros for all parameter counts would be a pain, so I have created a small utility called shworker, which generates all the macros from templates like this.

Note that SourceHook was only designed to hook interfaces that are publicly exposed by the engine or the gamedll; That is, you can not (yet?) hook things like the Dispatch() function from Jeff’s ConCommandBase pointer.

Ok, how to use this!?
You can hook functions to:

  1. Global functions / Static member functions:
    SH_HOOK_STATICFUNC(SourceMod's GetInterface function, Interface, Function, YourFunction, Post)
    
    
      
    • SourceMod’s GetInterface function:
      If you are in core, this is the global GetInterface function. If you are in a module, this is the GetInterface function pointer SourceMod passed to some init function.
    • Interface:
      The interface you want to hook
    • Function:
      The function you want to hook
    • YourFunction:
      Your hook function
    • Post: Set this to true if you want to make a post hook, to false otherwise

    For example, you can do something like this:

    void MyChangeLevelFunction(const char *s1, const char *s2)
    {
       LOG_SOMEHOW("ChangeLevel pre-hook called; Superceding! s1=%s, s2=%s", s1, s2);
       RETURN_META(MRES_SUPERCEDE);
    }
    
    ... (in some init function)
    SH_HOOK_STATICFUNC(GetInterface, IVEngineServer, ChangeLevel, MyChangeLevelFunction, false);
      

    Yeah, you can use the old meta macros in your hooks. Note that MRES_UNSET has been removed; not providing a meta result means the same as providing MRES_CONTINUE.

  2. Non-static member function:
    SH_HOOK_MEMBERFUNC(SourceMod's GetInterface function, Interface, Function, YourInstance, YourFunction, Post)
      
    • SourceMod’s GetInterface function:
      If you are in core, this is the global GetInterface function. If you are in a module, this is the GetInterface function pointer SourceMod passed to some init function.
    • Interface:
      The interface you want to hook
    • Function:
      The function you want to hook
    • YourInstance:
      A pointer to the instance of your class
    • YourFunction:
      Your hook function
    • Post: Set this to true if you want to make a post hook, to false otherwise

    Example:

    class MyTestClass
    {
    public:
       void MyChangeLevelFunction(const char *s1, const char *s2)
       {
          LOG_SOMEHOW("ChangeLevel pre-hook called; Superceding! s1=%s, s2=%s", s1, s2);
          RETURN_META(MRES_SUPERCEDE);
       }
    };
    
    MyTestClass g_TestClass;
    
    ... (in some init function)
    SH_HOOK_MEMBERFUNC(GetInterface, IVEngineServer, ChangeLevel, &g_Test, &MyTestClass::MyChangeLevelFunction, false);
      

Of course you can also remove hooks:

SH_REMOVE_STATICFUNC(SM's GetInterface function, interface, interface function, your function)
SH_REMOVE_MEMBERFUNC(SM's GetInterface function, interface, interface function, your instance, your function)

Note that these remove both pre- and post-hooks.

If a function is hooked, the hook always gets called, no matter what calls the function. That’s why you should not use the interface pointers provided by engine/gamedll. Instead, get your interface pointers using:

SH_GET_CALLCLASS(SM's GetInterface function, interface, is_gamedll_interface?, interface version)

the third and fourth parameters are used if SourceHook doesn’t support the interface – then, the engine’s / gamedll’s interface factory is used.
ie.

IVEngineServer *engine = SH_GET_CALLCLASS(GetInterface, IVEngineServer, false /*engine*/, INTERFACEVERSION_VENGINESERVER);
engine->ChangeLevel("xxx", "yyy");  // Calls engine's function directly, skips hooks

If you want to use SourceHook from modules, you will have to define the global g_SHPtr variable, and set it to GetInterface(SOURCEHOOKINTERFACE_NAME). For example:

#include "sourcehook/sourcehook.h"

IVSourceHook *g_SHPtr;
...
Init-Function:
g_SHPtr = GetInterface(SOURCEHOOKINTERFACE_NAME); // GetInterface is SM's GetInterface function

This pointer provides functions for hooks (=> you will be able to set up hooks without it, but you will not be able to use the metamod-style macros in them).

I’ve listed all macros you can use in hooks here:

	SET_META_RESULT(meta_result)
		Sets the meta result to meta_result, does not return
	RETURN_META(meta_result)
		Sets the meta result to meta_result, then returns
	RETURN_META_VALUE(meta_result, value)
		Sets the meta result to meta_result, then returns value.
	META_RESULT_STATUS
		Contains the highet meta result that was returned by now
	META_RESULT_PREVIOUS
		Contains the previous hook's meta result
	META_RESULT_ORIG_RET
		In post functions: Contains the original function's return value
				(or, if a hook specified MRES_SUPERCEDE, its return value)
		In pre functions: Undefined
	META_RESULT_OVERRIDE_RET
		Contains the override value (if any)

By the way, hooking of non-static member functions is accomplished using Don Clugston’s excellent FastDelegate class.

UPDATED Feb 03:
- Actually implemented the remove hook macros
- Changed get-call-class macros a bit

One Response to “SourceHook”

  1. BAILOPAN says:

    This rocks out a lot. Great work PM, and of course lance and XAD :)

Leave a Reply

You must be logged in to post a comment.