Difference between revisions of "SourceHook Development"

From AlliedModders Wiki
Jump to: navigation, search
m (removed references to SH_REMOVE_blah)
m (fixed up for v5 stuff)
Line 1: Line 1:
SourceHook is a powerful API (Application Programming Interface) for detouring virtual functions.  Unlike static detours, SourceHook needs only to swap addresses in and out of an object's virtual table.  This makes it fast and generally very platform-safe.
+
SourceHook is a powerful API (Application Programming Interface) for detouring (hooking) virtual functions.  Unlike static detours, SourceHook needs only to swap addresses in and out of an object's virtual table.  This makes it fast and generally very platform-safe.
  
 
SourceHook is coupled with Don Clugston's [http://www.codeproject.com/cpp/FastDelegate.asp FastDelegate] headers.  Virtual hooks can be detoured to any static function of the same prototype, or any member function (of any class) as long as the prototype matches.
 
SourceHook is coupled with Don Clugston's [http://www.codeproject.com/cpp/FastDelegate.asp FastDelegate] headers.  Virtual hooks can be detoured to any static function of the same prototype, or any member function (of any class) as long as the prototype matches.
Line 69: Line 69:
 
*<tt>RETURN_META_VALUE</tt> - Only usable from non-<tt>void</tt> functions.  Signals the action to take, then returns the supplied value.
 
*<tt>RETURN_META_VALUE</tt> - Only usable from non-<tt>void</tt> functions.  Signals the action to take, then returns the supplied value.
  
There are two methods of adding or removing hooks.  Hooks can be bound to '''static''' or '''member''' functions.  Both have a similar syntax.
+
There are two methods of adding or removing hooks.  Hooks can be bound to '''static''' or '''member''' functions.  Both have a similar syntax.  Their macros are:
 +
*<tt>SH_STATIC(Function)</tt> - Hook to a static function.
 +
*<tt>SH_MEMBER(Instance, Function)</tt> - Hook to a member function.
  
 
<b>It is important to realize that a simple hook will only be invoked when used on the same instance.</b>  That is to say, if there are 500 instances of object X, and a hook is added to instance #8 to &X::Y, then the hook will only be invoked from instance #8.  Multiple hooks can be declared on the same instance, and multiple instances can be bound to the same hook.
 
<b>It is important to realize that a simple hook will only be invoked when used on the same instance.</b>  That is to say, if there are 500 instances of object X, and a hook is added to instance #8 to &X::Y, then the hook will only be invoked from instance #8.  Multiple hooks can be declared on the same instance, and multiple instances can be bound to the same hook.
Line 76: Line 78:
  
 
==Adding Static Hooks==
 
==Adding Static Hooks==
The static binding macro is <tt>SH_ADD_HOOK_STATICFUNC</tt>.  The syntax is:
+
The static binding macro is <tt>SH_[ADD|REMOVE]_HOOK</tt>.  The syntax is:
  
<cpp>SH_ADD_HOOK_STATICFUNC(Interface, Function, Instance, Hook, [post? true,false])</cpp>
+
<cpp>SH_[ADD|REMOVE]_HOOK(Interface, Function, Instance, Hook, [post? true,false])</cpp>
  
 
An example of adding a <tt>LogPrint</tt> hook:
 
An example of adding a <tt>LogPrint</tt> hook:
<cpp>HookId log_hook = 0;
+
<cpp>void Hook_LogPrint(const char *msg)
void Hook_LogPrint(const char *msg)
 
 
{
 
{
 
   if (strcmp(msg, "If this string matches the function will be blocked") == 0)
 
   if (strcmp(msg, "If this string matches the function will be blocked") == 0)
Line 95: Line 96:
 
void StartHooks()
 
void StartHooks()
 
{
 
{
   log_hook = SH_ADD_HOOK_STATICFUNC(IVEngineServer, LogPrint, engine, Hook_LogPrint, false);
+
   log_hook = SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);
 
}
 
}
  
 
void StopHooks()
 
void StopHooks()
 
{
 
{
   if (log_hook)
+
   SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);
  {
 
      SH_REMOVE_HOOK_ID(log_hook);
 
  }
 
 
}</cpp>
 
}</cpp>
  
 
==Adding Member Hooks==
 
==Adding Member Hooks==
The member binding macro is <tt>SH_ADD_HOOK_MEMFUNC</tt>The syntax is:
+
The syntax is similar to static functions.  Example equivalent to the above:
 
+
<cpp>
<cpp>SH_ADD_HOOK_MEMFUNC(Interface, Function, Instance, HookInstance, HookFunction, [post? true,false])</cpp>
 
 
 
