ISource2ModHooks

One way to hook anything, the same from C++ and SourcePawn:

Hook = Point (what) + Target (on which object) + Handler (with its Context)

Points (HookPoint::*):

  • game functions with a typed context: TakeDamage, Touch, PostThink, AcceptInput, the movement functions, PlayerChat (Host_Say), ... (ISource2ModHookContexts.h);
  • entity virtuals with a typed context, hooked on the target's own class (SourceMod's SDKHooks): Spawn, Use, StartTouch, EndTouch, Blocked, Think, Killed, WeaponPickup, WeaponSwitch;
  • engine callbacks: GameFrame, StartupServer, ClientConnect, ClientConnected, ClientPutInServer, ClientDisconnect, ClientVoice, ClientSettingsChanged, the Steam API, LoadEventsFromFile;
  • any function: HookPoint::Function(fn), fn an ISource2ModFunction -- a gamedata entry (Find), a signature, a symbol, a vtable slot, an engine interface's virtual, a core address (ISource2ModFunctions::From*). SourceMod's DHooks.

Targets (HookTarget):

  • All() every call: a detour, every instance;
  • Instance(object) one object: an entity, a CServerSideClient, a services object, the game rules, ...;
  • Class("CCSPlayerPawn") every object of that C++ class or one derived from it (RTTI). The object is the function's this (for a free function its first argument). A virtual is hooked once, on the vtable, and the core compares the object before a handler runs. Engine callbacks take All() only.

Filters (HookFilter, optional): a point whose object is an entity compares its designer name ("logic_relay", "player"); AcceptInput also the input's name.

Entity events (created, spawned, deleted, parent changed) and entity outputs are listeners of ISource2ModEntities, the way commands, convars and game events have theirs: this is for functions.

Every Hook() returns a Source2ModHookId; Unhook(id) drops it. What a plugin still holds when it unloads, the core drops for it.

KHook (ISource2ModKHook.h: KHOOK_VIRTUAL, KHOOK_MEMBER, KHOOK_FUNCTION) stays available to C++ plugins, but prefer this: a plugin with KHook hooks of its own is "raw tier" in s2m list and needs a rebuild whenever KHook or the engine changes, a plugin on ISource2ModHooks only a core update.

// Half damage for one pawn.
g_pSource2ModHooks->Hook(HookPoint::TakeDamage, HookTarget::Instance(pawn),
    [](TakeDamageContext& ctx, bool post) -> HookReturn<std::int64_t>
    {
        ctx.info->m_flDamage /= 2;     // the original sees it: the info is passed by pointer
        return Action::Ignore;
    });

// Every tick, after the game's own work.
g_pSource2ModHooks->Hook(HookPoint::GameFrame, HookTarget::All(), SOURCE2MOD_MEMBER(this, &Plugin::OnGameFrame), true);

// One input of one kind of entity.
g_pSource2ModHooks->Hook(HookPoint::AcceptInput, HookTarget::All(), OnRelayInput, false, HookFilter{ "logic_relay", "Trigger" });

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

Interface id ISource2ModHooks001 (SOURCE2MOD_HOOKS_INTERFACE) · Global g_pSource2ModHooks · Header source2mod/ISource2ModHooks.h

Types

HookHandler

template <typename CONTEXT> using HookHandler = Source2ModCallback<HookReturn<typename CONTEXT::Return>(CONTEXT& ctx, bool post)>;

A handler: the context, and whether this is the post pass. A function, an object and a method (SOURCE2MOD_MEMBER), or a lambda.

Methods

Hook

template <typename CONTEXT> Source2ModHookId Hook(HookPointOf<CONTEXT> point, const HookTarget& target, typename HookNonDeduced<HookHandler<CONTEXT>>::type handler, bool post = false, const HookFilter& filter = {});

Hooks @p point on @p target.

ParameterTypeDefaultDescription
pointHookPointOf<CONTEXT>
targetconst HookTarget&
handlertypename HookNonDeduced<HookHandler<CONTEXT>>::type
postboolfalsefalse: before the original (may change arguments, Override / Supersede); true: after it (sees the result).
filterconst HookFilter&{}Designer / output / input name, for the points that take one.

