Function Hooks

HookPoint::Function — hooking any game function by gamedata entry, address, signature, symbol or vtable slot through ISource2ModFunctions; SourceMod's DHooks.

What has no typed point of its own is still one call away. HookPoint::Function(fn) hooks any function an ISource2ModFunction describes: where it is and what it takes. The same object is what you call a game function through, so a function described once can be called and hooked. This is SourceMod's DHooks.

plugin.cpp
#include "source2mod/ISource2ModFunctions.h"
#include "source2mod/ISource2ModHooks.h"

// A gamedata entry with a "function" object: Find() and nothing else.
if (ISource2ModFunction* pSwitchTeam = g_pSource2ModFunctions->Find("CCSPlayerController::SwitchTeam"))
    g_pSource2ModHooks->Hook(HookPoint::Function(pSwitchTeam), HookTarget::All(), SOURCE2MOD_MEMBER(this, &MyPlugin::OnSwitchTeam));

HookReturn<void> MyPlugin::OnSwitchTeam(FunctionContext& ctx, bool post)
{
    // `this` is argument 0 of a member function, the team 1.
    auto* pController = ctx.GetArg<CCSPlayerController*>(0);
    const int nTeam = ctx.GetArg<int>(1);

    if (pController && pController->IsBot() && nTeam == 3)
        return Action::Supersede;   // bots stay where they are

    return Action::Ignore;
}

Where the function comes from

C++ (g_pSource2ModFunctions->)SourcePawn (Source2ModFunction.)The function
Find(name)Find(name)A gamedata entry with a "function" object — the core's or one you added. The types come from the gamedata.
FromGameConfig(owner, name, types)FromGameConfig(name, returnType) + AddParamA gamedata entry without one, by its signature, symbol or — a virtual — offset.
FromSignature(owner, module, signature, types, section)FromSignature(module, signature, returnType, section)A pattern of your own in a module ("server"), optionally in one section (".text").
FromSignatureRelative(owner, module, signature, offset, nextInstructionOffset, types, section)FromSignatureRelative(...)The target of a call / jmp / lea the pattern matches.
FromSymbol(owner, module, symbol, types)FromSymbol(module, symbol, returnType)An exported or debug symbol.
FromVirtualTable(owner, module, className, index, types, baseName)FromVirtualTable(module, vtableName, vtableIndex, returnType, baseName)Slot index of one class's vtable (an RTTI name); baseName picks a secondary base's vtable. See Virtual functions.
FromVirtual(owner, index, types)FromVirtual(vtableIndex, returnType)Slot index of whatever object it is called or hooked on — an engine interface, say. Hook it with an Instance or Class target.
Create(owner, address, vtable, index, isMember, ret, params, count)FromAddress(address, returnType)An address you or the core resolved (an ADDR_* getter, Source2ModAddress).

Find returns a function owned by the core, valid until the server shuts down. The others are the plugin's (owner is g_PluginID in C++): freed with Destroy() or when it unloads. Each returns null when the function is not found; the reason is on the console.

A script has no WIN_LINUX(): a pattern for the other platform simply does not resolve, so try both, or put the function into gamedata. See Gamedata for writing entries and finding slots.

Describing the types

A function from Find is described by its gamedata "function" object — see Calling game functions. Every other one is described where it is created: in C++ a FunctionTypes (return type, parameter types with this first for a member function, their count, whether it is a member), in SourcePawn one AddParam per parameter, this first.

// CBasePlayerWeapon* CCSPlayer_ItemServices::GiveNamedItem(const char*, int, CEconItemView*, bool, void*)
static const Source2ModType kGiveNamedItem[] = { Source2ModType::Pointer, Source2ModType::String, Source2ModType::Int32,
                                                 Source2ModType::Pointer, Source2ModType::Bool, Source2ModType::Pointer };
ISource2ModFunction* pGiveNamedItem = g_pSource2ModFunctions->FromGameConfig(g_PluginID, "CCSPlayer_ItemServices::GiveNamedItem",
    FunctionTypes{ Source2ModType::Entity, kGiveNamedItem, 6, true });

g_pSource2ModHooks->Hook(HookPoint::Function(pGiveNamedItem), HookTarget::All(), SOURCE2MOD_MEMBER(this, &MyPlugin::OnGiveNamedItem));