Example equivalent to the above:
 
<cpp>HookId log_hook = 0;
 
 
class MyHooks
 
class MyHooks
 
{
 
{
Line 129: Line 123:
 
   void StartHooks()
 
   void StartHooks()
 
   {
 
   {
       log_hook = SH_ADD_HOOK_MEMFUNC(IVEngineServer, LogPrint, engine, this, &MyHooks::Hook_LogPrint, false);
+
       SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);
 +
  }
 +
 
 +
  void StopHooks()
 +
  {
 +
      SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);
 
   }
 
   }
 
};</cpp>
 
};</cpp>
 
Removal does not change.
 
  
  
Line 182: Line 179:
  
 
==Adding Hooks==
 
==Adding Hooks==
Adding or removing hook binds is done via four extra macros:
+
Adding or removing hook binds is done via the following extra macros:
*<tt>SH_ADD_MANUALHOOK_STATICFUNC</tt>
+
*<tt>SH_ADD_MANUALHOOK</tt>
*<tt>SH_ADD_MANUALHOOK_MEMFUNC</tt>
+
*<tt>SH_REMOVE_MANUALHOOK</tt>
  
 
These work similar to the original functions.  Example:
 
These work similar to the original functions.  Example:
 
<cpp>extern Interface *iface;
 
<cpp>extern Interface *iface;
HookId func2_hook = 0;
 
  
 
bool Hook_Function2(int clam)
 
bool Hook_Function2(int clam)
Line 197: Line 193:
 
void StartHooks()
 
void StartHooks()
 
{
 
{
   func2_hook = SH_ADD_MANUALHOOK_STATICFUNC(MHook_Function2, iface, Hook_Function2, false);
+
   SH_ADD_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);
 
}
 
}
  
 
void StopHooks()
 
void StopHooks()
 
{
 
{
   if (func2_hook)
+
   SH_REMOVE_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);
  {
 
      SH_REMOVE_HOOK_ID(func2_hook);
 
  }
 
 
}</cpp>
 
}</cpp>
  
The member function version is similar:
+
Similarly, a member function version would use <tt>SH_MEMBER</tt> instead.
<cpp>SH_ADD_MANUALHOOK_MEMFUNC(UniqueName, Instance, HookInstance, HookFunction, [post? true,false]);</cpp>
 
  
  
=Extended Syntax=
+
=Extended Removal Syntax=
SourceHook contains an extended hook syntax which attempts to modularize and unify the syntax of hookingThe original syntax as described above can still be usedHowever, to take advantage of global hooks (which are described in the next section), the extended syntax must be learned.
+
The syntax described in the above sections is new as of SourceHook v4.5<tt>SH_REMOVE_HOOK</tt> is, for all intents and purposes, optionalThere is another way to remove hooks.
  
If you have no intention of using global hooks, you may ignore this section.
+
Each <tt>SH_ADD</tt> macro returns a non-zero <tt>int</tt> on success.  The same integer can be passed to the <tt>SH_REMOVE_HOOK_ID</tt> macro, and the hook will be removed.  This alternate removal syntax can simplify code that uses multiple successive or dynamic hooks.
  
The extended syntax eliminates the need for separate <tt>STATICFUNC</tt> and <tt>MEMFUNC</tt> macros.  Instead, new overall macros are introduced:
+
Global hooks, described in a later section, require usage of <tt>SH_REMOVE_HOOK_ID</tt> - that is, there is no helper macro to simplify removing a global hook.
*<tt>SH_STATIC(Function)</tt> - Specifies a static hook bind.
 
*<tt>SH_MEMBER(Instance, Function)</tt> - Specifies a member hook bind.
 
  
The macros for adding hooks change as below.  All examples will use the following code as a basis:
 
  
<cpp>
+
=Deprecated Macros=
class Player
+
SourceHook v5.0 deprecates older macros that were used in earlier versions.  The macros are:
{
+
*<tt>SH_ADD_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_STATIC</tt>.
public:
+
*<tt>SH_REMOVE_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_STATIC</tt>.
  virtual void TakeDamage(int damage) =0;
+
*<tt>SH_ADD_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_MEMBER</tt>.
};
+
*<tt>SH_REMOVE_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_MEMBER</tt>.
 
+
*<tt>SH_ADD_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.
void Hook_TakeDamage(int damage);
+
*<tt>SH_REMOVE_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.
void StartPlayerHooks(Player *player);
+
*<tt>SH_ADD_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.
void StopPlayerHooks(Player *player);
+
*<tt>SH_REMOVE_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.
 
 
HookId damage_hook = 0;
 
