https://wiki.alliedmods.net/api.php?action=feedcontributions&user=Dave+Gomboc&feedformat=atomAlliedModders Wiki - User contributions [en]2024-03-29T08:41:07ZUser contributionsMediaWiki 1.31.6https://wiki.alliedmods.net/index.php?title=SourceHook_Development&diff=6138SourceHook Development2008-09-12T18:15:42Z<p>Dave Gomboc: /* Hook Functions */</p>
<hr />
<div>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.<br />
<br />
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.<br />
<br />
All code in SourceHook is part of the SourceHook namespace. Thus, it may be prudent to declare this before using SourceHook structures or types:<br />
<br />
<cpp>using namespace SourceHook;</cpp><br />
<br />
=Simple Hooks=<br />
SourceHook has the following steps of operation:<br />
<br />
* 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.<br />
* Hook the function - as a member function of another class or a regular static function.<br />
* 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.<br />
* 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.<br />
* After you are done using a hook, you must safely remove it before the object is destroyed (otherwise, memory will be leaked).<br />
<br />
==Declaration==<br />
As an example, take the following class prototype:<br />
<cpp><br />
class IVEngineServer<br />
{<br />
public:<br />
/*...*/<br />
virtual void LogPrint( const char *msg ) = 0;<br />
virtual bool IsDedicated() = 0;<br />
};<br />
<br />
extern IVEngineServer *engine;<br />
</cpp><br />
<br />
<br />
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:<br />
<br />
*{{bcode|SH_DECL_HOOK}}n - n is the number of parameters<br />
**The parameters are: Class name, member function name, attributes, overloaded?, the return type, and a list of the parameter types.<br />
*{{bcode|SH_DECL_HOOKn_void}} - n is the number of parameters<br />
**_void specifies that the function does not return a value. The format is the same as above except the "return type" parameter is missing.<br />
*'''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.<br />
<br />
Our macro will look like this:<br />
<pre><br />
SH_DECL_HOOK1_void(IVEngineServer, LogPrint, SH_NOATTRIB, 0, const char *);<br />
SH_DECL_HOOK0(IVEngineServer, IsDedicated, SH_NOATTRIB, 0, bool);<br />
</pre><br />
<br />
Broken down for the first line:<br />
*There is 1 parameter.<br />
*The function has no return value.<br />
*<tt>IVEngineServer</tt> is the class containing the function.<br />
*<tt>LogPrint</tt> is the function being hooked.<br />
*The function as no attributes (for example, it is not const).<br />
*The function is not overloaded.<br />
*The first (and only) argument is a <tt>const char *</tt><br />
<br />
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.<br />
<br />
==Hook Functions==<br />
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'':<br />
*<tt>MRES_IGNORED</tt> - The original function will be called.<br />
*<tt>MRES_HANDLED</tt> - Same as <tt>MRES_IGNORED</tt>, except subsequent hooks can assume this means something important was changed.<br />
*<tt>MRES_OVERRIDE</tt> - The original function will be called, but the new return value will be used instead of the one from the original function.<br />
*<tt>MRES_SUPERCEDE</tt> - The original function will not be called. The new return value (if any) will be used instead.<br />
<br />
These enum constants are defined in <tt><sourcehook.h></tt>, and are made available to Metamod:Source plugin developers via <tt><ISmmPlugin.h></tt>.<br />
<br />
Once all pre-hooks have been processed, SourceHook takes an action based on the "highest" hook action returned (<tt>MRES_IGNORED</tt> being lowest, <tt>MRES_SUPERCEDE</tt> 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.<br />
<br />
A hook's action is signalled via one of two macros:<br />
*<tt>RETURN_META</tt> - Only usable from <tt>void</tt> functions. Signals the action to take, then returns.<br />
*<tt>RETURN_META_VALUE</tt> - Only usable from non-<tt>void</tt> functions. Signals the action to take, then returns the supplied value.<br />
<br />
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:<br />
*<tt>SH_STATIC(Function)</tt> - Hook to a static function.<br />
*<tt>SH_MEMBER(Instance, Function)</tt> - Hook to a member function.<br />
<br />
<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 function X::Y in instance #8, then the hook will only be invoked from instance #8. <b>Multiple hooks can be declared on the same instance, and multiple instances can be bound to the same hook, but one hook will only be invoked for the instances it was hooked to.</b><br />
<br />
To have hooks that work across all instances, and thus do not need to be delegated per-instance, see the "Global Hooks" section. As of SourceHook v5, it is safe to remove hooks on a destroyed instance, as the instance is not actually dereferenced. However, its virtual table must still be accessible.<br />
<br />
==Adding Hooks==<br />
The macro to add hooks is <tt>SH_[ADD|REMOVE]_HOOK</tt>. The syntax is:<br />
<br />
<cpp>SH_[ADD|REMOVE]_HOOK(Interface, Function, Instance, Hook, [post? true,false])</cpp><br />
<br />
An example of adding a <tt>LogPrint</tt> hook:<br />
<cpp>void Hook_LogPrint(const char *msg)<br />
{<br />
if (strcmp(msg, "If this string matches the function will be blocked") == 0)<br />
{<br />
RETURN_META(MRES_SUPERCEDE);<br />
}<br />
<br />
/* Not needed, but good style */<br />
RETURN_META(MRES_IGNORED);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
log_hook = SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);<br />
}</cpp><br />
<br />
The syntax is similar for hooking to member functions. Example equivalent to the above:<br />
<cpp><br />
class MyHooks<br />
{<br />
public:<br />
void Hook_LogPrint(const char *msg)<br />
{<br />
if (strcmp(msg, "If this string matches the function will be blocked") == 0)<br />
{<br />
RETURN_META(MRES_SUPERCEDE);<br />
}<br />
<br />
/* Not needed, but good style */<br />
RETURN_META(MRES_IGNORED);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);<br />
}<br />
};</cpp><br />
<br />
<br />
=Manual Hooks=<br />
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:<br />
<br />
Interface v1:<br />
<cpp><br />
class Interface<br />
{<br />
public:<br />
virtual void Function1() =0;<br />
virtual bool Function2(int clam) =0;<br />
};</cpp><br />
<br />
Interface v2:<br />
<cpp><br />
class Interface<br />
{<br />
public:<br />
virtual bool Function2(int clam) =0;<br />
};</cpp><br />
<br />
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.<br />
<br />
==Declaration==<br />
Declaring a manual hook is similar to declaring a normal/simple hook. The syntax is:<br />
<br />
<cpp>SH_DECL_MANUALHOOK<n>[_void](UniqueName, vtblIndex, vtblOffs, thisOffs, [return and param types])</cpp><br />
<br />
The <tt>UniqueName</tt> is a unique identifier for the hook. The <tt>vtblIndex</tt> is the index into the virtual table at which the function lies. In most compilers, this index starts from 0. The <tt>vtblOffs</tt> and <tt>thisOffs</tt> fields are used for multiple inheritance and are almost always 0 in modern compiler single inheritance.<br />
<br />
An example of hooking the two functions from the first interface version:<br />
<cpp>SH_DECL_MANUALHOOK0_void(MHook_Function1, 0, 0, 0);<br />
SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);</cpp><br />
<br />
==Reconfiguring==<br />
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 <tt>Function2</tt> hook in the case of the second version being detected:<br />
<br />
<cpp><br />
void SwitchToNewerHooks()<br />
{<br />
SH_MANUALHOOK_RECONFIGURE(MHook_Function2, 0, 0, 0);<br />
}<br />
</cpp><br />
<br />
Note that the hook was referenced by its unique identifier.<br />
<br />
==Adding Hooks==<br />
Adding or removing hook binds is done via the following extra macros:<br />
*<tt>SH_ADD_MANUALHOOK</tt><br />
*<tt>SH_REMOVE_MANUALHOOK</tt><br />
<br />
These work similar to the original functions. Example:<br />
<cpp>extern Interface *iface;<br />
<br />
bool Hook_Function2(int clam)<br />
{<br />
RETURN_META_VALUE(MRES_IGNORED, false);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
SH_ADD_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);<br />
}</cpp><br />
<br />
Similarly, a member function version would use <tt>SH_MEMBER</tt> instead.<br />
<br />
<br />
=Extended Removal Syntax=<br />
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, optional. There is another way to remove hooks.<br />
<br />
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.<br />
<br />
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.<br />
<br />
<br />
=Old Macros=<br />
SourceHook v5.0 deprecates older macros that were used in earlier versions. The macros are:<br />
*<tt>SH_ADD_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_REMOVE_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_ADD_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_REMOVE_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_ADD_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_REMOVE_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_ADD_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_REMOVE_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.<br />
<br />
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. <br />
<br />
This syntax is considered deprecated, but it is still supported. Code written with these macros will continue to compile against older SourceHook versions. If you are writing a plugin which must work against Metamod:Source 1.4 and 1.6, you will want to use the older macros for simplicity.<br />
<br />
<br />
=Global Hooks=<br />
<b>Note:</b> Global Hooks are only available in SourceHook v4.5 or later.<br />
<br />
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:<br />
<br />
<cpp><br />
class CBaseEntity<br />
{<br />
public:<br />
virtual void SetHealth(int health) =0;<br />
};<br />
<br />
class CBaseCat : public CBaseEntity<br />
{<br />
public:<br />
virtual void SetHealth();<br />
};<br />
<br />
class CBaseKitten : public CBaseCat<br />
{<br />
public:<br />
virtual void SetHealth();<br />
};</cpp><br />
<br />
In this example, <tt>CBaseCat</tt> and <tt>CBaseKitten</tt> instances have ''separate virtual tables''. Although they both derive from <tt>CBaseEntity</tt>, they are separate virtual objects. <b>Therefore, a global hook on <tt>CBaseEntity</tt> will receive no invocations, and a hook on <tt>CBaseCat</tt> will receive only <tt>CBaseCat</tt> instances, as long as the instance is not a class derived from <tt>CBaseCat</tt></b>.<br />
<br />
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:<br />
*An instance of the class can be passed.<br />
*The direct address to the virtual table can be passed.<br />
<br />
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).<br />
<br />
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.<br />
<br />
The macro <tt>META_IFACEPTR</tt> is especially useful for global hooks. See [[SourceHook_Development#Interface_Pointers_from_Hooks|Interface Pointers from Hooks]] near the end.<br />
<br />
Lastly, global hooks exclusively use the extended hooking syntax. That means there exists only <tt>SH_ADD</tt> macros. <tt>SH_HOOK_REMOVE_ID</tt> must be used and the hook ID generated via <tt>SH_ADD</tt> must be cached.<br />
<br />
All examples will use the following code as a basis:<br />
<cpp><br />
class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage) =0;<br />
};<br />
<br />
void Hook_TakeDamage(int damage);<br />
</cpp><br />
<br />
==Simple Hooks==<br />
Two extra macros exist for adding a global hook:<br />
<br />
<cpp><br />
SH_ADD_VPHOOK(Interface, Function, Instance, Handler, Post)<br />
SH_ADD_DVPHOOK(Interface, Function, VirtualTable, Handler, Post)<br />
</cpp><br />
<br />
An example:<br />
<cpp><br />
extern void *player_vtable;<br />
HookId takedamage_hook = 0;<br />
<br />
void StartHooks()<br />
{<br />
takedamage_hook = SH_ADD_DVPHOOK(Player, TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
if (takedamage_hook)<br />
{<br />
SH_REMOVE_HOOK_ID(takedamage_hook);<br />
}<br />
}</cpp><br />
<br />
==Manual Hooks==<br />
Similarly, manual hooks are straightforward:<br />
<cpp><br />
SH_DECL_MANUALHOOK1_void(MHook_TakeDamage, 0, 0, 0, int);<br />
<br />
extern void *player_vtable;<br />
int takedamage_hook = 0;<br />
<br />
void StartHooks()<br />
{<br />
takedamage_hook = SH_ADD_MANUALDVPHOOK(MHook_TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
if (takedamage_hook)<br />
{<br />
SH_REMOVE_HOOK_ID(takedamage_hook);<br />
}<br />
}</cpp><br />
<br />
<br />
=Modifying Parameters=<br />
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:<br />
*Caller passes 5 into X.<br />
*Hook changes the 5 to a 6.<br />
*Hooked function receives a 6 and continues normally.<br />
<br />
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 <tt>Player::TakeDamage</tt> to the <tt>Hook_TakeDamage</tt> function.<br />
<br />
<cpp><br />
class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage);<br />
};<br />
<br />
void Hook_TakeDamage(int damage);</cpp><br />
<br />
Our objective is to multiply the damage by 2.<br />
<br />
==Simple Hooks==<br />
For simple hooks, changing parameters looks similar to an <tt>SH_CALL</tt>. The macros are:<br />
<br />
<cpp>RETURN_META_NEWPARAMS(Action, HookFunction, ([params]))<br />
RETURN_META_VALUE_NEWPARAMS(Action, Value, HookFunction, ([params]))<br />
</cpp><br />
<br />
Example:<br />
<cpp><br />
void Hook_TakeDamage(int damage)<br />
{<br />
RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (damage * 2));<br />
}</cpp><br />
<br />
Note that the parenthesis enclosing the parameters are required.<br />
<br />
==Manual Hooks==<br />
Manual hooks require slightly different macros. They are:<br />
<br />
<cpp>RETURN_META_MNEWPARAMS(Action, UniqueName, ([params]));<br />
RETURN_META_VALUE_MNEWPARAMS(Action, Value, UniqueName, ([params]));</cpp><br />
<br />
Example:<br />
<cpp><br />
SH_DECL_MANUALHOOK1_void(MHook_Player_TakeDamage, 0, 0, 0, int);<br />
<br />
void Hook_TakeDamage(int damage)<br />
{<br />
RETURN_META_MNEWPARAMS(MRES_IGNORED, MHook_Player_TakeDamage, (damage *2));<br />
}</cpp><br />
<br />
Note that the parenthesis enclosing the parameters are required.<br />
<br />
<br />
=Bypassing Hooks=<br />
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.<br />
<br />
To way to do this is to use the <tt>SH_CALL</tt> macro:<br />
<cpp>SH_CALL(Instance, HookFunction)(params)</cpp><br />
<br />
Example:<br />
<cpp>SH_CALL(engine, &IVEngineServer::LogPrint)("Secret Message");</cpp><br />
<br />
Similarly, manual hooks have <tt>SH_MCALL</tt>:<br />
<br />
<cpp>SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);<br />
<br />
bool Function2_Bypass(int clam)<br />
{<br />
return SH_MCALL(iface, MHook_Function2)(clam);<br />
}</cpp><br />
<br />
==Deprecated Syntax==<br />
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.<br />
<br />
<br />
=Other Macros=<br />
SourceHook contains a large variety of extra macros. This section is a grab bag of the more commonly used ones.<br />
<br />
==Interface Pointers from Hooks==<br />
Let's say you have the following hook:<br />
<br />
<cpp>class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage);<br />
float GetDamageModifier();<br />
};<br />
<br />
void Hook_TakeDamage(int damage);<br />
</cpp><br />
<br />
How can you get the <tt>Player</tt> instance while in the hook? This can be achieved via the <tt>META_IFACEPTR</tt> macro. Example:<br />
<br />
<cpp>void Hook_TakeDamage(int damage)<br />
{<br />
Player *pPlayer = META_IFACEPTR(Player);<br />
<br />
int new_damage = (int)((pPlayer->GetDamageModifier() + 0.3) * (float)damage);<br />
<br />
RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (new_damage));<br />
}</cpp><br />
<br />
Note that the class name should be passed to <tt>META_IFACEPTR</tt>, not the pointer type.<br />
<br />
==Ignoring Reference Returns==<br />
There is a special macro, <tt>RETURN_META_NOREF</tt>, for ignoring a return value for reference-returning functions. Example:<br />
<br />
<cpp><br />
class ISomething<br />
{<br />
public: <br />
virtual int & GetSomething() =0;<br />
};<br />
<br />
int & Hook_GetSomething()<br />
{<br />
RETURN_META_NOREF(MRES_IGNORED, int &);<br />
}</cpp><br />
<br />
=Automatic hookmanager generation=<br />
Normally, the SH_DECL_ macros generate a so-called "hook manager", a function which is invoked instead of the original function and then calls the hooks and processes their return and <tt>META_RES</tt> values.<br />
<br />
It is also possible to let SourceHook auto-generate the hook manager. This is neccessary if the function prototype is unknown at compile time (for example if hooks can be defined from third-party plugins of your plugin).<br />
<br />
Example Usage from a Metamod:Source plugin:<br />
<cpp><br />
#include "sourcehook_pibuilder.h"<br />
<br />
// We want to hook a void (int, float) function<br />
<br />
// Request IHookManagerAutoGen interface<br />
SourceHook::IHookManagerAutoGen *hmag =<br />
static_cast<SourceHook::IHookManagerAutoGen *>(ismm->MetaFactory(MMIFACE_SH_HOOKMANAUTOGEN, NULL, NULL));<br />
<br />
// (check for hmag == NULL)<br />
<br />
// Build prototype information (using CProtoInfoBuilder helper class).<br />
SourceHook::CProtoInfoBuilder protoInfo(SourceHook::ProtoInfo::CallConv_ThisCall);<br />
protoInfo.AddParam(sizeof(int), SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal,<br />
NULL, NULL, NULL, NULL);<br />
protoInfo.AddParam(sizeof(float), SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal,<br />
NULL, NULL, NULL, NULL);<br />
<br />
// Generate the hook manager<br />
HookManagerPubFunc generatedHookMan = hmag->MakeHookMan(protoInfo, 0 /* vtable offset */, 0 /* vtable index */);<br />
<br />
// Add the hook<br />
int hookid = g_SHPtr->AddHook(g_PLID, SourceHook::Hook_Normal/Hook_VP/Hook_DVP, iface_ptr, thisptr_offs,<br />
generatedHookMan, handler, post);<br />
<br />
// ... (use the hook)<br />
<br />
// After you're done using the hook (cleanup)<br />
// Remove the hook<br />
g_SHPtr->RemoveHookByID(hookid);<br />
<br />
// Release hook manager<br />
hmag->ReleaseHookMan(generatedHookMan);<br />
</cpp><br />
<tt>CProtoInfoBuilder::AddParam</tt> has 7 parameters:<br />
* size: size of the type. This is sizeof(type) even if it is passed by reference!<br />
* passtype: this can be PassType_Basic for integer types (char/short/int/...), PassType_Float for floating-point types (float/double) and PassType_Object for unions, structs and classes.<br />
* passflags: flags. Has to contain either PassFlag_ByVal or PassFlag_ByRef.<br />
* pNormalCtor: for PassType_Object, set this to the pointer to the user-defined default constructor of the object, if it has one.<br />
* pCCtor: for PassType_Object, set this to the pointer to the user-defined copy constructor of the object, if it has one.<br />
* pODtor: for PassType_Object, set this to the pointer to the user-defined destructor of the object, if it has one.<br />
* pAssignOperator: for PassType_Object, set this to the pointer to the user-defined assignment operator of the object, if it has one.<br />
<br />
If you are hooking a non-void function, also call <tt>CProtoInfoBuilder::SetReturnType</tt>. It has the same arguments as <tt>AddParam</tt>.<br />
<br />
The <tt>handler</tt> parameter of <tt>ISourceHook::AddHook</tt> is a pointer to the ISHDelegate interface.<br />
In C++, you construct ISHDelegates like this:<br />
<cpp><br />
class MyDelegate : public SourceHook::ISHDelegate<br />
{<br />
public:<br />
// vtable index 0<br />
virtual bool IsEqual(ISHDelegate *pOtherDeleg)<br />
{<br />
// pOtherDeleg is guaranteed to be from the same plugin.<br />
// This function is only used for compat with the old SH_REMOVE_HOOK method.<br />
// if you don't want to use that with your hooks, you can simply return false here.<br />
// if for some reason you need it, a good idea could be comparing the vtable pointer:<br />
return *reinterpret_cast<void**>(this) == *reinterpret_cast<void**>(pOtherDeleg);<br />
<br />
// But in general I'd just return false!<br />
}<br />
<br />
// vtable index 1<br />
virtual void DeleteThis()<br />
{<br />
delete this; // Called from SourceHook when this instance is not needed<br />
// and should be deleted.<br />
}<br />
<br />
// vtable index 2<br />
virtual ret_type Call(params)<br />
{<br />
// your code.<br />
// SH_DECL_ macros pass execution to the actual user's handler through a FastDelegate<br />
// which is stored as a member variable of the delegate class.<br />
}<br />
};<br />
</cpp><br />
<br />
The first parameter of the CProtoInfoBuilder constructor (see example code above) is the calling convention in an extended sense. At the moment it can be either CallConv_ThisCall or (CallConv_ThisCall | CallConv_HasVafmt). The second value means that the function has printf-like string formatting. Then, the finaly ''const char *, ...'' arguments will be added automatically, and the Deleagte::Call method should have one more parameter: ''const char *formattedString''.<br />
<br />
pNormalCtor/pCCtor/pODtor/pAssignOperator should be found using offsets / sigscanning. If you are a C++ plugin and know the type at compile time, you can also use the following class:<br />
<cpp><br />
// Address of constructor/destructor<br />
// (using wrappers)<br />
template <class T><br />
class Ctor_Thunk<br />
{<br />
public:<br />
void NormalConstructor()<br />
{<br />
new(this) T;<br />
}<br />
<br />
void CopyConstructor(const T &other)<br />
{<br />
new(this) T(other);<br />
}<br />
<br />
void Destructor()<br />
{<br />
reinterpret_cast<T*>(this)->~T();<br />
}<br />
<br />
const T& AssignOp(const T &other)<br />
{<br />
return (*reinterpret_cast<T*>(this) = other);<br />
}<br />
};<br />
<br />
<br />
template <class T><br />
void *FindFuncAddr(T mfp)<br />
{<br />
union<br />
{<br />
T a;<br />
void *b;<br />
} u;<br />
u.a = mfp;<br />
return u.b;<br />
}<br />
<br />
// Usage:<br />
FindFuncAddr(&Ctor_Thunk<type>::NormalConstructor)<br />
// (if the type is a reference type, use it without & here)<br />
</cpp><br />
<br />
=Embedding=<br />
Embedding SourceHook in your own application or library is very easy. The <tt>sourcehook.cpp</tt> file must be compiled or linked into your project. To instantiate the SourceHook engine, you must create a <tt>CSourceHookImpl</tt> instance. Example:<br />
<br />
<cpp><br />
/* Normally, just <sourcehook.h> is included, but this is needed to instantiate the engine. */<br />
#include <sourcehook/sourcehook_impl.h><br />
<br />
SourceHook::Impl::CSourceHookImpl g_SourceHook;<br />
</cpp><br />
<br />
To actually use SourceHook, it is necessary to have two global variables:<br />
*<tt>g_PLID</tt> - A unique integer that identifies the library using SourceHook. This is used for removing all hooks a library is using.<br />
*<tt>g_SHPtr</tt> - A pointer to the <tt>SourceHook::ISourceHook</tt> interface.<br />
<br />
Example header file:<br />
<cpp><br />
#include <sourcehook/sourcehook.h><br />
<br />
extern SourceHook::ISourceHook *g_SHPtr;<br />
extern int g_PLID;</cpp><br />
<br />
Example addition to the global code:<br />
<cpp><br />
SourceHook::ISourceHook *g_SHPtr = &g_SourceHook;<br />
int g_PLID = 0;</cpp><br />
<br />
==Multiple Libraries/Shared Hooks==<br />
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.<br />
<br />
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.<br />
<br />
*<tt>PausePlugin()</tt> - Silences any hooks from an ID, such that those hooks will not be called.<br />
*<tt>UnpausePlugin()</tt> - Un-silences any silenced hooks from an ID.<br />
*<tt>UnloadPlugin()</tt> - Clean-up any left-over hooks from an ID.</div>Dave Gombochttps://wiki.alliedmods.net/index.php?title=SourceHook_Development&diff=6137SourceHook Development2008-09-12T17:23:37Z<p>Dave Gomboc: /* Hook Functions */</p>
<hr />
<div>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.<br />
<br />
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.<br />
<br />
All code in SourceHook is part of the SourceHook namespace. Thus, it may be prudent to declare this before using SourceHook structures or types:<br />
<br />
<cpp>using namespace SourceHook;</cpp><br />
<br />
=Simple Hooks=<br />
SourceHook has the following steps of operation:<br />
<br />
* 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.<br />
* Hook the function - as a member function of another class or a regular static function.<br />
* 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.<br />
* 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.<br />
* After you are done using a hook, you must safely remove it before the object is destroyed (otherwise, memory will be leaked).<br />
<br />
==Declaration==<br />
As an example, take the following class prototype:<br />
<cpp><br />
class IVEngineServer<br />
{<br />
public:<br />
/*...*/<br />
virtual void LogPrint( const char *msg ) = 0;<br />
virtual bool IsDedicated() = 0;<br />
};<br />
<br />
extern IVEngineServer *engine;<br />
</cpp><br />
<br />
<br />
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:<br />
<br />
*{{bcode|SH_DECL_HOOK}}n - n is the number of parameters<br />
**The parameters are: Class name, member function name, attributes, overloaded?, the return type, and a list of the parameter types.<br />
*{{bcode|SH_DECL_HOOKn_void}} - n is the number of parameters<br />
**_void specifies that the function does not return a value. The format is the same as above except the "return type" parameter is missing.<br />
*'''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.<br />
<br />
Our macro will look like this:<br />
<pre><br />
SH_DECL_HOOK1_void(IVEngineServer, LogPrint, SH_NOATTRIB, 0, const char *);<br />
SH_DECL_HOOK0(IVEngineServer, IsDedicated, SH_NOATTRIB, 0, bool);<br />
</pre><br />
<br />
Broken down for the first line:<br />
*There is 1 parameter.<br />
*The function has no return value.<br />
*<tt>IVEngineServer</tt> is the class containing the function.<br />
*<tt>LogPrint</tt> is the function being hooked.<br />
*The function as no attributes (for example, it is not const).<br />
*The function is not overloaded.<br />
*The first (and only) argument is a <tt>const char *</tt><br />
<br />
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.<br />
<br />
==Hook Functions==<br />
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'':<br />
*<tt>MRES_IGNORED</tt> - The original function will be called.<br />
*<tt>MRES_HANDLED</tt> - Same as <tt>MRES_IGNORED</tt>, except subsequent hooks can assume this means something important was changed.<br />
*<tt>MRES_OVERRIDE</tt> - The original function will be called, but the new return value will be used instead of the one from the original function.<br />
*<tt>MRES_SUPERCEDE</tt> - The original function will not be called. The new return value (if any) will be used instead.<br />
<br />
These constants are defined in <tt><sourcehook.h></tt>, and are made available to Metamod:Source plugin developers via <tt><ISmmPlugin.h></tt>.<br />
<br />
Once all pre-hooks have been processed, SourceHook takes an action based on the "highest" hook action returned (<tt>MRES_IGNORED</tt> being lowest, <tt>MRES_SUPERCEDE</tt> 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.<br />
<br />
A hook's action is signalled via one of two macros:<br />
*<tt>RETURN_META</tt> - Only usable from <tt>void</tt> functions. Signals the action to take, then returns.<br />
*<tt>RETURN_META_VALUE</tt> - Only usable from non-<tt>void</tt> functions. Signals the action to take, then returns the supplied value.<br />
<br />
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:<br />
*<tt>SH_STATIC(Function)</tt> - Hook to a static function.<br />
*<tt>SH_MEMBER(Instance, Function)</tt> - Hook to a member function.<br />
<br />
<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 function X::Y in instance #8, then the hook will only be invoked from instance #8. <b>Multiple hooks can be declared on the same instance, and multiple instances can be bound to the same hook, but one hook will only be invoked for the instances it was hooked to.</b><br />
<br />
To have hooks that work across all instances, and thus do not need to be delegated per-instance, see the "Global Hooks" section. As of SourceHook v5, it is safe to remove hooks on a destroyed instance, as the instance is not actually dereferenced. However, its virtual table must still be accessible.<br />
<br />
==Adding Hooks==<br />
The macro to add hooks is <tt>SH_[ADD|REMOVE]_HOOK</tt>. The syntax is:<br />
<br />
<cpp>SH_[ADD|REMOVE]_HOOK(Interface, Function, Instance, Hook, [post? true,false])</cpp><br />
<br />
An example of adding a <tt>LogPrint</tt> hook:<br />
<cpp>void Hook_LogPrint(const char *msg)<br />
{<br />
if (strcmp(msg, "If this string matches the function will be blocked") == 0)<br />
{<br />
RETURN_META(MRES_SUPERCEDE);<br />
}<br />
<br />
/* Not needed, but good style */<br />
RETURN_META(MRES_IGNORED);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
log_hook = SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);<br />
}</cpp><br />
<br />
The syntax is similar for hooking to member functions. Example equivalent to the above:<br />
<cpp><br />
class MyHooks<br />
{<br />
public:<br />
void Hook_LogPrint(const char *msg)<br />
{<br />
if (strcmp(msg, "If this string matches the function will be blocked") == 0)<br />
{<br />
RETURN_META(MRES_SUPERCEDE);<br />
}<br />
<br />
/* Not needed, but good style */<br />
RETURN_META(MRES_IGNORED);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);<br />
}<br />
};</cpp><br />
<br />
<br />
=Manual Hooks=<br />
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:<br />
<br />
Interface v1:<br />
<cpp><br />
class Interface<br />
{<br />
public:<br />
virtual void Function1() =0;<br />
virtual bool Function2(int clam) =0;<br />
};</cpp><br />
<br />
Interface v2:<br />
<cpp><br />
class Interface<br />
{<br />
public:<br />
virtual bool Function2(int clam) =0;<br />
};</cpp><br />
<br />
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.<br />
<br />
==Declaration==<br />
Declaring a manual hook is similar to declaring a normal/simple hook. The syntax is:<br />
<br />
<cpp>SH_DECL_MANUALHOOK<n>[_void](UniqueName, vtblIndex, vtblOffs, thisOffs, [return and param types])</cpp><br />
<br />
The <tt>UniqueName</tt> is a unique identifier for the hook. The <tt>vtblIndex</tt> is the index into the virtual table at which the function lies. In most compilers, this index starts from 0. The <tt>vtblOffs</tt> and <tt>thisOffs</tt> fields are used for multiple inheritance and are almost always 0 in modern compiler single inheritance.<br />
<br />
An example of hooking the two functions from the first interface version:<br />
<cpp>SH_DECL_MANUALHOOK0_void(MHook_Function1, 0, 0, 0);<br />
SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);</cpp><br />
<br />
==Reconfiguring==<br />
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 <tt>Function2</tt> hook in the case of the second version being detected:<br />
<br />
<cpp><br />
void SwitchToNewerHooks()<br />
{<br />
SH_MANUALHOOK_RECONFIGURE(MHook_Function2, 0, 0, 0);<br />
}<br />
</cpp><br />
<br />
Note that the hook was referenced by its unique identifier.<br />
<br />
==Adding Hooks==<br />
Adding or removing hook binds is done via the following extra macros:<br />
*<tt>SH_ADD_MANUALHOOK</tt><br />
*<tt>SH_REMOVE_MANUALHOOK</tt><br />
<br />
These work similar to the original functions. Example:<br />
<cpp>extern Interface *iface;<br />
<br />
bool Hook_Function2(int clam)<br />
{<br />
RETURN_META_VALUE(MRES_IGNORED, false);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
SH_ADD_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);<br />
}</cpp><br />
<br />
Similarly, a member function version would use <tt>SH_MEMBER</tt> instead.<br />
<br />
<br />
=Extended Removal Syntax=<br />
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, optional. There is another way to remove hooks.<br />
<br />
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.<br />
<br />
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.<br />
<br />
<br />
=Old Macros=<br />
SourceHook v5.0 deprecates older macros that were used in earlier versions. The macros are:<br />
*<tt>SH_ADD_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_REMOVE_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_ADD_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_REMOVE_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_ADD_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_REMOVE_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_ADD_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_REMOVE_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.<br />
<br />
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. <br />
<br />
This syntax is considered deprecated, but it is still supported. Code written with these macros will continue to compile against older SourceHook versions. If you are writing a plugin which must work against Metamod:Source 1.4 and 1.6, you will want to use the older macros for simplicity.<br />
<br />
<br />
=Global Hooks=<br />
<b>Note:</b> Global Hooks are only available in SourceHook v4.5 or later.<br />
<br />
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:<br />
<br />
<cpp><br />
class CBaseEntity<br />
{<br />
public:<br />
virtual void SetHealth(int health) =0;<br />
};<br />
<br />
class CBaseCat : public CBaseEntity<br />
{<br />
public:<br />
virtual void SetHealth();<br />
};<br />
<br />
class CBaseKitten : public CBaseCat<br />
{<br />
public:<br />
virtual void SetHealth();<br />
};</cpp><br />
<br />
In this example, <tt>CBaseCat</tt> and <tt>CBaseKitten</tt> instances have ''separate virtual tables''. Although they both derive from <tt>CBaseEntity</tt>, they are separate virtual objects. <b>Therefore, a global hook on <tt>CBaseEntity</tt> will receive no invocations, and a hook on <tt>CBaseCat</tt> will receive only <tt>CBaseCat</tt> instances, as long as the instance is not a class derived from <tt>CBaseCat</tt></b>.<br />
<br />
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:<br />
*An instance of the class can be passed.<br />
*The direct address to the virtual table can be passed.<br />
<br />
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).<br />
<br />
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.<br />
<br />
The macro <tt>META_IFACEPTR</tt> is especially useful for global hooks. See [[SourceHook_Development#Interface_Pointers_from_Hooks|Interface Pointers from Hooks]] near the end.<br />
<br />
Lastly, global hooks exclusively use the extended hooking syntax. That means there exists only <tt>SH_ADD</tt> macros. <tt>SH_HOOK_REMOVE_ID</tt> must be used and the hook ID generated via <tt>SH_ADD</tt> must be cached.<br />
<br />
All examples will use the following code as a basis:<br />
<cpp><br />
class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage) =0;<br />
};<br />
<br />
void Hook_TakeDamage(int damage);<br />
</cpp><br />
<br />
==Simple Hooks==<br />
Two extra macros exist for adding a global hook:<br />
<br />
<cpp><br />
SH_ADD_VPHOOK(Interface, Function, Instance, Handler, Post)<br />
SH_ADD_DVPHOOK(Interface, Function, VirtualTable, Handler, Post)<br />
</cpp><br />
<br />
An example:<br />
<cpp><br />
extern void *player_vtable;<br />
HookId takedamage_hook = 0;<br />
<br />
void StartHooks()<br />
{<br />
takedamage_hook = SH_ADD_DVPHOOK(Player, TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
if (takedamage_hook)<br />
{<br />
SH_REMOVE_HOOK_ID(takedamage_hook);<br />
}<br />
}</cpp><br />
<br />
==Manual Hooks==<br />
Similarly, manual hooks are straightforward:<br />
<cpp><br />
SH_DECL_MANUALHOOK1_void(MHook_TakeDamage, 0, 0, 0, int);<br />
<br />
extern void *player_vtable;<br />
int takedamage_hook = 0;<br />
<br />
void StartHooks()<br />
{<br />
takedamage_hook = SH_ADD_MANUALDVPHOOK(MHook_TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
if (takedamage_hook)<br />
{<br />
SH_REMOVE_HOOK_ID(takedamage_hook);<br />
}<br />
}</cpp><br />
<br />
<br />
=Modifying Parameters=<br />
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:<br />
*Caller passes 5 into X.<br />
*Hook changes the 5 to a 6.<br />
*Hooked function receives a 6 and continues normally.<br />
<br />
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 <tt>Player::TakeDamage</tt> to the <tt>Hook_TakeDamage</tt> function.<br />
<br />
<cpp><br />
class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage);<br />
};<br />
<br />
void Hook_TakeDamage(int damage);</cpp><br />
<br />
Our objective is to multiply the damage by 2.<br />
<br />
==Simple Hooks==<br />
For simple hooks, changing parameters looks similar to an <tt>SH_CALL</tt>. The macros are:<br />
<br />
<cpp>RETURN_META_NEWPARAMS(Action, HookFunction, ([params]))<br />
RETURN_META_VALUE_NEWPARAMS(Action, Value, HookFunction, ([params]))<br />
</cpp><br />
<br />
Example:<br />
<cpp><br />
void Hook_TakeDamage(int damage)<br />
{<br />
RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (damage * 2));<br />
}</cpp><br />
<br />
Note that the parenthesis enclosing the parameters are required.<br />
<br />
==Manual Hooks==<br />
Manual hooks require slightly different macros. They are:<br />
<br />
<cpp>RETURN_META_MNEWPARAMS(Action, UniqueName, ([params]));<br />
RETURN_META_VALUE_MNEWPARAMS(Action, Value, UniqueName, ([params]));</cpp><br />
<br />
Example:<br />
<cpp><br />
SH_DECL_MANUALHOOK1_void(MHook_Player_TakeDamage, 0, 0, 0, int);<br />
<br />
void Hook_TakeDamage(int damage)<br />
{<br />
RETURN_META_MNEWPARAMS(MRES_IGNORED, MHook_Player_TakeDamage, (damage *2));<br />
}</cpp><br />
<br />
Note that the parenthesis enclosing the parameters are required.<br />
<br />
<br />
=Bypassing Hooks=<br />
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.<br />
<br />
To way to do this is to use the <tt>SH_CALL</tt> macro:<br />
<cpp>SH_CALL(Instance, HookFunction)(params)</cpp><br />
<br />
Example:<br />
<cpp>SH_CALL(engine, &IVEngineServer::LogPrint)("Secret Message");</cpp><br />
<br />
Similarly, manual hooks have <tt>SH_MCALL</tt>:<br />
<br />
<cpp>SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);<br />
<br />
bool Function2_Bypass(int clam)<br />
{<br />
return SH_MCALL(iface, MHook_Function2)(clam);<br />
}</cpp><br />
<br />
==Deprecated Syntax==<br />
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.<br />
<br />
<br />
=Other Macros=<br />
SourceHook contains a large variety of extra macros. This section is a grab bag of the more commonly used ones.<br />
<br />
==Interface Pointers from Hooks==<br />
Let's say you have the following hook:<br />
<br />
<cpp>class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage);<br />
float GetDamageModifier();<br />
};<br />
<br />
void Hook_TakeDamage(int damage);<br />
</cpp><br />
<br />
How can you get the <tt>Player</tt> instance while in the hook? This can be achieved via the <tt>META_IFACEPTR</tt> macro. Example:<br />
<br />
<cpp>void Hook_TakeDamage(int damage)<br />
{<br />
Player *pPlayer = META_IFACEPTR(Player);<br />
<br />
int new_damage = (int)((pPlayer->GetDamageModifier() + 0.3) * (float)damage);<br />
<br />
RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (new_damage));<br />
}</cpp><br />
<br />
Note that the class name should be passed to <tt>META_IFACEPTR</tt>, not the pointer type.<br />
<br />
==Ignoring Reference Returns==<br />
There is a special macro, <tt>RETURN_META_NOREF</tt>, for ignoring a return value for reference-returning functions. Example:<br />
<br />
<cpp><br />
class ISomething<br />
{<br />
public: <br />
virtual int & GetSomething() =0;<br />
};<br />
<br />
int & Hook_GetSomething()<br />
{<br />
RETURN_META_NOREF(MRES_IGNORED, int &);<br />
}</cpp><br />
<br />
=Automatic hookmanager generation=<br />
Normally, the SH_DECL_ macros generate a so-called "hook manager", a function which is invoked instead of the original function and then calls the hooks and processes their return and <tt>META_RES</tt> values.<br />
<br />
It is also possible to let SourceHook auto-generate the hook manager. This is neccessary if the function prototype is unknown at compile time (for example if hooks can be defined from third-party plugins of your plugin).<br />
<br />
Example Usage from a Metamod:Source plugin:<br />
<cpp><br />
#include "sourcehook_pibuilder.h"<br />
<br />
// We want to hook a void (int, float) function<br />
<br />
// Request IHookManagerAutoGen interface<br />
SourceHook::IHookManagerAutoGen *hmag =<br />
static_cast<SourceHook::IHookManagerAutoGen *>(ismm->MetaFactory(MMIFACE_SH_HOOKMANAUTOGEN, NULL, NULL));<br />
<br />
// (check for hmag == NULL)<br />
<br />
// Build prototype information (using CProtoInfoBuilder helper class).<br />
SourceHook::CProtoInfoBuilder protoInfo(SourceHook::ProtoInfo::CallConv_ThisCall);<br />
protoInfo.AddParam(sizeof(int), SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal,<br />
NULL, NULL, NULL, NULL);<br />
protoInfo.AddParam(sizeof(float), SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal,<br />
NULL, NULL, NULL, NULL);<br />
<br />
// Generate the hook manager<br />
HookManagerPubFunc generatedHookMan = hmag->MakeHookMan(protoInfo, 0 /* vtable offset */, 0 /* vtable index */);<br />
<br />
// Add the hook<br />
int hookid = g_SHPtr->AddHook(g_PLID, SourceHook::Hook_Normal/Hook_VP/Hook_DVP, iface_ptr, thisptr_offs,<br />
generatedHookMan, handler, post);<br />
<br />
// ... (use the hook)<br />
<br />
// After you're done using the hook (cleanup)<br />
// Remove the hook<br />
g_SHPtr->RemoveHookByID(hookid);<br />
<br />
// Release hook manager<br />
hmag->ReleaseHookMan(generatedHookMan);<br />
</cpp><br />
<tt>CProtoInfoBuilder::AddParam</tt> has 7 parameters:<br />
* size: size of the type. This is sizeof(type) even if it is passed by reference!<br />
* passtype: this can be PassType_Basic for integer types (char/short/int/...), PassType_Float for floating-point types (float/double) and PassType_Object for unions, structs and classes.<br />
* passflags: flags. Has to contain either PassFlag_ByVal or PassFlag_ByRef.<br />
* pNormalCtor: for PassType_Object, set this to the pointer to the user-defined default constructor of the object, if it has one.<br />
* pCCtor: for PassType_Object, set this to the pointer to the user-defined copy constructor of the object, if it has one.<br />
* pODtor: for PassType_Object, set this to the pointer to the user-defined destructor of the object, if it has one.<br />
* pAssignOperator: for PassType_Object, set this to the pointer to the user-defined assignment operator of the object, if it has one.<br />
<br />
If you are hooking a non-void function, also call <tt>CProtoInfoBuilder::SetReturnType</tt>. It has the same arguments as <tt>AddParam</tt>.<br />
<br />
The <tt>handler</tt> parameter of <tt>ISourceHook::AddHook</tt> is a pointer to the ISHDelegate interface.<br />
In C++, you construct ISHDelegates like this:<br />
<cpp><br />
class MyDelegate : public SourceHook::ISHDelegate<br />
{<br />
public:<br />
// vtable index 0<br />
virtual bool IsEqual(ISHDelegate *pOtherDeleg)<br />
{<br />
// pOtherDeleg is guaranteed to be from the same plugin.<br />
// This function is only used for compat with the old SH_REMOVE_HOOK method.<br />
// if you don't want to use that with your hooks, you can simply return false here.<br />
// if for some reason you need it, a good idea could be comparing the vtable pointer:<br />
return *reinterpret_cast<void**>(this) == *reinterpret_cast<void**>(pOtherDeleg);<br />
<br />
// But in general I'd just return false!<br />
}<br />
<br />
// vtable index 1<br />
virtual void DeleteThis()<br />
{<br />
delete this; // Called from SourceHook when this instance is not needed<br />
// and should be deleted.<br />
}<br />
<br />
// vtable index 2<br />
virtual ret_type Call(params)<br />
{<br />
// your code.<br />
// SH_DECL_ macros pass execution to the actual user's handler through a FastDelegate<br />
// which is stored as a member variable of the delegate class.<br />
}<br />
};<br />
</cpp><br />
<br />
The first parameter of the CProtoInfoBuilder constructor (see example code above) is the calling convention in an extended sense. At the moment it can be either CallConv_ThisCall or (CallConv_ThisCall | CallConv_HasVafmt). The second value means that the function has printf-like string formatting. Then, the finaly ''const char *, ...'' arguments will be added automatically, and the Deleagte::Call method should have one more parameter: ''const char *formattedString''.<br />
<br />
pNormalCtor/pCCtor/pODtor/pAssignOperator should be found using offsets / sigscanning. If you are a C++ plugin and know the type at compile time, you can also use the following class:<br />
<cpp><br />
// Address of constructor/destructor<br />
// (using wrappers)<br />
template <class T><br />
class Ctor_Thunk<br />
{<br />
public:<br />
void NormalConstructor()<br />
{<br />
new(this) T;<br />
}<br />
<br />
void CopyConstructor(const T &other)<br />
{<br />
new(this) T(other);<br />
}<br />
<br />
void Destructor()<br />
{<br />
reinterpret_cast<T*>(this)->~T();<br />
}<br />
<br />
const T& AssignOp(const T &other)<br />
{<br />
return (*reinterpret_cast<T*>(this) = other);<br />
}<br />
};<br />
<br />
<br />
template <class T><br />
void *FindFuncAddr(T mfp)<br />
{<br />
union<br />
{<br />
T a;<br />
void *b;<br />
} u;<br />
u.a = mfp;<br />
return u.b;<br />
}<br />
<br />
// Usage:<br />
FindFuncAddr(&Ctor_Thunk<type>::NormalConstructor)<br />
// (if the type is a reference type, use it without & here)<br />
</cpp><br />
<br />
=Embedding=<br />
Embedding SourceHook in your own application or library is very easy. The <tt>sourcehook.cpp</tt> file must be compiled or linked into your project. To instantiate the SourceHook engine, you must create a <tt>CSourceHookImpl</tt> instance. Example:<br />
<br />
<cpp><br />
/* Normally, just <sourcehook.h> is included, but this is needed to instantiate the engine. */<br />
#include <sourcehook/sourcehook_impl.h><br />
<br />
SourceHook::Impl::CSourceHookImpl g_SourceHook;<br />
</cpp><br />
<br />
To actually use SourceHook, it is necessary to have two global variables:<br />
*<tt>g_PLID</tt> - A unique integer that identifies the library using SourceHook. This is used for removing all hooks a library is using.<br />
*<tt>g_SHPtr</tt> - A pointer to the <tt>SourceHook::ISourceHook</tt> interface.<br />
<br />
Example header file:<br />
<cpp><br />
#include <sourcehook/sourcehook.h><br />
<br />
extern SourceHook::ISourceHook *g_SHPtr;<br />
extern int g_PLID;</cpp><br />
<br />
Example addition to the global code:<br />
<cpp><br />
SourceHook::ISourceHook *g_SHPtr = &g_SourceHook;<br />
int g_PLID = 0;</cpp><br />
<br />
==Multiple Libraries/Shared Hooks==<br />
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.<br />
<br />
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.<br />
<br />
*<tt>PausePlugin()</tt> - Silences any hooks from an ID, such that those hooks will not be called.<br />
*<tt>UnpausePlugin()</tt> - Un-silences any silenced hooks from an ID.<br />
*<tt>UnloadPlugin()</tt> - Clean-up any left-over hooks from an ID.</div>Dave Gombochttps://wiki.alliedmods.net/index.php?title=SourceHook_Development&diff=6136SourceHook Development2008-09-12T16:32:12Z<p>Dave Gomboc: /* Hook Functions */</p>
<hr />
<div>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.<br />
<br />
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.<br />
<br />
All code in SourceHook is part of the SourceHook namespace. Thus, it may be prudent to declare this before using SourceHook structures or types:<br />
<br />
<cpp>using namespace SourceHook;</cpp><br />
<br />
=Simple Hooks=<br />
SourceHook has the following steps of operation:<br />
<br />
* 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.<br />
* Hook the function - as a member function of another class or a regular static function.<br />
* 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.<br />
* 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.<br />
* After you are done using a hook, you must safely remove it before the object is destroyed (otherwise, memory will be leaked).<br />
<br />
==Declaration==<br />
As an example, take the following class prototype:<br />
<cpp><br />
class IVEngineServer<br />
{<br />
public:<br />
/*...*/<br />
virtual void LogPrint( const char *msg ) = 0;<br />
virtual bool IsDedicated() = 0;<br />
};<br />
<br />
extern IVEngineServer *engine;<br />
</cpp><br />
<br />
<br />
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:<br />
<br />
*{{bcode|SH_DECL_HOOK}}n - n is the number of parameters<br />
**The parameters are: Class name, member function name, attributes, overloaded?, the return type, and a list of the parameter types.<br />
*{{bcode|SH_DECL_HOOKn_void}} - n is the number of parameters<br />
**_void specifies that the function does not return a value. The format is the same as above except the "return type" parameter is missing.<br />
*'''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.<br />
<br />
Our macro will look like this:<br />
<pre><br />
SH_DECL_HOOK1_void(IVEngineServer, LogPrint, SH_NOATTRIB, 0, const char *);<br />
SH_DECL_HOOK0(IVEngineServer, IsDedicated, SH_NOATTRIB, 0, bool);<br />
</pre><br />
<br />
Broken down for the first line:<br />
*There is 1 parameter.<br />
*The function has no return value.<br />
*<tt>IVEngineServer</tt> is the class containing the function.<br />
*<tt>LogPrint</tt> is the function being hooked.<br />
*The function as no attributes (for example, it is not const).<br />
*The function is not overloaded.<br />
*The first (and only) argument is a <tt>const char *</tt><br />
<br />
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.<br />
<br />
==Hook Functions==<br />
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'':<br />
*<tt>META_IGNORED</tt> - The original function will be called.<br />
*<tt>META_HANDLED</tt> - Same as <tt>META_IGNORED</tt>, except subsequent hooks can assume this means something important was changed.<br />
*<tt>META_OVERRIDE</tt> - The original function will be called, but the new return value will be used instead of the one from the original function.<br />
*<tt>META_SUPERCEDE</tt> - The original function will not be called. The new return value (if any) will be used instead.<br />
<br />
These constants are defined in <tt><sourcehook.h></tt>, and are made available to Metamod:Source plugin developers via <tt><ISmmPlugin.h></tt>.<br />
<br />
Once all pre-hooks have been processed, SourceHook takes an action based on the "highest" hook action returned (<tt>META_IGNORED</tt> being lowest, <tt>META_SUPERCEDE</tt> 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.<br />
<br />
A hook's action is signalled via one of two macros:<br />
*<tt>RETURN_META</tt> - Only usable from <tt>void</tt> functions. Signals the action to take, then returns.<br />
*<tt>RETURN_META_VALUE</tt> - Only usable from non-<tt>void</tt> functions. Signals the action to take, then returns the supplied value.<br />
<br />
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:<br />
*<tt>SH_STATIC(Function)</tt> - Hook to a static function.<br />
*<tt>SH_MEMBER(Instance, Function)</tt> - Hook to a member function.<br />
<br />
<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 function X::Y in instance #8, then the hook will only be invoked from instance #8. <b>Multiple hooks can be declared on the same instance, and multiple instances can be bound to the same hook, but one hook will only be invoked for the instances it was hooked to.</b><br />
<br />
To have hooks that work across all instances, and thus do not need to be delegated per-instance, see the "Global Hooks" section. As of SourceHook v5, it is safe to remove hooks on a destroyed instance, as the instance is not actually dereferenced. However, its virtual table must still be accessible.<br />
<br />
==Adding Hooks==<br />
The macro to add hooks is <tt>SH_[ADD|REMOVE]_HOOK</tt>. The syntax is:<br />
<br />
<cpp>SH_[ADD|REMOVE]_HOOK(Interface, Function, Instance, Hook, [post? true,false])</cpp><br />
<br />
An example of adding a <tt>LogPrint</tt> hook:<br />
<cpp>void Hook_LogPrint(const char *msg)<br />
{<br />
if (strcmp(msg, "If this string matches the function will be blocked") == 0)<br />
{<br />
RETURN_META(MRES_SUPERCEDE);<br />
}<br />
<br />
/* Not needed, but good style */<br />
RETURN_META(MRES_IGNORED);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
log_hook = SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);<br />
}</cpp><br />
<br />
The syntax is similar for hooking to member functions. Example equivalent to the above:<br />
<cpp><br />
class MyHooks<br />
{<br />
public:<br />
void Hook_LogPrint(const char *msg)<br />
{<br />
if (strcmp(msg, "If this string matches the function will be blocked") == 0)<br />
{<br />
RETURN_META(MRES_SUPERCEDE);<br />
}<br />
<br />
/* Not needed, but good style */<br />
RETURN_META(MRES_IGNORED);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);<br />
}<br />
};</cpp><br />
<br />
<br />
=Manual Hooks=<br />
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:<br />
<br />
Interface v1:<br />
<cpp><br />
class Interface<br />
{<br />
public:<br />
virtual void Function1() =0;<br />
virtual bool Function2(int clam) =0;<br />
};</cpp><br />
<br />
Interface v2:<br />
<cpp><br />
class Interface<br />
{<br />
public:<br />
virtual bool Function2(int clam) =0;<br />
};</cpp><br />
<br />
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.<br />
<br />
==Declaration==<br />
Declaring a manual hook is similar to declaring a normal/simple hook. The syntax is:<br />
<br />
<cpp>SH_DECL_MANUALHOOK<n>[_void](UniqueName, vtblIndex, vtblOffs, thisOffs, [return and param types])</cpp><br />
<br />
The <tt>UniqueName</tt> is a unique identifier for the hook. The <tt>vtblIndex</tt> is the index into the virtual table at which the function lies. In most compilers, this index starts from 0. The <tt>vtblOffs</tt> and <tt>thisOffs</tt> fields are used for multiple inheritance and are almost always 0 in modern compiler single inheritance.<br />
<br />
An example of hooking the two functions from the first interface version:<br />
<cpp>SH_DECL_MANUALHOOK0_void(MHook_Function1, 0, 0, 0);<br />
SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);</cpp><br />
<br />
==Reconfiguring==<br />
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 <tt>Function2</tt> hook in the case of the second version being detected:<br />
<br />
<cpp><br />
void SwitchToNewerHooks()<br />
{<br />
SH_MANUALHOOK_RECONFIGURE(MHook_Function2, 0, 0, 0);<br />
}<br />
</cpp><br />
<br />
Note that the hook was referenced by its unique identifier.<br />
<br />
==Adding Hooks==<br />
Adding or removing hook binds is done via the following extra macros:<br />
*<tt>SH_ADD_MANUALHOOK</tt><br />
*<tt>SH_REMOVE_MANUALHOOK</tt><br />
<br />
These work similar to the original functions. Example:<br />
<cpp>extern Interface *iface;<br />
<br />
bool Hook_Function2(int clam)<br />
{<br />
RETURN_META_VALUE(MRES_IGNORED, false);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
SH_ADD_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);<br />
}</cpp><br />
<br />
Similarly, a member function version would use <tt>SH_MEMBER</tt> instead.<br />
<br />
<br />
=Extended Removal Syntax=<br />
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, optional. There is another way to remove hooks.<br />
<br />
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.<br />
<br />
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.<br />
<br />
<br />
=Old Macros=<br />
SourceHook v5.0 deprecates older macros that were used in earlier versions. The macros are:<br />
*<tt>SH_ADD_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_REMOVE_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_ADD_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_REMOVE_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_ADD_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_REMOVE_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_ADD_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_REMOVE_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.<br />
<br />
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. <br />
<br />
This syntax is considered deprecated, but it is still supported. Code written with these macros will continue to compile against older SourceHook versions. If you are writing a plugin which must work against Metamod:Source 1.4 and 1.6, you will want to use the older macros for simplicity.<br />
<br />
<br />
=Global Hooks=<br />
<b>Note:</b> Global Hooks are only available in SourceHook v4.5 or later.<br />
<br />
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:<br />
<br />
<cpp><br />
class CBaseEntity<br />
{<br />
public:<br />
virtual void SetHealth(int health) =0;<br />
};<br />
<br />
class CBaseCat : public CBaseEntity<br />
{<br />
public:<br />
virtual void SetHealth();<br />
};<br />
<br />
class CBaseKitten : public CBaseCat<br />
{<br />
public:<br />
virtual void SetHealth();<br />
};</cpp><br />
<br />
In this example, <tt>CBaseCat</tt> and <tt>CBaseKitten</tt> instances have ''separate virtual tables''. Although they both derive from <tt>CBaseEntity</tt>, they are separate virtual objects. <b>Therefore, a global hook on <tt>CBaseEntity</tt> will receive no invocations, and a hook on <tt>CBaseCat</tt> will receive only <tt>CBaseCat</tt> instances, as long as the instance is not a class derived from <tt>CBaseCat</tt></b>.<br />
<br />
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:<br />
*An instance of the class can be passed.<br />
*The direct address to the virtual table can be passed.<br />
<br />
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).<br />
<br />
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.<br />
<br />
The macro <tt>META_IFACEPTR</tt> is especially useful for global hooks. See [[SourceHook_Development#Interface_Pointers_from_Hooks|Interface Pointers from Hooks]] near the end.<br />
<br />
Lastly, global hooks exclusively use the extended hooking syntax. That means there exists only <tt>SH_ADD</tt> macros. <tt>SH_HOOK_REMOVE_ID</tt> must be used and the hook ID generated via <tt>SH_ADD</tt> must be cached.<br />
<br />
All examples will use the following code as a basis:<br />
<cpp><br />
class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage) =0;<br />
};<br />
<br />
void Hook_TakeDamage(int damage);<br />
</cpp><br />
<br />
==Simple Hooks==<br />
Two extra macros exist for adding a global hook:<br />
<br />
<cpp><br />
SH_ADD_VPHOOK(Interface, Function, Instance, Handler, Post)<br />
SH_ADD_DVPHOOK(Interface, Function, VirtualTable, Handler, Post)<br />
</cpp><br />
<br />
An example:<br />
<cpp><br />
extern void *player_vtable;<br />
HookId takedamage_hook = 0;<br />
<br />
void StartHooks()<br />
{<br />
takedamage_hook = SH_ADD_DVPHOOK(Player, TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
if (takedamage_hook)<br />
{<br />
SH_REMOVE_HOOK_ID(takedamage_hook);<br />
}<br />
}</cpp><br />
<br />
==Manual Hooks==<br />
Similarly, manual hooks are straightforward:<br />
<cpp><br />
SH_DECL_MANUALHOOK1_void(MHook_TakeDamage, 0, 0, 0, int);<br />
<br />
extern void *player_vtable;<br />
int takedamage_hook = 0;<br />
<br />
void StartHooks()<br />
{<br />
takedamage_hook = SH_ADD_MANUALDVPHOOK(MHook_TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
if (takedamage_hook)<br />
{<br />
SH_REMOVE_HOOK_ID(takedamage_hook);<br />
}<br />
}</cpp><br />
<br />
<br />
=Modifying Parameters=<br />
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:<br />
*Caller passes 5 into X.<br />
*Hook changes the 5 to a 6.<br />
*Hooked function receives a 6 and continues normally.<br />
<br />
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 <tt>Player::TakeDamage</tt> to the <tt>Hook_TakeDamage</tt> function.<br />
<br />
<cpp><br />
class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage);<br />
};<br />
<br />
void Hook_TakeDamage(int damage);</cpp><br />
<br />
Our objective is to multiply the damage by 2.<br />
<br />
==Simple Hooks==<br />
For simple hooks, changing parameters looks similar to an <tt>SH_CALL</tt>. The macros are:<br />
<br />
<cpp>RETURN_META_NEWPARAMS(Action, HookFunction, ([params]))<br />
RETURN_META_VALUE_NEWPARAMS(Action, Value, HookFunction, ([params]))<br />
</cpp><br />
<br />
Example:<br />
<cpp><br />
void Hook_TakeDamage(int damage)<br />
{<br />
RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (damage * 2));<br />
}</cpp><br />
<br />
Note that the parenthesis enclosing the parameters are required.<br />
<br />
==Manual Hooks==<br />
Manual hooks require slightly different macros. They are:<br />
<br />
<cpp>RETURN_META_MNEWPARAMS(Action, UniqueName, ([params]));<br />
RETURN_META_VALUE_MNEWPARAMS(Action, Value, UniqueName, ([params]));</cpp><br />
<br />
Example:<br />
<cpp><br />
SH_DECL_MANUALHOOK1_void(MHook_Player_TakeDamage, 0, 0, 0, int);<br />
<br />
void Hook_TakeDamage(int damage)<br />
{<br />
RETURN_META_MNEWPARAMS(MRES_IGNORED, MHook_Player_TakeDamage, (damage *2));<br />
}</cpp><br />
<br />
Note that the parenthesis enclosing the parameters are required.<br />
<br />
<br />
=Bypassing Hooks=<br />
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.<br />
<br />
To way to do this is to use the <tt>SH_CALL</tt> macro:<br />
<cpp>SH_CALL(Instance, HookFunction)(params)</cpp><br />
<br />
Example:<br />
<cpp>SH_CALL(engine, &IVEngineServer::LogPrint)("Secret Message");</cpp><br />
<br />
Similarly, manual hooks have <tt>SH_MCALL</tt>:<br />
<br />
<cpp>SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);<br />
<br />
bool Function2_Bypass(int clam)<br />
{<br />
return SH_MCALL(iface, MHook_Function2)(clam);<br />
}</cpp><br />
<br />
==Deprecated Syntax==<br />
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.<br />
<br />
<br />
=Other Macros=<br />
SourceHook contains a large variety of extra macros. This section is a grab bag of the more commonly used ones.<br />
<br />
==Interface Pointers from Hooks==<br />
Let's say you have the following hook:<br />
<br />
<cpp>class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage);<br />
float GetDamageModifier();<br />
};<br />
<br />
void Hook_TakeDamage(int damage);<br />
</cpp><br />
<br />
How can you get the <tt>Player</tt> instance while in the hook? This can be achieved via the <tt>META_IFACEPTR</tt> macro. Example:<br />
<br />
<cpp>void Hook_TakeDamage(int damage)<br />
{<br />
Player *pPlayer = META_IFACEPTR(Player);<br />
<br />
int new_damage = (int)((pPlayer->GetDamageModifier() + 0.3) * (float)damage);<br />
<br />
RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (new_damage));<br />
}</cpp><br />
<br />
Note that the class name should be passed to <tt>META_IFACEPTR</tt>, not the pointer type.<br />
<br />
==Ignoring Reference Returns==<br />
There is a special macro, <tt>RETURN_META_NOREF</tt>, for ignoring a return value for reference-returning functions. Example:<br />
<br />
<cpp><br />
class ISomething<br />
{<br />
public: <br />
virtual int & GetSomething() =0;<br />
};<br />
<br />
int & Hook_GetSomething()<br />
{<br />
RETURN_META_NOREF(MRES_IGNORED, int &);<br />
}</cpp><br />
<br />
=Automatic hookmanager generation=<br />
Normally, the SH_DECL_ macros generate a so-called "hook manager", a function which is invoked instead of the original function and then calls the hooks and processes their return and <tt>META_RES</tt> values.<br />
<br />
It is also possible to let SourceHook auto-generate the hook manager. This is neccessary if the function prototype is unknown at compile time (for example if hooks can be defined from third-party plugins of your plugin).<br />
<br />
Example Usage from a Metamod:Source plugin:<br />
<cpp><br />
#include "sourcehook_pibuilder.h"<br />
<br />
// We want to hook a void (int, float) function<br />
<br />
// Request IHookManagerAutoGen interface<br />
SourceHook::IHookManagerAutoGen *hmag =<br />
static_cast<SourceHook::IHookManagerAutoGen *>(ismm->MetaFactory(MMIFACE_SH_HOOKMANAUTOGEN, NULL, NULL));<br />
<br />
// (check for hmag == NULL)<br />
<br />
// Build prototype information (using CProtoInfoBuilder helper class).<br />
SourceHook::CProtoInfoBuilder protoInfo(SourceHook::ProtoInfo::CallConv_ThisCall);<br />
protoInfo.AddParam(sizeof(int), SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal,<br />
NULL, NULL, NULL, NULL);<br />
protoInfo.AddParam(sizeof(float), SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal,<br />
NULL, NULL, NULL, NULL);<br />
<br />
// Generate the hook manager<br />
HookManagerPubFunc generatedHookMan = hmag->MakeHookMan(protoInfo, 0 /* vtable offset */, 0 /* vtable index */);<br />
<br />
// Add the hook<br />
int hookid = g_SHPtr->AddHook(g_PLID, SourceHook::Hook_Normal/Hook_VP/Hook_DVP, iface_ptr, thisptr_offs,<br />
generatedHookMan, handler, post);<br />
<br />
// ... (use the hook)<br />
<br />
// After you're done using the hook (cleanup)<br />
// Remove the hook<br />
g_SHPtr->RemoveHookByID(hookid);<br />
<br />
// Release hook manager<br />
hmag->ReleaseHookMan(generatedHookMan);<br />
</cpp><br />
<tt>CProtoInfoBuilder::AddParam</tt> has 7 parameters:<br />
* size: size of the type. This is sizeof(type) even if it is passed by reference!<br />
* passtype: this can be PassType_Basic for integer types (char/short/int/...), PassType_Float for floating-point types (float/double) and PassType_Object for unions, structs and classes.<br />
* passflags: flags. Has to contain either PassFlag_ByVal or PassFlag_ByRef.<br />
* pNormalCtor: for PassType_Object, set this to the pointer to the user-defined default constructor of the object, if it has one.<br />
* pCCtor: for PassType_Object, set this to the pointer to the user-defined copy constructor of the object, if it has one.<br />
* pODtor: for PassType_Object, set this to the pointer to the user-defined destructor of the object, if it has one.<br />
* pAssignOperator: for PassType_Object, set this to the pointer to the user-defined assignment operator of the object, if it has one.<br />
<br />
If you are hooking a non-void function, also call <tt>CProtoInfoBuilder::SetReturnType</tt>. It has the same arguments as <tt>AddParam</tt>.<br />
<br />
The <tt>handler</tt> parameter of <tt>ISourceHook::AddHook</tt> is a pointer to the ISHDelegate interface.<br />
In C++, you construct ISHDelegates like this:<br />
<cpp><br />
class MyDelegate : public SourceHook::ISHDelegate<br />
{<br />
public:<br />
// vtable index 0<br />
virtual bool IsEqual(ISHDelegate *pOtherDeleg)<br />
{<br />
// pOtherDeleg is guaranteed to be from the same plugin.<br />
// This function is only used for compat with the old SH_REMOVE_HOOK method.<br />
// if you don't want to use that with your hooks, you can simply return false here.<br />
// if for some reason you need it, a good idea could be comparing the vtable pointer:<br />
return *reinterpret_cast<void**>(this) == *reinterpret_cast<void**>(pOtherDeleg);<br />
<br />
// But in general I'd just return false!<br />
}<br />
<br />
// vtable index 1<br />
virtual void DeleteThis()<br />
{<br />
delete this; // Called from SourceHook when this instance is not needed<br />
// and should be deleted.<br />
}<br />
<br />
// vtable index 2<br />
virtual ret_type Call(params)<br />
{<br />
// your code.<br />
// SH_DECL_ macros pass execution to the actual user's handler through a FastDelegate<br />
// which is stored as a member variable of the delegate class.<br />
}<br />
};<br />
</cpp><br />
<br />
The first parameter of the CProtoInfoBuilder constructor (see example code above) is the calling convention in an extended sense. At the moment it can be either CallConv_ThisCall or (CallConv_ThisCall | CallConv_HasVafmt). The second value means that the function has printf-like string formatting. Then, the finaly ''const char *, ...'' arguments will be added automatically, and the Deleagte::Call method should have one more parameter: ''const char *formattedString''.<br />
<br />
pNormalCtor/pCCtor/pODtor/pAssignOperator should be found using offsets / sigscanning. If you are a C++ plugin and know the type at compile time, you can also use the following class:<br />
<cpp><br />
// Address of constructor/destructor<br />
// (using wrappers)<br />
template <class T><br />
class Ctor_Thunk<br />
{<br />
public:<br />
void NormalConstructor()<br />
{<br />
new(this) T;<br />
}<br />
<br />
void CopyConstructor(const T &other)<br />
{<br />
new(this) T(other);<br />
}<br />
<br />
void Destructor()<br />
{<br />
reinterpret_cast<T*>(this)->~T();<br />
}<br />
<br />
const T& AssignOp(const T &other)<br />
{<br />
return (*reinterpret_cast<T*>(this) = other);<br />
}<br />
};<br />
<br />
<br />
template <class T><br />
void *FindFuncAddr(T mfp)<br />
{<br />
union<br />
{<br />
T a;<br />
void *b;<br />
} u;<br />
u.a = mfp;<br />
return u.b;<br />
}<br />
<br />
// Usage:<br />
FindFuncAddr(&Ctor_Thunk<type>::NormalConstructor)<br />
// (if the type is a reference type, use it without & here)<br />
</cpp><br />
<br />
=Embedding=<br />
Embedding SourceHook in your own application or library is very easy. The <tt>sourcehook.cpp</tt> file must be compiled or linked into your project. To instantiate the SourceHook engine, you must create a <tt>CSourceHookImpl</tt> instance. Example:<br />
<br />
<cpp><br />
/* Normally, just <sourcehook.h> is included, but this is needed to instantiate the engine. */<br />
#include <sourcehook/sourcehook_impl.h><br />
<br />
SourceHook::Impl::CSourceHookImpl g_SourceHook;<br />
</cpp><br />
<br />
To actually use SourceHook, it is necessary to have two global variables:<br />
*<tt>g_PLID</tt> - A unique integer that identifies the library using SourceHook. This is used for removing all hooks a library is using.<br />
*<tt>g_SHPtr</tt> - A pointer to the <tt>SourceHook::ISourceHook</tt> interface.<br />
<br />
Example header file:<br />
<cpp><br />
#include <sourcehook/sourcehook.h><br />
<br />
extern SourceHook::ISourceHook *g_SHPtr;<br />
extern int g_PLID;</cpp><br />
<br />
Example addition to the global code:<br />
<cpp><br />
SourceHook::ISourceHook *g_SHPtr = &g_SourceHook;<br />
int g_PLID = 0;</cpp><br />
<br />
==Multiple Libraries/Shared Hooks==<br />
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.<br />
<br />
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.<br />
<br />
*<tt>PausePlugin()</tt> - Silences any hooks from an ID, such that those hooks will not be called.<br />
*<tt>UnpausePlugin()</tt> - Un-silences any silenced hooks from an ID.<br />
*<tt>UnloadPlugin()</tt> - Clean-up any left-over hooks from an ID.</div>Dave Gombochttps://wiki.alliedmods.net/index.php?title=SourceHook_Development&diff=6135SourceHook Development2008-09-12T16:31:24Z<p>Dave Gomboc: /* Hook Functions */</p>
<hr />
<div>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.<br />
<br />
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.<br />
<br />
All code in SourceHook is part of the SourceHook namespace. Thus, it may be prudent to declare this before using SourceHook structures or types:<br />
<br />
<cpp>using namespace SourceHook;</cpp><br />
<br />
=Simple Hooks=<br />
SourceHook has the following steps of operation:<br />
<br />
* 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.<br />
* Hook the function - as a member function of another class or a regular static function.<br />
* 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.<br />
* 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.<br />
* After you are done using a hook, you must safely remove it before the object is destroyed (otherwise, memory will be leaked).<br />
<br />
==Declaration==<br />
As an example, take the following class prototype:<br />
<cpp><br />
class IVEngineServer<br />
{<br />
public:<br />
/*...*/<br />
virtual void LogPrint( const char *msg ) = 0;<br />
virtual bool IsDedicated() = 0;<br />
};<br />
<br />
extern IVEngineServer *engine;<br />
</cpp><br />
<br />
<br />
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:<br />
<br />
*{{bcode|SH_DECL_HOOK}}n - n is the number of parameters<br />
**The parameters are: Class name, member function name, attributes, overloaded?, the return type, and a list of the parameter types.<br />
*{{bcode|SH_DECL_HOOKn_void}} - n is the number of parameters<br />
**_void specifies that the function does not return a value. The format is the same as above except the "return type" parameter is missing.<br />
*'''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.<br />
<br />
Our macro will look like this:<br />
<pre><br />
SH_DECL_HOOK1_void(IVEngineServer, LogPrint, SH_NOATTRIB, 0, const char *);<br />
SH_DECL_HOOK0(IVEngineServer, IsDedicated, SH_NOATTRIB, 0, bool);<br />
</pre><br />
<br />
Broken down for the first line:<br />
*There is 1 parameter.<br />
*The function has no return value.<br />
*<tt>IVEngineServer</tt> is the class containing the function.<br />
*<tt>LogPrint</tt> is the function being hooked.<br />
*The function as no attributes (for example, it is not const).<br />
*The function is not overloaded.<br />
*The first (and only) argument is a <tt>const char *</tt><br />
<br />
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.<br />
<br />
==Hook Functions==<br />
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'':<br />
*<tt>META_IGNORED</tt> - The original function will be called.<br />
*<tt>META_HANDLED</tt> - Same as <tt>META_IGNORED</tt>, except subsequent hooks can assume this means something important was changed.<br />
*<tt>META_OVERRIDE</tt> - The original function will be called, but the new return value will be used instead of the one from the original function.<br />
*<tt>META_SUPERCEDE</tt> - The original function will not be called. The new return value (if any) will be used instead.<br />
<br />
These constants are defined in <sourcehook.h>, and are made available to Metamod:Source plugin developers via <ISmmPlugin.h>.<br />
<br />
Once all pre-hooks have been processed, SourceHook takes an action based on the "highest" hook action returned (<tt>META_IGNORED</tt> being lowest, <tt>META_SUPERCEDE</tt> 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.<br />
<br />
A hook's action is signalled via one of two macros:<br />
*<tt>RETURN_META</tt> - Only usable from <tt>void</tt> functions. Signals the action to take, then returns.<br />
*<tt>RETURN_META_VALUE</tt> - Only usable from non-<tt>void</tt> functions. Signals the action to take, then returns the supplied value.<br />
<br />
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:<br />
*<tt>SH_STATIC(Function)</tt> - Hook to a static function.<br />
*<tt>SH_MEMBER(Instance, Function)</tt> - Hook to a member function.<br />
<br />
<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 function X::Y in instance #8, then the hook will only be invoked from instance #8. <b>Multiple hooks can be declared on the same instance, and multiple instances can be bound to the same hook, but one hook will only be invoked for the instances it was hooked to.</b><br />
<br />
To have hooks that work across all instances, and thus do not need to be delegated per-instance, see the "Global Hooks" section. As of SourceHook v5, it is safe to remove hooks on a destroyed instance, as the instance is not actually dereferenced. However, its virtual table must still be accessible.<br />
<br />
==Adding Hooks==<br />
The macro to add hooks is <tt>SH_[ADD|REMOVE]_HOOK</tt>. The syntax is:<br />
<br />
<cpp>SH_[ADD|REMOVE]_HOOK(Interface, Function, Instance, Hook, [post? true,false])</cpp><br />
<br />
An example of adding a <tt>LogPrint</tt> hook:<br />
<cpp>void Hook_LogPrint(const char *msg)<br />
{<br />
if (strcmp(msg, "If this string matches the function will be blocked") == 0)<br />
{<br />
RETURN_META(MRES_SUPERCEDE);<br />
}<br />
<br />
/* Not needed, but good style */<br />
RETURN_META(MRES_IGNORED);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
log_hook = SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_STATIC(Hook_LogPrint), false);<br />
}</cpp><br />
<br />
The syntax is similar for hooking to member functions. Example equivalent to the above:<br />
<cpp><br />
class MyHooks<br />
{<br />
public:<br />
void Hook_LogPrint(const char *msg)<br />
{<br />
if (strcmp(msg, "If this string matches the function will be blocked") == 0)<br />
{<br />
RETURN_META(MRES_SUPERCEDE);<br />
}<br />
<br />
/* Not needed, but good style */<br />
RETURN_META(MRES_IGNORED);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
SH_ADD_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_HOOK(IVEngineServer, LogPrint, engine, SH_MEMBER(this, &MyHooks::Hook_LogPrint), false);<br />
}<br />
};</cpp><br />
<br />
<br />
=Manual Hooks=<br />
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:<br />
<br />
Interface v1:<br />
<cpp><br />
class Interface<br />
{<br />
public:<br />
virtual void Function1() =0;<br />
virtual bool Function2(int clam) =0;<br />
};</cpp><br />
<br />
Interface v2:<br />
<cpp><br />
class Interface<br />
{<br />
public:<br />
virtual bool Function2(int clam) =0;<br />
};</cpp><br />
<br />
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.<br />
<br />
==Declaration==<br />
Declaring a manual hook is similar to declaring a normal/simple hook. The syntax is:<br />
<br />
<cpp>SH_DECL_MANUALHOOK<n>[_void](UniqueName, vtblIndex, vtblOffs, thisOffs, [return and param types])</cpp><br />
<br />
The <tt>UniqueName</tt> is a unique identifier for the hook. The <tt>vtblIndex</tt> is the index into the virtual table at which the function lies. In most compilers, this index starts from 0. The <tt>vtblOffs</tt> and <tt>thisOffs</tt> fields are used for multiple inheritance and are almost always 0 in modern compiler single inheritance.<br />
<br />
An example of hooking the two functions from the first interface version:<br />
<cpp>SH_DECL_MANUALHOOK0_void(MHook_Function1, 0, 0, 0);<br />
SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);</cpp><br />
<br />
==Reconfiguring==<br />
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 <tt>Function2</tt> hook in the case of the second version being detected:<br />
<br />
<cpp><br />
void SwitchToNewerHooks()<br />
{<br />
SH_MANUALHOOK_RECONFIGURE(MHook_Function2, 0, 0, 0);<br />
}<br />
</cpp><br />
<br />
Note that the hook was referenced by its unique identifier.<br />
<br />
==Adding Hooks==<br />
Adding or removing hook binds is done via the following extra macros:<br />
*<tt>SH_ADD_MANUALHOOK</tt><br />
*<tt>SH_REMOVE_MANUALHOOK</tt><br />
<br />
These work similar to the original functions. Example:<br />
<cpp>extern Interface *iface;<br />
<br />
bool Hook_Function2(int clam)<br />
{<br />
RETURN_META_VALUE(MRES_IGNORED, false);<br />
}<br />
<br />
void StartHooks()<br />
{<br />
SH_ADD_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
SH_REMOVE_MANUALHOOK(MHook_Function2, iface, SH_STATIC(Hook_Function2), false);<br />
}</cpp><br />
<br />
Similarly, a member function version would use <tt>SH_MEMBER</tt> instead.<br />
<br />
<br />
=Extended Removal Syntax=<br />
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, optional. There is another way to remove hooks.<br />
<br />
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.<br />
<br />
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.<br />
<br />
<br />
=Old Macros=<br />
SourceHook v5.0 deprecates older macros that were used in earlier versions. The macros are:<br />
*<tt>SH_ADD_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_REMOVE_HOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_ADD_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_HOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_REMOVE_HOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_HOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_ADD_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_REMOVE_MANUALHOOK_STATICFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_STATIC</tt>.<br />
*<tt>SH_ADD_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_ADD_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.<br />
*<tt>SH_REMOVE_MANUALHOOK_MEMFUNC</tt> - Wrapper for <tt>SH_REMOVE_MANUALHOOK</tt> and <tt>SH_MEMBER</tt>.<br />
<br />
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. <br />
<br />
This syntax is considered deprecated, but it is still supported. Code written with these macros will continue to compile against older SourceHook versions. If you are writing a plugin which must work against Metamod:Source 1.4 and 1.6, you will want to use the older macros for simplicity.<br />
<br />
<br />
=Global Hooks=<br />
<b>Note:</b> Global Hooks are only available in SourceHook v4.5 or later.<br />
<br />
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:<br />
<br />
<cpp><br />
class CBaseEntity<br />
{<br />
public:<br />
virtual void SetHealth(int health) =0;<br />
};<br />
<br />
class CBaseCat : public CBaseEntity<br />
{<br />
public:<br />
virtual void SetHealth();<br />
};<br />
<br />
class CBaseKitten : public CBaseCat<br />
{<br />
public:<br />
virtual void SetHealth();<br />
};</cpp><br />
<br />
In this example, <tt>CBaseCat</tt> and <tt>CBaseKitten</tt> instances have ''separate virtual tables''. Although they both derive from <tt>CBaseEntity</tt>, they are separate virtual objects. <b>Therefore, a global hook on <tt>CBaseEntity</tt> will receive no invocations, and a hook on <tt>CBaseCat</tt> will receive only <tt>CBaseCat</tt> instances, as long as the instance is not a class derived from <tt>CBaseCat</tt></b>.<br />
<br />
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:<br />
*An instance of the class can be passed.<br />
*The direct address to the virtual table can be passed.<br />
<br />
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).<br />
<br />
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.<br />
<br />
The macro <tt>META_IFACEPTR</tt> is especially useful for global hooks. See [[SourceHook_Development#Interface_Pointers_from_Hooks|Interface Pointers from Hooks]] near the end.<br />
<br />
Lastly, global hooks exclusively use the extended hooking syntax. That means there exists only <tt>SH_ADD</tt> macros. <tt>SH_HOOK_REMOVE_ID</tt> must be used and the hook ID generated via <tt>SH_ADD</tt> must be cached.<br />
<br />
All examples will use the following code as a basis:<br />
<cpp><br />
class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage) =0;<br />
};<br />
<br />
void Hook_TakeDamage(int damage);<br />
</cpp><br />
<br />
==Simple Hooks==<br />
Two extra macros exist for adding a global hook:<br />
<br />
<cpp><br />
SH_ADD_VPHOOK(Interface, Function, Instance, Handler, Post)<br />
SH_ADD_DVPHOOK(Interface, Function, VirtualTable, Handler, Post)<br />
</cpp><br />
<br />
An example:<br />
<cpp><br />
extern void *player_vtable;<br />
HookId takedamage_hook = 0;<br />
<br />
void StartHooks()<br />
{<br />
takedamage_hook = SH_ADD_DVPHOOK(Player, TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
if (takedamage_hook)<br />
{<br />
SH_REMOVE_HOOK_ID(takedamage_hook);<br />
}<br />
}</cpp><br />
<br />
==Manual Hooks==<br />
Similarly, manual hooks are straightforward:<br />
<cpp><br />
SH_DECL_MANUALHOOK1_void(MHook_TakeDamage, 0, 0, 0, int);<br />
<br />
extern void *player_vtable;<br />
int takedamage_hook = 0;<br />
<br />
void StartHooks()<br />
{<br />
takedamage_hook = SH_ADD_MANUALDVPHOOK(MHook_TakeDamage, playervtable, SH_STATIC(Hook_TakeDamage), false);<br />
}<br />
<br />
void StopHooks()<br />
{<br />
if (takedamage_hook)<br />
{<br />
SH_REMOVE_HOOK_ID(takedamage_hook);<br />
}<br />
}</cpp><br />
<br />
<br />
=Modifying Parameters=<br />
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:<br />
*Caller passes 5 into X.<br />
*Hook changes the 5 to a 6.<br />
*Hooked function receives a 6 and continues normally.<br />
<br />
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 <tt>Player::TakeDamage</tt> to the <tt>Hook_TakeDamage</tt> function.<br />
<br />
<cpp><br />
class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage);<br />
};<br />
<br />
void Hook_TakeDamage(int damage);</cpp><br />
<br />
Our objective is to multiply the damage by 2.<br />
<br />
==Simple Hooks==<br />
For simple hooks, changing parameters looks similar to an <tt>SH_CALL</tt>. The macros are:<br />
<br />
<cpp>RETURN_META_NEWPARAMS(Action, HookFunction, ([params]))<br />
RETURN_META_VALUE_NEWPARAMS(Action, Value, HookFunction, ([params]))<br />
</cpp><br />
<br />
Example:<br />
<cpp><br />
void Hook_TakeDamage(int damage)<br />
{<br />
RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (damage * 2));<br />
}</cpp><br />
<br />
Note that the parenthesis enclosing the parameters are required.<br />
<br />
==Manual Hooks==<br />
Manual hooks require slightly different macros. They are:<br />
<br />
<cpp>RETURN_META_MNEWPARAMS(Action, UniqueName, ([params]));<br />
RETURN_META_VALUE_MNEWPARAMS(Action, Value, UniqueName, ([params]));</cpp><br />
<br />
Example:<br />
<cpp><br />
SH_DECL_MANUALHOOK1_void(MHook_Player_TakeDamage, 0, 0, 0, int);<br />
<br />
void Hook_TakeDamage(int damage)<br />
{<br />
RETURN_META_MNEWPARAMS(MRES_IGNORED, MHook_Player_TakeDamage, (damage *2));<br />
}</cpp><br />
<br />
Note that the parenthesis enclosing the parameters are required.<br />
<br />
<br />
=Bypassing Hooks=<br />
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.<br />
<br />
To way to do this is to use the <tt>SH_CALL</tt> macro:<br />
<cpp>SH_CALL(Instance, HookFunction)(params)</cpp><br />
<br />
Example:<br />
<cpp>SH_CALL(engine, &IVEngineServer::LogPrint)("Secret Message");</cpp><br />
<br />
Similarly, manual hooks have <tt>SH_MCALL</tt>:<br />
<br />
<cpp>SH_DECL_MANUALHOOK1(MHook_Function2, 1, 0, 0, bool, int);<br />
<br />
bool Function2_Bypass(int clam)<br />
{<br />
return SH_MCALL(iface, MHook_Function2)(clam);<br />
}</cpp><br />
<br />
==Deprecated Syntax==<br />
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.<br />
<br />
<br />
=Other Macros=<br />
SourceHook contains a large variety of extra macros. This section is a grab bag of the more commonly used ones.<br />
<br />
==Interface Pointers from Hooks==<br />
Let's say you have the following hook:<br />
<br />
<cpp>class Player<br />
{<br />
public:<br />
virtual void TakeDamage(int damage);<br />
float GetDamageModifier();<br />
};<br />
<br />
void Hook_TakeDamage(int damage);<br />
</cpp><br />
<br />
How can you get the <tt>Player</tt> instance while in the hook? This can be achieved via the <tt>META_IFACEPTR</tt> macro. Example:<br />
<br />
<cpp>void Hook_TakeDamage(int damage)<br />
{<br />
Player *pPlayer = META_IFACEPTR(Player);<br />
<br />
int new_damage = (int)((pPlayer->GetDamageModifier() + 0.3) * (float)damage);<br />
<br />
RETURN_META_NEWPARAMS(MRES_IGNORED, &Player::TakeDamage, (new_damage));<br />
}</cpp><br />
<br />
Note that the class name should be passed to <tt>META_IFACEPTR</tt>, not the pointer type.<br />
<br />
==Ignoring Reference Returns==<br />
There is a special macro, <tt>RETURN_META_NOREF</tt>, for ignoring a return value for reference-returning functions. Example:<br />
<br />
<cpp><br />
class ISomething<br />
{<br />
public: <br />
virtual int & GetSomething() =0;<br />
};<br />
<br />
int & Hook_GetSomething()<br />
{<br />
RETURN_META_NOREF(MRES_IGNORED, int &);<br />
}</cpp><br />
<br />
=Automatic hookmanager generation=<br />
Normally, the SH_DECL_ macros generate a so-called "hook manager", a function which is invoked instead of the original function and then calls the hooks and processes their return and <tt>META_RES</tt> values.<br />
<br />
It is also possible to let SourceHook auto-generate the hook manager. This is neccessary if the function prototype is unknown at compile time (for example if hooks can be defined from third-party plugins of your plugin).<br />
<br />
Example Usage from a Metamod:Source plugin:<br />
<cpp><br />
#include "sourcehook_pibuilder.h"<br />
<br />
// We want to hook a void (int, float) function<br />
<br />
// Request IHookManagerAutoGen interface<br />
SourceHook::IHookManagerAutoGen *hmag =<br />
static_cast<SourceHook::IHookManagerAutoGen *>(ismm->MetaFactory(MMIFACE_SH_HOOKMANAUTOGEN, NULL, NULL));<br />
<br />
// (check for hmag == NULL)<br />
<br />
// Build prototype information (using CProtoInfoBuilder helper class).<br />
SourceHook::CProtoInfoBuilder protoInfo(SourceHook::ProtoInfo::CallConv_ThisCall);<br />
protoInfo.AddParam(sizeof(int), SourceHook::PassInfo::PassType_Basic, SourceHook::PassInfo::PassFlag_ByVal,<br />
NULL, NULL, NULL, NULL);<br />
protoInfo.AddParam(sizeof(float), SourceHook::PassInfo::PassType_Float, SourceHook::PassInfo::PassFlag_ByVal,<br />
NULL, NULL, NULL, NULL);<br />
<br />
// Generate the hook manager<br />
HookManagerPubFunc generatedHookMan = hmag->MakeHookMan(protoInfo, 0 /* vtable offset */, 0 /* vtable index */);<br />
<br />
// Add the hook<br />
int hookid = g_SHPtr->AddHook(g_PLID, SourceHook::Hook_Normal/Hook_VP/Hook_DVP, iface_ptr, thisptr_offs,<br />
generatedHookMan, handler, post);<br />
<br />
// ... (use the hook)<br />
<br />
// After you're done using the hook (cleanup)<br />
// Remove the hook<br />
g_SHPtr->RemoveHookByID(hookid);<br />
<br />
// Release hook manager<br />
hmag->ReleaseHookMan(generatedHookMan);<br />
</cpp><br />
<tt>CProtoInfoBuilder::AddParam</tt> has 7 parameters:<br />
* size: size of the type. This is sizeof(type) even if it is passed by reference!<br />
* passtype: this can be PassType_Basic for integer types (char/short/int/...), PassType_Float for floating-point types (float/double) and PassType_Object for unions, structs and classes.<br />
* passflags: flags. Has to contain either PassFlag_ByVal or PassFlag_ByRef.<br />
* pNormalCtor: for PassType_Object, set this to the pointer to the user-defined default constructor of the object, if it has one.<br />
* pCCtor: for PassType_Object, set this to the pointer to the user-defined copy constructor of the object, if it has one.<br />
* pODtor: for PassType_Object, set this to the pointer to the user-defined destructor of the object, if it has one.<br />
* pAssignOperator: for PassType_Object, set this to the pointer to the user-defined assignment operator of the object, if it has one.<br />
<br />
If you are hooking a non-void function, also call <tt>CProtoInfoBuilder::SetReturnType</tt>. It has the same arguments as <tt>AddParam</tt>.<br />
<br />
The <tt>handler</tt> parameter of <tt>ISourceHook::AddHook</tt> is a pointer to the ISHDelegate interface.<br />
In C++, you construct ISHDelegates like this:<br />
<cpp><br />
class MyDelegate : public SourceHook::ISHDelegate<br />
{<br />
public:<br />
// vtable index 0<br />
virtual bool IsEqual(ISHDelegate *pOtherDeleg)<br />
{<br />
// pOtherDeleg is guaranteed to be from the same plugin.<br />
// This function is only used for compat with the old SH_REMOVE_HOOK method.<br />
// if you don't want to use that with your hooks, you can simply return false here.<br />
// if for some reason you need it, a good idea could be comparing the vtable pointer:<br />
return *reinterpret_cast<void**>(this) == *reinterpret_cast<void**>(pOtherDeleg);<br />
<br />
// But in general I'd just return false!<br />
}<br />
<br />
// vtable index 1<br />
virtual void DeleteThis()<br />
{<br />
delete this; // Called from SourceHook when this instance is not needed<br />
// and should be deleted.<br />
}<br />
<br />
// vtable index 2<br />
virtual ret_type Call(params)<br />
{<br />
// your code.<br />
// SH_DECL_ macros pass execution to the actual user's handler through a FastDelegate<br />
// which is stored as a member variable of the delegate class.<br />
}<br />
};<br />
</cpp><br />
<br />
The first parameter of the CProtoInfoBuilder constructor (see example code above) is the calling convention in an extended sense. At the moment it can be either CallConv_ThisCall or (CallConv_ThisCall | CallConv_HasVafmt). The second value means that the function has printf-like string formatting. Then, the finaly ''const char *, ...'' arguments will be added automatically, and the Deleagte::Call method should have one more parameter: ''const char *formattedString''.<br />
<br />
pNormalCtor/pCCtor/pODtor/pAssignOperator should be found using offsets / sigscanning. If you are a C++ plugin and know the type at compile time, you can also use the following class:<br />
<cpp><br />
// Address of constructor/destructor<br />
// (using wrappers)<br />
template <class T><br />
class Ctor_Thunk<br />
{<br />
public:<br />
void NormalConstructor()<br />
{<br />
new(this) T;<br />
}<br />
<br />
void CopyConstructor(const T &other)<br />
{<br />
new(this) T(other);<br />
}<br />
<br />
void Destructor()<br />
{<br />
reinterpret_cast<T*>(this)->~T();<br />
}<br />
<br />
const T& AssignOp(const T &other)<br />
{<br />
return (*reinterpret_cast<T*>(this) = other);<br />
}<br />
};<br />
<br />
<br />
template <class T><br />
void *FindFuncAddr(T mfp)<br />
{<br />
union<br />
{<br />
T a;<br />
void *b;<br />
} u;<br />
u.a = mfp;<br />
return u.b;<br />
}<br />
<br />
// Usage:<br />
FindFuncAddr(&Ctor_Thunk<type>::NormalConstructor)<br />
// (if the type is a reference type, use it without & here)<br />
</cpp><br />
<br />
=Embedding=<br />
Embedding SourceHook in your own application or library is very easy. The <tt>sourcehook.cpp</tt> file must be compiled or linked into your project. To instantiate the SourceHook engine, you must create a <tt>CSourceHookImpl</tt> instance. Example:<br />
<br />
<cpp><br />
/* Normally, just <sourcehook.h> is included, but this is needed to instantiate the engine. */<br />
#include <sourcehook/sourcehook_impl.h><br />
<br />
SourceHook::Impl::CSourceHookImpl g_SourceHook;<br />
</cpp><br />
<br />
To actually use SourceHook, it is necessary to have two global variables:<br />
*<tt>g_PLID</tt> - A unique integer that identifies the library using SourceHook. This is used for removing all hooks a library is using.<br />
*<tt>g_SHPtr</tt> - A pointer to the <tt>SourceHook::ISourceHook</tt> interface.<br />
<br />
Example header file:<br />
<cpp><br />
#include <sourcehook/sourcehook.h><br />
<br />
extern SourceHook::ISourceHook *g_SHPtr;<br />
extern int g_PLID;</cpp><br />
<br />
Example addition to the global code:<br />
<cpp><br />
SourceHook::ISourceHook *g_SHPtr = &g_SourceHook;<br />
int g_PLID = 0;</cpp><br />
<br />
==Multiple Libraries/Shared Hooks==<br />
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.<br />
<br />
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.<br />
<br />
*<tt>PausePlugin()</tt> - Silences any hooks from an ID, such that those hooks will not be called.<br />
*<tt>UnpausePlugin()</tt> - Un-silences any silenced hooks from an ID.<br />
*<tt>UnloadPlugin()</tt> - Clean-up any left-over hooks from an ID.</div>Dave Gombochttps://wiki.alliedmods.net/index.php?title=Sample_Plugins_(Metamod:Source)&diff=6116Sample Plugins (Metamod:Source)2008-08-20T19:38:21Z<p>Dave Gomboc: typo fix</p>
<hr />
<div>Metamod:Source comes with two sample plugins: <tt>stub</tt> and <tt>sample</tt>. This article is a brief overview of how to read and compile them. They are intended as a baseline for developing your own plugins, though they are certainly not required for your own development.<br />
<br />
Metamod:Source is a C++ environment, but this is not a C++ tutorial. You should have sufficient knowledge of computer organization (memory, pointers, addressing) and intermediate experience with C++. Most importantly, you should be willing to dive into header files to research API definitions (which is necessary for the HL2SDK regardless). <br />
<br />
Make sure to read the overview article, [[Metamod:Source Development]].<br />
<br />
=Introduction=<br />
Before you begin, you should set up your [[Metamod:Source Environment]]. If you fail to complete this step, it is unlikely much else in this article will work for you.<br />
<br />
The two sample plugins provided are:<br />
*<tt>stub_mm</tt> - Bare-bones plugin that does almost nothing.<br />
*<tt>sample_mm</tt> - More complete example plugin which implements a few things that Valve's serverplugin_sample does, such as hooking functions, creating ConVars, console commands, and showing dialog boxes.<br />
<br />
<br />
=The Engine Divide=<br />
The Orange Box and Episode 1 engines are not compatible, and thus there is a division:<br />
*Metamod:Source 1.4/Episode 1<br />
*Metamod:Source 1.6/Orange Box<br />
<br />
Unfortunately, there is a "lag period" where not all games have moved to the new engine. It is likely that this lag period will continue for at least another twelve months (this writing is as of February 2008). As such, we are writing all of our plugin code such that it compiles against both platforms. '''It is your choice whether to do this as well.''' As the usage of Metamod:Source 1.4 and the original engine decays, we will begin removing the legacy cruft from the sample plugins. That probably won't happen until Valve has a stable port of CS:S to Orange Box.<br />
<br />
As such, you will notice various idiosyncracies in the sample plugins. For example, <tt>METAMOD_PLAPI_VERSION</tt> exists in Metamod:Source 1.6 or later, so it is used to decide between <tt>GetEngineFactory</tt> (1.6) or <tt>engineFactory</tt> (1.4). Similarly, the sample plugins use two macros:<br />
*<tt>ENGINE_ORANGEBOX</tt> - Orange Box build<br />
*<tt>ENGINE_ORIGINAL</tt> - Original/Episode 1 build<br />
<br />
These two macros are used in a few places to toggle functionality compatible for the given build.<br />
<br />
For more information, see:<br />
*[[Upgrading Plugins to Metamod:Source 1.6.0]]<br />
*[[Porting to Orange Box]]<br />
<br />
<br />
=Compiling=<br />
<br />
==Windows==<br />
Both plugins have a Visual Studio 2005 project file in their <tt>msvc8</tt> folders. Each plugin has four build modes:<br />
*<tt>Release - Orange Box</tt> - Release mode, Orange Box, MM:S 1.6<br />
*<tt>Debug - Orange Box</tt> - Debug mode, Orange Box, MM:S 1.6<br />
*<tt>Release - Original</tt> - Release mode, Original/Episode1, MM:S 1.4<br />
*<tt>Debug - Original</tt> - Debug mode, Original/Episode1, MM:S 1.4<br />
<br />
There exists normal "Release" and "Debug" build modes -- you should not use them, as they are not configured.<br />
<br />
==Linux==<br />
On Linux, you cannot simply type "make" in the folder containing the <tt>Makefile</tt>. You must specify a combination of parameters:<br />
*<tt>ENGINE</tt> - Required. Must be either "orangebox" or "original".<br />
*<tt>DEBUG</tt> - Optional. Can be empty (Release mode) or "true" (Debug mode).<br />
<br />
Binaries and object files will be written to one of the following folders:<br />
*<tt>Release.orangebox</tt><br />
*<tt>Debug.orangebox</tt><br />
*<tt>Release.original</tt><br />
*<tt>Debug.original</tt><br />
<br />
Examples of building one of the example plugins:<br />
<pre><br />
#MM:S 1.4/Episode 1, debug mode<br />
make DEBUG=true ENGINE=original<br />
#MM:S 1.6/Orange Box, release mode<br />
make ENGINE=orangebox<br />
#Cleaning the MM:S 1.4 build<br />
make clean ENGINE=original<br />
</pre><br />
<br />
'''Note''' that you may need to edit the folder locations at the top of the Makefile, in case you set up your paths differently.<br />
<br />
<br />
=Stub Plugin=<br />
The stub plugin is a very simple, bare-bones plugin. It has little, if anything, beyond an implementation of the required API callbacks. <br />
<br />
It hooks one function, <tt>IServerGameDLL::ServerActivate</tt>, which is a callback fired after <tt>IServerGameDLL::LevelInit</tt>. <br />
<br />
Note that for the most part, <tt>stub_mm</tt> is free of compatibility cruft, whereas <tt>sample_mm</tt> tries to abstract more. This is so <tt>stub_mm</tt> doesn't appear to be "hiding" anything -- it's laid out very simply.<br />
<br />
<br />
=Sample Plugin=<br />
The full sample plugin is a bit more complicated, as it contains some of the examples from Valve's <tt>serverplugin_sample</tt>. It also may be a bit messy to read because of the engine compatibility wrappers in it.<br />
<br />
==Compatibility Wrappers==<br />
The first file of importance is <tt>engine_wrappers.h</tt>, which is full of compatibility shims. For example:<br />
*<tt>engineFactory</tt> became <tt>GetEngineFactory</tt> from 1.4 to 1.6<br />
*<tt>serverFactory</tt> became <tt>GetServerFactory</tt> from 1.4 to 1.6<br />
*<tt>ISmmAPI::Format</tt> did not exist in 1.4, so we created a simple <tt>#define</tt> to use <tt>snprintf</tt> instead. You could also use Valve's Q_snprintf if you desired.<br />
*Valve renamed <tt>VENGINE_CVAR_INTERFACE_VERSION</tt> to <tt>CVAR_INTERFACE_VERSION</tt> from Episode 1 to Orange Box.<br />
*The syntax to <tt>SH_CALL</tt> changed from 1.4 to 1.6 (which we have demonstrated for completeness and will be explained later).<br />
<br />
Also note the <tt>CCommand</tt> class. This is a wrapper around the <tt>IVEngineServer::Cmd_Arg*</tt> functions, which were removed in Orange Box. Valve replaced it with a newer, more re-entrant set of functions based around <tt>CCommand</tt>. Rather than create two codebases, we've simply wrapped the old functions in the new API. The functionality isn't ''exactly'' the same (becaose of re-entrancy), but it works nicely for our purposes. You can read more about this trick in [[Porting to Orange Box]].<br />
<br />
==Overview==<br />
===BaseAccessor===<br />
This is a singleton class responsible for registering anything extending from <tt>ConCommandBase</tt> (<tt>ConVar</tt> and <tt>ConCommand</tt>). You only need one, and it should wrap <tt>META_REGCVAR</tt>.<br />
<br />
Note that the accessor is invoked differently based on the engine version. For Orange Box, <tt>g_pCVar</tt> is assigned and <tt>ConVar_Register</tt> is called, whereas on Episode 1, only <tt>ConCommandBaseMgr::OneTimeInit</tt> is needed.<br />
<br />
===CallClasses===<br />
[[SourceHook Development|SourceHook]] has a feature where you can invoke a virtual function while bypassing all hooks on it. In SourceHook v4 (Metamod:Source 1.4 and lower), <tt>SH_CALL</tt> required a special object called a '''call class'''. This was removed in SourceHook v5. However, to be compatible with both MM:S 1.4 and 1.6, the sample plugin creates and destroys a call class when necessary.<br />
<br />
For more information on this syntax and what it means, see: [[Sourcehook Development#Bypassing_Hooks]].<br />
<br />
Note that the <tt>ENGINE_CALL</tt> macro is in <tt>engine_wrappers.h</tt>, and wraps <tt>SH_CALL</tt> based on the MM:S version.<br />
<br />
===VSP Listening===<br />
Some of the sample functionality requires an IServerPluginCallbacks pointer, and Metamod:Source can provide one for you.<br />
<br />
In Metamod:Source 1.4, you had to implement <tt>IMetamodListener</tt>, and do something like this:<br />
<pre><br />
ismm->AddListener(this, this);<br />
ismm->EnableVSPListener();<br />
</pre><br />
<br />
Then you had to wait for the callback to give you a correct pointer. In Metamod:Source 1.6, you can attempt to get the point immediately with:<br />
<pre><br />
if ((vsp_callbacks = ismm->GetVSPInfo(NULL)) == NULL)<br />
</pre><br />
<br />
===Hooked Functions===<br />
The sample plugin hooks most of the functions available to <tt>IServerPluginCallbacks</tt>, in order to show you how to re-implement Valve's functionality.<br />
<br />
Recall a few concepts:<br />
*In [[SourceHook Development|SourceHook]], hooks can either be "pre" (pre-empting the final call), or "post" (occurring after the pre hooks and final call, if any).<br />
*In Valve Server Plugins, only hooks labeled with <tt>PLUGIN_RESULT</tt> can override functionality. SourceHook, however, can override the functionality of '''any function'''. The default action is to continue (or pass-through), though there are options to override and supercede (see the [[Sourcehook Development#Hook_Functions]] for more information).<br />
*A hook with a return value must return a value, or else the compiler will complain. However, unless you use <tt>RETURN_META_VALUE</tt> with <tt>MRES_SUPERCEDE</tt> or <tt>MRES_OVERRIDE</tt>, the value will not affect anything.<br />
<br />
The following are generic hooks that Valve Server Plugins cannot override, so in the example they're hooked as '''post'''.<br />
*<tt>IServerGameDLL::LevelInit</tt>: Translates to <tt>LevelInit</tt>.<br />
*<tt>IServerGameDLL::ServerActivate</tt>: Translates to <tt>ServerActivate</tt>.<br />
*<tt>IServerGameDLL::GameFrame</tt>: Translates to <tt>GameFrame</tt>.<br />
*<tt>IServerGameDLL::LevelShutdown</tt>: Translates to <tt>LevelShutdown</tt>. (This is not actually hooked false in the example, but you probably shouldn't try overriding it.)<br />
*<tt>IServerGameClients::ClientActive</tt>: Translates to <tt>ClientActive</tt>. Not a very useful callback, not recommended for real work.<br />
*<tt>IServerGameClients::ClientDisconnect</tt>: Translates to <tt>ClientDisconnect</tt>. Called when the client disconnects.<br />
*<tt>IServerGameClients::ClientPutInServer</tt>: Translates to <tt>ClientPutInServer</tt>. Called when the client is put in-game and is fully instantiated (that is, IPlayerInfoManager will return valid results).<br />
*<tt>IServerGameClients::SetCommandClient</tt>: Translates to <tt>SetCommandClient</tt>. Called when a console command bound to a <tt>ConCommand</tt> is invoked, to tell all listeners which client typed the command. The index supplied is the client index minus one (that is, -1 for the server console, 0 for the first client, et cetera).<br />
<br />
The following hooks are pre-empted, and thus can be overridden or superseded:<br />
*<tt>IServerGameClients::ClientSettingsChanged</tt>: Translates to <tt>ClientSettingsChanged</tt>. Called when a client's info buffer changes (that is, anything from <tt>IVEngineServer::GetClientConVarValue</tt>). Overriding it will prevent the game from being notified.<br />
*<tt>IServerGameClients::ClientConnect</tt>: Translates to <tt>ClientConnect</tt>. Called when a client is connecting. You can override/supersede with a value of <tt>false</tt> to reject the client (note, of course, you should fill the rejection message buffer).<br />
*<tt>IServerGameClients::ClientCommand</tt>: Translates to <tt>ClientCommand</tt>. Called when the client types unrecognized text in their client console. If you don't override this function, the original game's handler will be called. If the game doesn't recognize the text, it will print "Unknown command" or something similar. Therefore, if you wish to block this text from appearing, you should supercede when you recognize the text. The sample plugin shows how to do this for a few commands.<br />
<br />
<br />
=The VDF Files=<br />
Note that both <tt>stub_mm</tt> and <tt>sample_mm</tt> come with their own VDF files. These are example files that load their respective binaries from the <tt>addons</tt> folder. Make two notes about this:<br />
*'''VDF files go in Metamod's folder'''. That is, <tt>addons/metamod</tt> (or whatever <tt>mm_basedir</tt> is). They are not Valve Server Plugin files and they cannot go in the <tt>addons</tt> folder.<br />
*If your plugin has more than one file, or it creates other files, it may be prudent to place it in its own sub-folder. The sample plugins are small and thus we placed the binaries in <tt>addons</tt> for simplicity. The extended convention for more complicated plugins is:<br />
**<tt>addons/NAME/bin/NAME_mm.dll</tt> or <tt>addons/NAME/bin/NAME_mm_i486.so</tt>. For example, <tt>addons/sourcemod/bin/sourcemod_mm</tt> is [[SourceMod]]'s VDF path, since it has a large directory structure.<br />
**''Note: This convention is not required, but it is recommended.''<br />
<br />
<br />
=Modifying/Reusing the Build Files=<br />
==Linux==<br />
The <tt>Makefile</tt>s are organized into four main sections:<br />
*User-specific paths<br />
*Project-specific settings<br />
*Build logic<br />
*Build rules<br />
<br />
The most important area to edit for a new project would be the "project-specific settings." These are variables such as:<br />
*<tt>OPT_FLAGS</tt> - Optimization flags (release-only).<br />
*<tt>DEBUG_FLAGS</tt> - Debug-only flags.<br />
*<tt>GCC4_FLAGS</tt> - Flags that are only used with GCC4.<br />
*<tt>CPP</tt> - The compiler to invoke.<br />
*<tt>BINARY</tt> - The binary file name to produce.<br />
*<tt>LINK</tt> - Extra link options.<br />
*<tt>INCLUDE</tt> - Extra include options (don't forget that you need -I to each item).<br />
<br />
You should probably keep the <tt>-DENGINE_ORANGEBOX</tt> and <tt>-DENGINE_ORIGINAL</tt> macros.<br />
<br />
For more information on flags and distribution, see [[Metamod:Source Development#Compiling]].<br />
<br />
==Windows==<br />
You should probably keep the <tt>ENGINE_ORANGEBOX</tt> and <tt>ENGINE_ORIGINAL</tt> preprocessor macros.<br />
<br />
You can edit the output binary name under the Linker options in the Project Properties dialog.<br />
<br />
You should not add global include folders in order to compile. Set up your [[Metamod:Source Environment]] and, if you need more include folders, add them to the "Additional Include Directories" option under the C++ options in the Project Properties dialog. It is a semicolon-delimited list, for example: <tt>$(SOURCEMM14);$(HL2SDK)\public</tt>.<br />
<br />
For the most part, editing a Visual Studio project is otherwise self-explanatory. See [[Metamod:Source Development#Compiling]] for more information on required "project-from-scratch" options.<br />
<br />
[[Category:Metamod:Source Development]]</div>Dave Gombochttps://wiki.alliedmods.net/index.php?title=Sample_Plugins_(Metamod:Source)&diff=6115Sample Plugins (Metamod:Source)2008-08-20T19:37:57Z<p>Dave Gomboc: List described as comma-delimited actually uses semi-colons to delimit it, so description changed accordingly.</p>
<hr />
<div>Metamod:Source comes with two sample plugins: <tt>stub</tt> and <tt>sample</tt>. This article is a brief overview of how to read and compile them. They are intended as a baseline for developing your own plugins, though they are certainly not required for your own development.<br />
<br />
Metamod:Source is a C++ environment, but this is not a C++ tutorial. You should have sufficient knowledge of computer organization (memory, pointers, addressing) and intermediate experience with C++. Most importantly, you should be willing to dive into header files to research API definitions (which is necessary for the HL2SDK regardless). <br />
<br />
Make sure to read the overview article, [[Metamod:Source Development]].<br />
<br />
=Introduction=<br />
Before you begin, you should set up your [[Metamod:Source Environment]]. If you fail to complete this step, it is unlikely much else in this article will work for you.<br />
<br />
The two sample plugins provided are:<br />
*<tt>stub_mm</tt> - Bare-bones plugin that does almost nothing.<br />
*<tt>sample_mm</tt> - More complete example plugin which implements a few things that Valve's serverplugin_sample does, such as hooking functions, creating ConVars, console commands, and showing dialog boxes.<br />
<br />
<br />
=The Engine Divide=<br />
The Orange Box and Episode 1 engines are not compatible, and thus there is a division:<br />
*Metamod:Source 1.4/Episode 1<br />
*Metamod:Source 1.6/Orange Box<br />
<br />
Unfortunately, there is a "lag period" where not all games have moved to the new engine. It is likely that this lag period will continue for at least another twelve months (this writing is as of February 2008). As such, we are writing all of our plugin code such that it compiles against both platforms. '''It is your choice whether to do this as well.''' As the usage of Metamod:Source 1.4 and the original engine decays, we will begin removing the legacy cruft from the sample plugins. That probably won't happen until Valve has a stable port of CS:S to Orange Box.<br />
<br />
As such, you will notice various idiosyncracies in the sample plugins. For example, <tt>METAMOD_PLAPI_VERSION</tt> exists in Metamod:Source 1.6 or later, so it is used to decide between <tt>GetEngineFactory</tt> (1.6) or <tt>engineFactory</tt> (1.4). Similarly, the sample plugins use two macros:<br />
*<tt>ENGINE_ORANGEBOX</tt> - Orange Box build<br />
*<tt>ENGINE_ORIGINAL</tt> - Original/Episode 1 build<br />
<br />
These two macros are used in a few places to toggle functionality compatible for the given build.<br />
<br />
For more information, see:<br />
*[[Upgrading Plugins to Metamod:Source 1.6.0]]<br />
*[[Porting to Orange Box]]<br />
<br />
<br />
=Compiling=<br />
<br />
==Windows==<br />
Both plugins have a Visual Studio 2005 project file in their <tt>msvc8</tt> folders. Each plugin has four build modes:<br />
*<tt>Release - Orange Box</tt> - Release mode, Orange Box, MM:S 1.6<br />
*<tt>Debug - Orange Box</tt> - Debug mode, Orange Box, MM:S 1.6<br />
*<tt>Release - Original</tt> - Release mode, Original/Episode1, MM:S 1.4<br />
*<tt>Debug - Original</tt> - Debug mode, Original/Episode1, MM:S 1.4<br />
<br />
There exists normal "Release" and "Debug" build modes -- you should not use them, as they are not configured.<br />
<br />
==Linux==<br />
On Linux, you cannot simply type "make" in the folder containing the <tt>Makefile</tt>. You must specify a combination of parameters:<br />
*<tt>ENGINE</tt> - Required. Must be either "orangebox" or "original".<br />
*<tt>DEBUG</tt> - Optional. Can be empty (Release mode) or "true" (Debug mode).<br />
<br />
Binaries and object files will be written to one of the following folders:<br />
*<tt>Release.orangebox</tt><br />
*<tt>Debug.orangebox</tt><br />
*<tt>Release.original</tt><br />
*<tt>Debug.original</tt><br />
<br />
Examples of building one of the example plugins:<br />
<pre><br />
#MM:S 1.4/Episode 1, debug mode<br />
make DEBUG=true ENGINE=original<br />
#MM:S 1.6/Orange Box, release mode<br />
make ENGINE=orangebox<br />
#Cleaning the MM:S 1.4 build<br />
make clean ENGINE=original<br />
</pre><br />
<br />
'''Note''' that you may need to edit the folder locations at the top of the Makefile, in case you set up your paths differently.<br />
<br />
<br />
=Stub Plugin=<br />
The stub plugin is a very simple, bare-bones plugin. It has little, if anything, beyond an implementation of the required API callbacks. <br />
<br />
It hooks one function, <tt>IServerGameDLL::ServerActivate</tt>, which is a callback fired after <tt>IServerGameDLL::LevelInit</tt>. <br />
<br />
Note that for the most part, <tt>stub_mm</tt> is free of compatibility cruft, whereas <tt>sample_mm</tt> tries to abstract more. This is so <tt>stub_mm</tt> doesn't appear to be "hiding" anything -- it's laid out very simply.<br />
<br />
<br />
=Sample Plugin=<br />
The full sample plugin is a bit more complicated, as it contains some of the examples from Valve's <tt>serverplugin_sample</tt>. It also may be a bit messy to read because of the engine compatibility wrappers in it.<br />
<br />
==Compatibility Wrappers==<br />
The first file of importance is <tt>engine_wrappers.h</tt>, which is full of compatibility shims. For example:<br />
*<tt>engineFactory</tt> became <tt>GetEngineFactory</tt> from 1.4 to 1.6<br />
*<tt>serverFactory</tt> became <tt>GetServerFactory</tt> from 1.4 to 1.6<br />
*<tt>ISmmAPI::Format</tt> did not exist in 1.4, so we created a simple <tt>#define</tt> to use <tt>snprintf</tt> instead. You could also use Valve's Q_snprintf if you desired.<br />
*Valve renamed <tt>VENGINE_CVAR_INTERFACE_VERSION</tt> to <tt>CVAR_INTERFACE_VERSION</tt> from Episode 1 to Orange Box.<br />
*The syntax to <tt>SH_CALL</tt> changed from 1.4 to 1.6 (which we have demonstrated for completeness and will be explained later).<br />
<br />
Also note the <tt>CCommand</tt> class. This is a wrapper around the <tt>IVEngineServer::Cmd_Arg*</tt> functions, which were removed in Orange Box. Valve replaced it with a newer, more re-entrant set of functions based around <tt>CCommand</tt>. Rather than create two codebases, we've simply wrapped the old functions in the new API. The functionality isn't ''exactly'' the same (becaose of re-entrancy), but it works nicely for our purposes. You can read more about this trick in [[Porting to Orange Box]].<br />
<br />
==Overview==<br />
===BaseAccessor===<br />
This is a singleton class responsible for registering anything extending from <tt>ConCommandBase</tt> (<tt>ConVar</tt> and <tt>ConCommand</tt>). You only need one, and it should wrap <tt>META_REGCVAR</tt>.<br />
<br />
Note that the accessor is invoked differently based on the engine version. For Orange Box, <tt>g_pCVar</tt> is assigned and <tt>ConVar_Register</tt> is called, whereas on Episode 1, only <tt>ConCommandBaseMgr::OneTimeInit</tt> is needed.<br />
<br />
===CallClasses===<br />
[[SourceHook Development|SourceHook]] has a feature where you can invoke a virtual function while bypassing all hooks on it. In SourceHook v4 (Metamod:Source 1.4 and lower), <tt>SH_CALL</tt> required a special object called a '''call class'''. This was removed in SourceHook v5. However, to be compatible with both MM:S 1.4 and 1.6, the sample plugin creates and destroys a call class when necessary.<br />
<br />
For more information on this syntax and what it means, see: [[Sourcehook Development#Bypassing_Hooks]].<br />
<br />
Note that the <tt>ENGINE_CALL</tt> macro is in <tt>engine_wrappers.h</tt>, and wraps <tt>SH_CALL</tt> based on the MM:S version.<br />
<br />
===VSP Listening===<br />
Some of the sample functionality requires an IServerPluginCallbacks pointer, and Metamod:Source can provide one for you.<br />
<br />
In Metamod:Source 1.4, you had to implement <tt>IMetamodListener</tt>, and do something like this:<br />
<pre><br />
ismm->AddListener(this, this);<br />
ismm->EnableVSPListener();<br />
</pre><br />
<br />
Then you had to wait for the callback to give you a correct pointer. In Metamod:Source 1.6, you can attempt to get the point immediately with:<br />
<pre><br />
if ((vsp_callbacks = ismm->GetVSPInfo(NULL)) == NULL)<br />
</pre><br />
<br />
===Hooked Functions===<br />
The sample plugin hooks most of the functions available to <tt>IServerPluginCallbacks</tt>, in order to show you how to re-implement Valve's functionality.<br />
<br />
Recall a few concepts:<br />
*In [[SourceHook Development|SourceHook]], hooks can either be "pre" (pre-empting the final call), or "post" (occurring after the pre hooks and final call, if any).<br />
*In Valve Server Plugins, only hooks labeled with <tt>PLUGIN_RESULT</tt> can override functionality. SourceHook, however, can override the functionality of '''any function'''. The default action is to continue (or pass-through), though there are options to override and supercede (see the [[Sourcehook Development#Hook_Functions]] for more information).<br />
*A hook with a return value must return a value, or else the compiler will complain. However, unless you use <tt>RETURN_META_VALUE</tt> with <tt>MRES_SUPERCEDE</tt> or <tt>MRES_OVERRIDE</tt>, the value will not affect anything.<br />
<br />
The following are generic hooks that Valve Server Plugins cannot override, so in the example they're hooked as '''post'''.<br />
*<tt>IServerGameDLL::LevelInit</tt>: Translates to <tt>LevelInit</tt>.<br />
*<tt>IServerGameDLL::ServerActivate</tt>: Translates to <tt>ServerActivate</tt>.<br />
*<tt>IServerGameDLL::GameFrame</tt>: Translates to <tt>GameFrame</tt>.<br />
*<tt>IServerGameDLL::LevelShutdown</tt>: Translates to <tt>LevelShutdown</tt>. (This is not actually hooked false in the example, but you probably shouldn't try overriding it.)<br />
*<tt>IServerGameClients::ClientActive</tt>: Translates to <tt>ClientActive</tt>. Not a very useful callback, not recommended for real work.<br />
*<tt>IServerGameClients::ClientDisconnect</tt>: Translates to <tt>ClientDisconnect</tt>. Called when the client disconnects.<br />
*<tt>IServerGameClients::ClientPutInServer</tt>: Translates to <tt>ClientPutInServer</tt>. Called when the client is put in-game and is fully instantiated (that is, IPlayerInfoManager will return valid results).<br />
*<tt>IServerGameClients::SetCommandClient</tt>: Translates to <tt>SetCommandClient</tt>. Called when a console command bound to a <tt>ConCommand</tt> is invoked, to tell all listeners which client typed the command. The index supplied is the client index minus one (that is, -1 for the server console, 0 for the first client, et cetera).<br />
<br />
The following hooks are pre-empted, and thus can be overridden or superseded:<br />
*<tt>IServerGameClients::ClientSettingsChanged</tt>: Translates to <tt>ClientSettingsChanged</tt>. Called when a client's info buffer changes (that is, anything from <tt>IVEngineServer::GetClientConVarValue</tt>). Overriding it will prevent the game from being notified.<br />
*<tt>IServerGameClients::ClientConnect</tt>: Translates to <tt>ClientConnect</tt>. Called when a client is connecting. You can override/supersede with a value of <tt>false</tt> to reject the client (note, of course, you should fill the rejection message buffer).<br />
*<tt>IServerGameClients::ClientCommand</tt>: Translates to <tt>ClientCommand</tt>. Called when the client types unrecognized text in their client console. If you don't override this function, the original game's handler will be called. If the game doesn't recognize the text, it will print "Unknown command" or something similar. Therefore, if you wish to block this text from appearing, you should supercede when you recognize the text. The sample plugin shows how to do this for a few commands.<br />
<br />
<br />
=The VDF Files=<br />
Note that both <tt>stub_mm</tt> and <tt>sample_mm</tt> come with their own VDF files. These are example files that load their respective binaries from the <tt>addons</tt> folder. Make two notes about this:<br />
*'''VDF files go in Metamod's folder'''. That is, <tt>addons/metamod</tt> (or whatever <tt>mm_basedir</tt> is). They are not Valve Server Plugin files and they cannot go in the <tt>addons</tt> folder.<br />
*If your plugin has more than one file, or it creates other files, it may be prudent to place it in its own sub-folder. The sample plugins are small and thus we placed the binaries in <tt>addons</tt> for simplicity. The extended convention for more complicated plugins is:<br />
**<tt>addons/NAME/bin/NAME_mm.dll</tt> or <tt>addons/NAME/bin/NAME_mm_i486.so</tt>. For example, <tt>addons/sourcemod/bin/sourcemod_mm</tt> is [[SourceMod]]'s VDF path, since it has a large directory structure.<br />
**''Note: This convention is not required, but it is recommended.''<br />
<br />
<br />
=Modifying/Reusing the Build Files=<br />
==Linux==<br />
The <tt>Makefile</tt>s are organized into four main sections:<br />
*User-specific paths<br />
*Project-specific settings<br />
*Build logic<br />
*Build rules<br />
<br />
The most important area to edit for a new project would be the "project-specific settings." These are variables such as:<br />
*<tt>OPT_FLAGS</tt> - Optimization flags (release-only).<br />
*<tt>DEBUG_FLAGS</tt> - Debug-only flags.<br />
*<tt>GCC4_FLAGS</tt> - Flags that are only used with GCC4.<br />
*<tt>CPP</tt> - The compiler to invoke.<br />
*<tt>BINARY</tt> - The binary file name to produce.<br />
*<tt>LINK</tt> - Extra link options.<br />
*<tt>INCLUDE</tt> - Extra include options (don't forget that you need -I to each item).<br />
<br />
You should probably keep the <tt>-DENGINE_ORANGEBOX</tt> and <tt>-DENGINE_ORIGINAL</tt> macros.<br />
<br />
For more information on flags and distribution, see [[Metamod:Source Development#Compiling]].<br />
<br />
==Windows==<br />
You should probably keep the <tt>ENGINE_ORANGEBOX</tt> and <tt>ENGINE_ORIGINAL</tt> preprocessor macros.<br />
<br />
You can edit the output binary name under the Linker options in the Project Properties dialog.<br />
<br />
You should not add global include folders in order to compile. Set up your [[Metamod:Source Environment]] and, if you need more include folders, add them to the "Additional Include Directories" option under the C++ options in the Project Properties dialog. It is a seimcolon-delimited list, for example: <tt>$(SOURCEMM14);$(HL2SDK)\public</tt>.<br />
<br />
For the most part, editing a Visual Studio project is otherwise self-explanatory. See [[Metamod:Source Development#Compiling]] for more information on required "project-from-scratch" options.<br />
<br />
[[Category:Metamod:Source Development]]</div>Dave Gomboc