Returns Source2ModHookId — The hook's id for Unhook(); 0 when the point does not exist on this game, the target does not fit the point, or the function of a Function point is null.

Unhook

virtual bool Unhook(Source2ModHookId id) = 0;

Drops one hook. @return false when there is no such hook.

ParameterType
idSource2ModHookId

IsAvailable

virtual bool IsAvailable(HookPointId point) = 0;

Whether the point resolves on the running game (its gamedata matched).

ParameterType
pointHookPointId

AddHook

virtual Source2ModHookId AddHook(HookPointId point, ISource2ModFunction* function, const HookTarget& target, const HookFilter& filter, const void* handler, bool post) = 0;

Type-erased Hook(): @p handler is a HookHandler<CONTEXT>* for @p point's context, copied by the core. Hook() is the way in.

ParameterType
pointHookPointId
functionISource2ModFunction*
targetconst HookTarget&
filterconst HookFilter&
handlerconst void*
postbool

HookTarget

One way to hook anything, the same from C++ and SourcePawn:

Hook = Point (what) + Target (on which object) + Handler (with its Context)

Points (HookPoint::*):

  • game functions with a typed context: TakeDamage, Touch, PostThink, AcceptInput, the movement functions, PlayerChat (Host_Say), ... (ISource2ModHookContexts.h);
  • entity virtuals with a typed context, hooked on the target's own class (SourceMod's SDKHooks): Spawn, Use, StartTouch, EndTouch, Blocked, Think, Killed, WeaponPickup, WeaponSwitch;
  • engine callbacks: GameFrame, StartupServer, ClientConnect, ClientConnected, ClientPutInServer, ClientDisconnect, ClientVoice, ClientSettingsChanged, the Steam API, LoadEventsFromFile;
  • any function: HookPoint::Function(fn), fn an ISource2ModFunction -- a gamedata entry (Find), a signature, a symbol, a vtable slot, an engine interface's virtual, a core address (ISource2ModFunctions::From*). SourceMod's DHooks.

Targets (HookTarget):

  • All() every call: a detour, every instance;
  • Instance(object) one object: an entity, a CServerSideClient, a services object, the game rules, ...;
  • Class("CCSPlayerPawn") every object of that C++ class or one derived from it (RTTI). The object is the function's this (for a free function its first argument). A virtual is hooked once, on the vtable, and the core compares the object before a handler runs. Engine callbacks take All() only.

Filters (HookFilter, optional): a point whose object is an entity compares its designer name ("logic_relay", "player"); AcceptInput also the input's name.

Entity events (created, spawned, deleted, parent changed) and entity outputs are listeners of ISource2ModEntities, the way commands, convars and game events have theirs: this is for functions.

Every Hook() returns a Source2ModHookId; Unhook(id) drops it. What a plugin still holds when it unloads, the core drops for it.

KHook (ISource2ModKHook.h: KHOOK_VIRTUAL, KHOOK_MEMBER, KHOOK_FUNCTION) stays available to C++ plugins, but prefer this: a plugin with KHook hooks of its own is "raw tier" in s2m list and needs a rebuild whenever KHook or the engine changes, a plugin on ISource2ModHooks only a core update.

// Half damage for one pawn.
g_pSource2ModHooks->Hook(HookPoint::TakeDamage, HookTarget::Instance(pawn),
    [](TakeDamageContext& ctx, bool post) -> HookReturn<std::int64_t>
    {
        ctx.info->m_flDamage /= 2;     // the original sees it: the info is passed by pointer
        return Action::Ignore;
    });

// Every tick, after the game's own work.
g_pSource2ModHooks->Hook(HookPoint::GameFrame, HookTarget::All(), SOURCE2MOD_MEMBER(this, &Plugin::OnGameFrame), true);

// One input of one kind of entity.
g_pSource2ModHooks->Hook(HookPoint::AcceptInput, HookTarget::All(), OnRelayInput, false, HookFilter{ "logic_relay", "Trigger" });

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