Use Entity for this when it is an entity (a script then gets an entity handle), Object when it is a schema object (services), Pointer for anything else.

Source2ModTypeC++In a script's handler
Bool, Int8, Int16, Int32bool, small integers, int32, uint32, enums, CPlayerSlotGetInt / GetBool, SetInt / SetBool
Int64int64, uint64decimal text: GetString, SetInt64
Float, Doublefloat, doubleGetFloat, SetFloat
EntityCEntityInstance* and every entity classGetEntity, SetEntity
Objecta schema object pointerGetObject, SetObject
Pointerany other pointeran opaque SchemaObject
Stringconst char*GetString; SetString points it at a copy that lives until the call is over
BufferStringCBufferString*GetString; SetBufferString writes in place
Vector, VectorValueVector* / const Vector&, Vector by valueGetVector; SetVector
Commandconst CCommand&GetCommandArgs — Arg(0) is the command; a function with one can be hooked, not called
IntRef, BoolRef, FloatRefint32*, bool*, float* out-parametersthe value it points at

Arguments that do not fit in registers are read from the stack: on Linux six integer-class and eight floating-point arguments go in registers, then eight more of either kind on the stack; on Windows at most fourteen in all.

The handler

A Function point's handler gets a FunctionContext: the arguments by index, as the function describes them — this of a member function is index 0 — and the return value.

C++SourcePawn
ctx.This()ctx.GetEntity(0) / GetObject(0)this of a member function, the first argument otherwise
ctx.GetArg<T>(i) / SetArg<T>(i, v)GetInt, GetFloat, GetEntity, GetString, GetVector, … / Set*an argument; a Vector or string argument is read as a pointer in C++ (GetArg<Vector*>)
ctx.GetReturn<T>() / SetReturn<T>(v)GetReturnInt, GetReturnEntity, … / SetReturn*the return value
ctx.CallOriginal<T>()ctx.CallOriginal()the function now, with the current arguments, past every hook on it
  • SetArg in a pre handler changes what the original — and the later handlers — get, whatever the answer (Ignore too; with Supersede the original does not run). This is the way to change an argument passed by value. Override without SetReturn means "run the original with these arguments"; the caller gets what it returns.
  • SetReturn with Override / Supersede is what the caller gets. In a post handler GetReturn is what the original returned.
  • CallOriginal in a pre handler really runs the function: answer Supersede afterwards (with SetReturn), or the game calls it a second time.
HookReturn<void> MyPlugin::OnGiveNamedItem(FunctionContext& ctx, bool post)
{
    const char* pszItem = ctx.GetArg<const char*>(1);
    if (pszItem && !V_strcmp(pszItem, "weapon_awp"))
    {
        ctx.SetReturn<CBasePlayerWeapon*>(nullptr);   // the caller gets no weapon
        return Action::Supersede;                     // and the game's function does not run
    }

    return Action::Ignore;
}

A Function handler returns HookReturn<void> in C++: the value goes through SetReturn, the action alone through the return.

Targets

HookTarget::All() (HookFunction) is every call. Instance(object) (HookFunctionInstance) and Class(name) (HookFunctionClass) narrow it to calls on one object or one class; for a free function the object is its first argument. A virtual needs a vtable to hook: one from FromVirtualTable, or an Instance / Class target — see Virtual functions.

// One client's string commands.
ISource2ModFunction* fn = g_pSource2ModFunctions->Find("CServerSideClient::ExecuteStringCommand");
g_pSource2ModHooks->Hook(HookPoint::Function(fn), HookTarget::Instance(pClient), OnStringCommand);

When the hook goes in

A Function point's hook is inserted immediately: the next call runs your handler. KHook falls back to its worker thread only when the function is running at that very moment. Unhook(id) takes it out again; what is still hooked when the plugin unloads goes with it.

In SourcePawn a Source2ModFunction is a handle. Deleting one from Find does not remove its hooks — the gamedata function is shared, and Unhook or the plugin unloading takes them down. Deleting a function the script owns (From*) drops the hooks on it.

Reference

  • ISource2ModFunctions — Find, Create, From*, FunctionTypes, Source2ModType
  • ISource2ModHooks — HookPoint::Function, FunctionContext
  • source2mod/gameconfig.inc (Source2ModFunction), source2mod/hooks.inc (FunctionContext, HookFunction*)

On this page