</cpp>
 
 
 
==Simple Hooks==
 
<cpp>void StartPlayerHooks(Player *player)
 
{
 
  damage_hook = SH_ADD_HOOK(Player, TakeDamage, player, SH_STATIC(Hook_TakeDamage), false);
 
}
 
 
 
void StopPlayerHooks(Player *player)
 
{
 
  if (damage_hook)
 
  {
 
      SH_REMOVE_HOOK_ID(damage_hook);
 
  }
 
}</cpp>
 
 
 
An object oriented version would look similar.  The version below uses <tt>SH_REMOVE_HOOK_ID</tt> instead.
 
<cpp>
 
class PlayerHooks
 
{
 
public:
 
  void TakeDamage(int damage)
 
  {
 
  }
 
  void StartHooks()
 
  {
 
      m_TakeDmgID =
 
        SH_ADD_HOOK(Player, TakeDamage, m_pPlayer, SH_MEMBER(this, &PlayerHooks::TakeDamage), false);
 
  }
 
  void StopHooks()
 
  {
 
      SH_REMOVE_HOOK_ID(m_TakeDmgID);
 
  }
 
private:
 
  int m_TakeDmgID;
 
  Player *m_pPlayer;
 
};</cpp>
 
 
 
==Manual Hooks==
 
Extended manual hooks are similar.
 
 
 
Example:
 
<cpp>
 
SH_DECL_MANUALHOOK1_void(MHook_TakeDamage, 0, 0, 0, int);
 
  
void StartPlayerHooks(Player *player)
+
These macros are fairly self explanatory.  The parameter where the <tt>SH_STATIC</tt> or <tt>SH_MEMBER</tt> macro would normally go is instead filled with the parameters to that macro. 
{
 
  damage_hook = SH_ADD_HOOK(MHook_TakeDamage, player, SH_STATIC(Hook_TakeDamage), false);
 
}
 
  
void StopPlayerHooks(Player *player)
+
This syntax is considered deprecated, although is supported so older source code continues to compile without major changes.
{
 
  if (damage_hook)
 
  {
 
      SH_REMOVE_HOOK_ID(damage_hook);
 
  }
 
}</cpp>
 
  
  
Line 348: Line 282:
 
<cpp>
 
<cpp>
 
SH_ADD_VPHOOK(Interface, Function, Instance, Handler, Post)
 
SH_ADD_VPHOOK(Interface, Function, Instance, Handler, Post)
SH_ADD_DVPHOOK(Interface, Function, VirtualTable, Handler, Post);
+
SH_ADD_DVPHOOK(Interface, Function, VirtualTable, Handler, Post)
 
</cpp>
 
</cpp>
  
Line 445: Line 379:
  
 
=Bypassing Hooks=
 
=Bypassing Hooks=
Often, either to avoid certain functionality or to avoid infinite recursion, it is necessary to bypass all hooks on a hooked function, such that only the original function is called.  For example, our example above blocks certain messages sent through <tt>LogPrint</tt>.  In order to send that message, the hook needs to be bypassed.
+
Often, either to avoid certain functionality or to avoid infinite recursion, it is necessary to bypass all hooks on a hooked function, such that only the original function is called.  For instance, a previous blocked certain messages sent through <tt>LogPrint</tt>.  In order to send that message, the hook needs to be bypassed.
  
The method of bypassing hooks changed in SourceHook versions v4.5 and above.  The old method is deprecated, but still supported.  Both are documented here.
+
To way to do this is to use the <tt>SH_CALL</tt> macro:
 
 
==New Method==
 
The new method uses the macro <tt>SH_CALL</tt>, which has the following prototype:
 
 
<cpp>SH_CALL(Instance, HookFunction)(params)</cpp>
 
<cpp>SH_CALL(Instance, HookFunction)(params)</cpp>
  
Line 456: Line 387:
 
<cpp>SH_CALL(engine, &IVEngineServer::LogPrint)("Secret Message");</cpp>
 
<cpp>SH_CALL(engine, &IVEngineServer::LogPrint)("Secret Message");</cpp>
  
==Old Method==
+
Similarly, manual hooks have <tt>SH_MCALL</tt>:
The old method is optional as of SourceHook v4.5 and represents restrictions that have since been lifted.  It is recommended that only the new style be used.
 
 
 
In order to bypass hooks on an interface, a ''CallClass'' object must be obtained.  Obtaining and releasing such an object should be considered an expensive operation.  An example might look like:
 
 
 