Fields

FieldTypeDescription
kindKind
instancevoid*
classNameconst char*Kind::Class: the RTTI name ("CCSPlayerPawn")

All

static HookTarget All();

Instance

static HookTarget Instance(const void* object);
ParameterType
objectconst void*

Class

static HookTarget Class(const char* name);
ParameterType
nameconst char*

Kind

enum class Kind : int32_t
NameValue
All0
Instance
Class

HookFilter

Compared before a handler runs; nullptr or "" matches anything.

Fields

FieldTypeDescription
designerNameconst char*the object is an entity: its designer name
nameconst char*AcceptInput: the input

HookPointOf

A point and the context its handlers get, so Hook() checks the handler's signature at compile time.

Fields

FieldTypeDescription
idHookPointId
functionISource2ModFunction*HookPointId::Function only

FunctionContext

The context of HookPoint::Function: the arguments by index, as the function describes them (this of a member function is index 0), and the return value.

SetArg in a pre handler changes what the original (and the later handlers) get. SetReturn with Override / Supersede is what the caller gets; in a post handler GetReturn is what the original returned. A Vector / string argument is read as a pointer (GetArg<Vector*>).

Function

ISource2ModFunction* Function() const;

This

void* This() const;

this of a member function, the first argument otherwise.

GetArg

template <typename T> T GetArg(int index) const;
ParameterType
indexint

SetArg

template <typename T> void SetArg(int index, T value);
ParameterType
indexint
valueT

GetReturn

template <typename T> T GetReturn() const;

SetReturn

template <typename T> void SetReturn(T value);
ParameterType
valueT

CallOriginal

template <typename T = void> T CallOriginal();

The function now, with the current arguments, past every hook on it. In a pre handler answer Supersede afterwards (with SetReturn), or the game calls it a second time.

Return

using Return = void;

HookReturn

What a handler answers: an Action, and for a function with a return value optionally the value -- KHook's { action, value }.

return { Action::Supersede, 0 };              // skip the original, it returns 0
return { Action::Override, AcquireResult::NotAllowedByMode };
return { Action::Supersede, ctx.CallOriginal() };
return Action::Ignore;                         // an action alone

An action alone takes its value from ctx.result, so a handler that sets the field and returns Override / Supersede keeps working. Several handlers on one point: Supersede stops the chain, otherwise the strongest action wins; each value a handler returns lands in ctx.result right away.

Fields

FieldType
actionAction
hasValuebool

Constructors

HookReturn(Action a);
HookReturn(Action a, RETURN v);

Enums

HookPointId

enum class HookPointId : int32_t

Every point. The numbers are SourcePawn's HookPoint_* too.

NameValueDescription
TakeDamage0Game functions (ISource2ModHookContexts.h).
CanAcquire
CanMove
CanUse
PostThink
ProcessUsercmds
SimulateUserCommands
RunCommand
AcceptInput
Touch
DropWeapon
AirAccelerate
AirMove
CanUnduck
CategorizePosition
CheckFalling
CheckParameters
CheckVelocity
CheckWater
Duck
Friction
FullWalkMove
GroundAccelerate
LadderMove
MoveInit
PlayerMove
ProcessMovement
SetupMove
TryPlayerMove
WalkMove
WaterMove
OnJumpLegacy
OnJumpModern
CheckJumpButtonLegacy
CheckJumpButtonModern
PlayerChat
Spawn50Entity virtuals, on the target's class vtable (SDKHooks).
Use
StartTouch
EndTouch
Blocked
Think
Killed
WeaponPickup
WeaponSwitch
GameFrame100Engine callbacks.
StartupServer
ClientConnect
ClientConnected
ClientPutInServer
ClientDisconnect
ClientVoice
ClientSettingsChanged
GameServerSteamAPIActivated
GameServerSteamAPIDeactivated
LoadEventsFromFile
Function300HookPoint::Function.

Functions

Function

inline HookPointOf<FunctionContext> Function(ISource2ModFunction* fn);

Any function from ISource2ModFunctions.

ParameterType
fnISource2ModFunction*

On this page