<cpp>
 
CallClass<IVEngineServer> *engine_bypass;
 
  
void LogPrint_Bypass(const char *msg)
+
<cpp>SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);
{
 
  SH_CALL(engine_bypass, &IVEngineServer::LogPrint)(msg);
 
}
 
  
void StartHooks()
 
{
 
  /* ... */
 
  engine_bypass = SH_GET_CALLCLASS(engine);
 
}
 
 
void StopHooks()
 
{
 
  /* ... */
 
  SH_RELEASE_CALLCLASS(engine_bypass);
 
}
 
</cpp>
 
 
Note that the syntax is similar to the new method, except the CallClass instance must be used instead of the actual class instance.
 
 
==Manual Bypasses==
 
Bypasses against manual hooks are possible as well.  Re-using the example from earlier:
 
 
<cpp>
 
class Interface
 
{
 
public:
 
  virtual bool Function2(int clam) =0;
 
};
 
 
extern Interface *iface;
 
 
SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);</cpp>
 
 
===New Method===
 
Supported in SourceHook v4.5 or higher.
 
 
<cpp>
 
 
bool Function2_Bypass(int clam)
 
bool Function2_Bypass(int clam)
 
{
 
{
Line 507: Line 396:
 
}</cpp>
 
}</cpp>
  
===Old Method===
+
==Deprecated Syntax==
Deprecated in SourceHook v4.5 or higher.
+
The syntax described above is used in SourceHook v5.0 and v4.5. An older syntax, using a "CallClass" data type, was used in SourceHook v4.4 and lower. This syntax was deprecated in v4.5 and completely removed in v5.0. Upgrading from SourceHook v4.4 means that code referencing <tt>SH_GET_CALLCLASS</tt> or <tt>SH_REMOVE_CALLCLASS</tt> must be removed, and <tt>SH_CALL</tt> simply needs an instance pointer instead.
 
 
<cpp>ManualCallClass *iface_bypass = NULL;
 
 
 
bool Function2_Bypass(int clam)
 
{
 
  return SH_MCALL(iface_bypass, MHook_Function2)(clam);
 
}
 
 
 
void StartHooks()
 
{
 
  /* ... */
 
  iface_bypass = SH_GET_MCALLCLASS(iface, sizeof(void*));
 
}
 
 
 
void StopHooks()
 
{
 
  /* ... */
 
  SH_RELEASE_CALLCLASS(iface_bypass);
 
}</cpp>
 
  
  
Line 582: Line 452:
 
#include <sourcehook/sourcehook_impl.h>
 
#include <sourcehook/sourcehook_impl.h>
  
SourceHook::CSourceHookImpl g_SourceHook;
+
SourceHook::Impl::CSourceHookImpl g_SourceHook;
 
</cpp>
 
</cpp>
  
Line 606: Line 476:
 
In order to support this, each separate library must be given the <tt>ISourceHook</tt> pointer and a unique <tt>g_PLID</tt> value.  <tt>CSourceHookImpl</tt> provides a few useful functions for managing hooks on "child" libraries or otherwise linked code.
 
In order to support this, each separate library must be given the <tt>ISourceHook</tt> pointer and a unique <tt>g_PLID</tt> value.  <tt>CSourceHookImpl</tt> provides a few useful functions for managing hooks on "child" libraries or otherwise linked code.
  
*<tt>IsPluginInUse()</tt> - Determines whether an ID owns any hooks.
 
 
*<tt>PausePlugin()</tt> - Silences any hooks from an ID, such that those hooks will not be called.
 
*<tt>PausePlugin()</tt> - Silences any hooks from an ID, such that those hooks will not be called.
 
*<tt>UnpausePlugin()</tt> - Un-silences any silenced hooks from an ID.
 
*<tt>UnpausePlugin()</tt> - Un-silences any silenced hooks from an ID.
 
*<tt>UnloadPlugin()</tt> - Clean-up any left-over hooks from an ID.
 
*<tt>UnloadPlugin()</tt> - Clean-up any left-over hooks from an ID.

Revision as of 08:26, 9 October 2007

SourceHook is a powerful API (Application Programming Interface) for detouring (hooking) virtual functions. Unlike static detours, SourceHook needs only to swap addresses in and out of an object's virtual table. This makes it fast and generally very platform-safe.

SourceHook is coupled with Don Clugston's FastDelegate headers. Virtual hooks can be detoured to any static function of the same prototype, or any member function (of any class) as long as the prototype matches.

All code in SourceHook is part of the SourceHook namespace. Thus, it may be prudent to declare this before using SourceHook structures or types:

using namespace SourceHook;

Simple Hooks

SourceHook has the following steps of operation:

  • Declare the prototype of the function you are going to hook. This generates compile-time code that is able to pinpoint exactly how to go about hooking the function.
  • Hook the function - as a member function of another class or a regular static function.
  • Before the hooked function is called, all of the "pre" hook handlers attached to it are called. Each hook can set a special flag, the highest of which is chosen as a final operation. This flag specifies whether the original function should be called or not.
  • Once all the hooks have been called, SourceHook decides whether to call the original function. Another set of hooks are called directly after, called "post" hook handlers. You can specify whether each hook is a post or pre hook - it simply changes whether it's called before or after the original call is made.
  • After you are done using a hook, you must safely remove it before the object is destroyed (otherwise, memory will be leaked).

Declaration

As an example, take the following class prototype:

class IVEngineServer
{
public:
   /*...*/
   virtual void LogPrint( const char *msg ) = 0;
   virtual bool IsDedicated() = 0;
};
 
extern IVEngineServer *engine;


The first step is to figure out how to declare its prototype to SourceHook. This function is void, and has one parameter. The declaration macro follows these formats:

  • SH_DECL_HOOKn - n is the number of parameters
    • The parameters are: Class name, member function name, attributes, overloaded?, the return type, and a list of the parameter types.
  • SH_DECL_HOOKn_void - n is the number of parameters
    • _void specifies that the function does not return a value. The format is the same as above except the "return type" parameter is missing.
  • Note: Not covered here are the SH_DECL_HOOKn[_void]_vafmt hooks. These can hook string-formattable variable argument lists. You do not pass the string format or ellipses parameter. SourceHook will automatically format the string for your hook.

Our macro will look like this:

SH_DECL_HOOK1_void(IVEngineServer, LogPrint, SH_NOATTRIB, 0, const char *);
SH_DECL_HOOK0(IVEngineServer, IsDedicated, SH_NOATTRIB, 0, bool);

Broken down for the first line:

  • There is 1 parameter.
  • The function has no return value.
  • IVEngineServer is the class containing the function.
  • LogPrint is the function being hooked.
  • The function as no attributes (for example, it is not const).
  • The function is not overloaded.
  • The first (and only) argument is a const char *

The second line is similar, except the parameter immediately after the overload parameter specifies the return type. There are no further parameters since it was declared with 0.

Hook Functions

Hooks can be declared either pre or post. A pre hook will intercept the original function before it is called. Pre-hooks can return one of four hook actions:

  • META_IGNORED - The original function will be called.
  • META_HANDLED - Same as META_IGNORED, except subsequent hooks can assume this means something important was changed.
  • META_OVERRIDE - The original function will be called, but the new return value will be used instead of the one from the original function.
  • META_SUPERCEDE - The original function will not be called. The new return value (if any) will be used instead.

Once all pre-hooks have been processed, SourceHook takes an action based on the "highest" hook action returned (META_IGNORED being lowest, META_SUPERCEDE being highest). Once the action has been processed, all post hooks are called. That is to say, even if the original function is never called, post hooks are still processed. Because a post hook as no chance at true interception, it is important to realize that depending on the information being detoured, the data may be modified or destroyed. Similarly, a post hook's returned action and value is ignored.

A hook's action is signalled via one of two macros:

  • RETURN_META - Only usable from void functions. Signals the action to take, then returns.
  • RETURN_META_VALUE - Only usable from non-void functions. Signals the action to take, then returns the supplied value.

There are two methods of adding or removing hooks. Hooks can be bound to static or member functions. Both have a similar syntax. Their macros are:

  • SH_STATIC(Function) - Hook to a static function.
  • SH_MEMBER(Instance, Function) - Hook to a member function.

It is important to realize that a simple hook will only be invoked when used on the same instance. That is to say, if there are 500 instances of object X, and a hook is added to instance #8 to &X::Y, then the hook will only be invoked from instance #8. Multiple hooks can be declared on the same instance, and multiple instances can be bound to the same hook.

This restriction on simple hooks means that the hook must be removed before the instance is destroyed; otherwise, memory will be leaked with no chance of removal (since the accessing the object is invalid). To have hooks that work across all instances, and thus do not need to be delegated per-instance, see the "Global Hooks" section.

Adding Static Hooks

The static binding macro is SH_[ADD|REMOVE]_HOOK. The syntax is:

SH_[ADD|REMOVE]_HOOK(Interface, Function, Instance, Hook, [post? true,false])

An example of adding a LogPrint hook:

void Hook_LogPrint(const char *msg)
{
   if (strcmp(msg, "If this string matches the function will be blocked") == 0)
   {
      RETURN_META(MRES_SUPERCEDE);
   }
 
   /* Not needed, but good style */
   RETURN_META(MRES_IGNORED);
}
 
void StartHooks()
{
   log_hook = SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);
}
 
void StopHooks()
{
   SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);
}

Adding Member Hooks

The syntax is similar to static functions. Example equivalent to the above:

class MyHooks
{
public:
   void Hook_LogPrint(const char *msg)
   {
      if (strcmp(msg, "If this string matches the function will be blocked") == 0)
      {
         RETURN_META(MRES_SUPERCEDE);
      }
 
      /* Not needed, but good style */
      RETURN_META(MRES_IGNORED);
   }
 
   void StartHooks()
   {
      SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);
   }
 
   void StopHooks()
   {
      SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);
   }
};


Manual Hooks

In some cases, it may be necessary to support multiple, incompatible ABI branches of an interface. For example, suppose you need to hook an application that may supply either version of these interfaces:

Interface v1:

class Interface
{
public:
   virtual void Function1() =0;
   virtual bool Function2(int clam) =0;
};

Interface v2:

class Interface
{
public:
   virtual bool Function2(int clam) =0;
};

Obviously, these two interfaces are backwards incompatible. Manual hooks allow you to precisely define the structure of the virtual table, bypassing the compiler's rules. These rules can be re-configured at runtime.

Declaration

Declaring a manual hook is similar to declaring a normal/simple hook. The syntax is:

SH_DECL_MANUALHOOK<n>[_void](UniqueName, vtblIndex, vtblOffs, thisOffs, [return and param types])

The UniqueName is a unique identifier for the hook. The vtblIndex is the index into the virtual table at which the function lies. In most compilers, this index starts from 0. The vtblOffs and thisOffs fields are used for multiple inheritance and are almost always 0 in modern compiler single inheritance.

An example of hooking the two functions from the first interface version:

SH_DECL_MANUALHOOK0_void(MHook_Function1, 0, 0, 0);
SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);

Reconfiguring

A manual hook can be reconfigured, which will update its set offsets. Reconfiguration automatically removes all hooks on the manual hook. Let's say we want to reconfigure the Function2 hook in the case of the second version being detected:

void SwitchToNewerHooks()
{
   SH_MANUALHOOK_RECONFIGURE(MHook_Function2, 0, 0, 0);
}

Note that the hook was referenced by its unique identifier.

Adding Hooks

Adding or removing hook binds is done via the following extra macros:

  • SH_ADD_MANUALHOOK
  • SH_REMOVE_MANUALHOOK

These work similar to the original functions. Example:

extern Interface *iface;
 
bool Hook_Function2(int clam)
{
   RETURN_META_VALUE(MRES_IGNORED, false);
}
 
void StartHooks()
{
   SH_ADD_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);
}
 
void StopHooks()
{
   SH_REMOVE_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);
}

Similarly, a member function version would use SH_MEMBER instead.


Extended Removal Syntax

The syntax described in the above sections is new as of SourceHook v4.5. SH_REMOVE_HOOK is, for all intents and purposes, optional. There is another way to remove hooks.

Each SH_ADD macro returns a non-zero int on success. The same integer can be passed to the SH_REMOVE_HOOK_ID macro, and the hook will be removed. This alternate removal syntax can simplify code that uses multiple successive or dynamic hooks.

Global hooks, described in a later section, require usage of SH_REMOVE_HOOK_ID - that is, there is no helper macro to simplify removing a global hook.


Deprecated Macros

SourceHook v5.0 deprecates older macros that were used in earlier versions. The macros are:

  • SH_ADD_HOOK_STATICFUNC - Wrapper for SH_ADD_HOOK and SH_STATIC.
  • SH_REMOVE_HOOK_STATICFUNC - Wrapper for SH_REMOVE_HOOK and SH_STATIC.
  • SH_ADD_HOOK_MEMFUNC - Wrapper for SH_ADD_HOOK and SH_MEMBER.
  • SH_REMOVE_HOOK_MEMFUNC - Wrapper for SH_REMOVE_HOOK and SH_MEMBER.
  • SH_ADD_MANUALHOOK_STATICFUNC - Wrapper for SH_ADD_MANUALHOOK and SH_STATIC.
  • SH_REMOVE_MANUALHOOK_STATICFUNC - Wrapper for SH_REMOVE_MANUALHOOK and SH_STATIC.
  • SH_ADD_MANUALHOOK_MEMFUNC - Wrapper for SH_ADD_MANUALHOOK and SH_MEMBER.
  • SH_REMOVE_MANUALHOOK_MEMFUNC - Wrapper for SH_REMOVE_MANUALHOOK and SH_MEMBER.

These macros are fairly self explanatory. The parameter where the SH_STATIC or SH_MEMBER macro would normally go is instead filled with the parameters to that macro.

This syntax is considered deprecated, although is supported so older source code continues to compile without major changes.


Global Hooks

Note: Global Hooks are only available in SourceHook v4.5 or later.

Global hooks are unlike normal hooks in that the hook is invoked for ALL instances, rather than solely the given the instance the hook was bound to. It is important to realize that this feature can be deceiving. Consider the following example:

class CBaseEntity
{
public:
   virtual void SetHealth(int health) =0;
};
 
class CBaseCat : public CBaseEntity
{
public:
   virtual void SetHealth();
};
 
class CBaseKitten : public CBaseCat
{
public:
   virtual void SetHealth();
};

In this example, CBaseCat and CBaseKitten instances have separate virtual tables. Although they both derive from CBaseEntity, they are separate virtual objects. Therefore, a global hook on CBaseEntity will receive no invocations, and a hook on CBaseCat will receive only CBaseCat instances, as long as the instance is not a class derived from CBaseCat.

With this understanding in place, there are two separate syntaxes - one for simple hooks and one for manual hooks. Additionally, there are two ways of declaring the virtual interface to use:

  • An instance of the class can be passed.
  • The direct address to the virtual table can be passed.

They are essentially equivalent, although one may be more advantageous than the other (for example, if no instances are known, but the vtable address can be extracted via pattern searching).

It is also important to note that global hooks are just a different method of "filtering." They fall into either the "simple" or "manual" category, and are otherwise exactly the same to those hooks. Thus there are no separate return/declaration macros for global hooks.

The macro META_IFACEPTR is especially useful for global hooks. See Interface Pointers from Hooks near the end.

Lastly, global hooks exclusively use the extended hooking syntax. That means there exists only SH_ADD macros. SH_HOOK_REMOVE_ID must be used and the hook ID generated via SH_ADD must be cached.

All examples will use the following code as a basis:

class Player
{
public:
   virtual void TakeDamage(int damage) =0;
};
 
void Hook_TakeDamage(int damage);

Simple Hooks

Two extra macros exist for adding a global hook:

SH_ADD_VPHOOK(Interface, Function, Instance, Handler, Post)
SH_ADD_DVPHOOK(Interface, Function, VirtualTable, Handler, Post)

An example:

extern void *player_vtable;
HookId takedamage_hook = 0;
 
void StartHooks()
{
   takedamage_hook = SH_ADD_DVPHOOK(Player, TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);
}
 
void StopHooks()
{
   if (takedamage_hook)
   {
      SH_REMOVE_HOOK_ID(takedamage_hook);
   }
}

Manual Hooks

Similarly, manual hooks are straightforward:

SH_DECL_MANUALHOOK1_void(MHook_TakeDamage, 0, 0, 0, int);
 
extern void *player_vtable;
int takedamage_hook = 0;
 
void StartHooks()
{
   takedamage_hook = SH_ADD_MANUALDVPHOOK(MHook_TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);
}
 
void StopHooks()
{
   if (takedamage_hook)
   {
      SH_REMOVE_HOOK_ID(takedamage_hook);
   }
}


Modifying Parameters

Consider another variation of the hooking process. There is a hook on function X, which has one parameter. The hook wants to change the value of this parameter transparently. For example:

  • Caller passes 5 into X.
  • Hook changes the 5 to a 6.
  • Hooked function receives a 6 and continues normally.

SourceHook has a method for achieving this. As an added bonus, the new parameters are passed to subsequent hooks. That means the replacement process is as transparent as possible. For this example, we'll use the following code, with an assumed hook on Player::TakeDamage to the Hook_TakeDamage function.

class Player
{
public:
   virtual void TakeDamage(int damage);
};
 
void Hook_TakeDamage(int damage);

Our objective is to multiply the damage by 2.

Simple Hooks

For simple hooks, changing parameters looks similar to an SH_CALL. The macros are:

RETURN_META_NEWPARAMS(Action, HookFunction, ([params]))
RETURN_META_VALUE_NEWPARAMS(Action, Value, HookFunction, ([params]))

Example:

void Hook_TakeDamage(int damage)
{
   RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (damage * 2));
}

Note that the parenthesis enclosing the parameters are required.

Manual Hooks

Manual hooks require slightly different macros. They are:

RETURN_META_MNEWPARAMS(Action, UniqueName, ([params]));
RETURN_META_VALUE_MNEWPARAMS(Action, Value, UniqueName, ([params]));

Example:

SH_DECL_MANUALHOOK1_void(MHook_Player_TakeDamage, 0, 0, 0, int);
 
void Hook_TakeDamage(int damage)
{
   RETURN_META_MNEWPARAMS(MRES_IGNORED, MHook_Player_TakeDamage, (damage *2));
}

Note that the parenthesis enclosing the parameters are required.


Bypassing Hooks

Often, either to avoid certain functionality or to avoid infinite recursion, it is necessary to bypass all hooks on a hooked function, such that only the original function is called. For instance, a previous blocked certain messages sent through LogPrint. In order to send that message, the hook needs to be bypassed.

To way to do this is to use the SH_CALL macro:

SH_CALL(Instance, HookFunction)(params)

Example:

SH_CALL(engine, &IVEngineServer::LogPrint)("Secret Message");

Similarly, manual hooks have SH_MCALL:

SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);
 
bool Function2_Bypass(int clam)
{
   return SH_MCALL(iface, MHook_Function2)(clam);
}

Deprecated Syntax

The syntax described above is used in SourceHook v5.0 and v4.5. An older syntax, using a "CallClass" data type, was used in SourceHook v4.4 and lower. This syntax was deprecated in v4.5 and completely removed in v5.0. Upgrading from SourceHook v4.4 means that code referencing SH_GET_CALLCLASS or SH_REMOVE_CALLCLASS must be removed, and SH_CALL simply needs an instance pointer instead.


Other Macros

SourceHook contains a large variety of extra macros. This section is a grab bag of the more commonly used ones.

Interface Pointers from Hooks

Let's say you have the following hook:

class Player
{
public:
   virtual void TakeDamage(int damage);
   float GetDamageModifier();
};
 
void Hook_TakeDamage(int damage);

How can you get the Player instance while in the hook? This can be achieved via the META_IFACEPTR macro. Example:

void Hook_TakeDamage(int damage)
{
   Player *pPlayer = META_IFACEPTR(Player);
 
   int new_damage = (int)((pPlayer->GetDamageModifier() + 0.3) * (float)damage);
 
   RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (new_damage));
}

Note that the class name should be passed to META_IFACEPTR, not the pointer type.

Ignoring Reference Returns

There is a special macro, RETURN_META_NOREF, for ignoring a return value for reference-returning functions. Example:

class ISomething
{
public:  
   virtual int & GetSomething() =0;
};
 
int & Hook_GetSomething()
{
   RETURN_META_NOREF(MRES_IGNORED, int &);
}


Embedding

Embedding SourceHook in your own application or library is very easy. The sourcehook.cpp file must be compiled or linked into your project. To instantiate the SourceHook engine, you must create a CSourceHookImpl instance. Example:

/* Normally, just <sourcehook.h> is included, but this is needed to instantiate the engine. */
#include <sourcehook/sourcehook_impl.h>
 
SourceHook::Impl::CSourceHookImpl g_SourceHook;

To actually use SourceHook, it is necessary to have two global variables:

  • g_PLID - A unique integer that identifies the library using SourceHook. This is used for removing all hooks a library is using.
  • g_SHPtr - A pointer to the SourceHook::ISourceHook interface.

Example header file:

#include <sourcehook/sourcehook.h>
 
extern SourceHook::ISourceHook *g_SHPtr;
extern int g_PLID;

Example addition to the global code:

SourceHook::ISourceHook *g_SHPtr = &g_SourceHook;
int g_PLID = 0;

Multiple Libraries/Shared Hooks

If SourceHook is going to be used across multiple libraries in the same process, it is essential that only one instance of SourceHook be present. Of course, that is only logical, since otherwise the instances would be replacing each other's virtual patches.

In order to support this, each separate library must be given the ISourceHook pointer and a unique g_PLID value. CSourceHookImpl provides a few useful functions for managing hooks on "child" libraries or otherwise linked code.

  • PausePlugin() - Silences any hooks from an ID, such that those hooks will not be called.
  • UnpausePlugin() - Un-silences any silenced hooks from an ID.
  • UnloadPlugin() - Clean-up any left-over hooks from an ID.