Calling Game Functions
Functions described once in gamedata and called by name from C++ and SourcePawn — Source2Mod's SDKCall.
A game function the API has no wrapper for is called by name. Its address
and its signature live in gamedata, once, and C++ plugins and scripts call
the same entry — SourceMod's SDKCall, without the PrepSDKCall_* dance in
every plugin.
Describing a function
A gamedata entry becomes callable when it has a "function" object. Where
the function is comes from the entry as before: "signatures" (a pattern or
an @symbol), or "offsets" for a virtual called on this.
{
"CBaseEntity::CollisionRulesChanged": {
"offsets": { "windows": 188, "linux": 187 },
"function": { "this": "entity", "return": "void", "params": [] }
},
"GetWeaponCSDataFromKey": {
"signatures": { "library": "server", "windows": "48 89 5C 24 ? 57 ...", "linux": "48 85 F6 0F 84 ..." },
"function": { "return": "object", "params": [ "int32", "string" ] }
},
"CCSGameRules::TerminateRound": {
"signatures": { "library": "server", "windows": "...", "linux": "..." },
"function": {
"this": "object",
"return": "void",
"params": {
"windows": [ "float", "int32", "pointer" ],
"linux": [ "int32", "pointer", "float" ]
}
}
}
}| Key | |
|---|---|
this | entity, object or pointer: a member function, and this is the first argument of a call. Left out for a free function. A virtual needs it. |
return | A type; void when left out. |
params | The types after this, or one list per platform when they differ. |
Types: void, bool, int8, int16, int32 (also enums, uint32),
int64, float, double, entity (CEntityInstance* and derived),
object (a schema object pointer), string (const char*), vector
(Vector* / const Vector&), vector_value (Vector by value), pointer
(anything else), buffer_string (CBufferString*), int_ref, bool_ref,
float_ref (out-parameters).
Every .json file in addons/source2mod/gamedata is read with the core's, so
a plugin ships its own entries as a file next to it.
Calling it
#include "source2mod/ISource2ModFunctions.h"
// Find once; the core keeps it until the server shuts down.
ISource2ModFunction* collision = g_pSource2ModFunctions->Find("CBaseEntity::CollisionRulesChanged");
ISource2ModFunction* weaponData = g_pSource2ModFunctions->Find("GetWeaponCSDataFromKey");
// The C++ type of the function, `this` first for a member function.
collision->Call<void(CBaseEntity*)>(pEntity);
auto* ak = weaponData->Call<CCSWeaponBaseVData*(int, const char*)>(-1, "weapon_ak47");
// Past every hook on the function (HookChain::Bypass).
collision->CallOriginal<void(CBaseEntity*)>(pEntity);// The types come from the gamedata: no AddParam.
Source2ModFunction collision = Source2ModFunction.Find("CBaseEntity::CollisionRulesChanged");
Source2ModFunction weaponData = Source2ModFunction.Find("GetWeaponCSDataFromKey");
collision.Call(entity);
int key = -1;
CCSWeaponBaseVData ak = view_as<CCSWeaponBaseVData>(weaponData.Call(key, "weapon_ak47"));
// Past every hook on the function.
collision.CallOriginal(entity);
delete collision;
delete weaponData;Find returns null when the entry is missing, has no "function", or its
signature does not match the running game; the reason is on the console.
In C++, Call<Signature> checks the signature against the gamedata on every
call (a few compares). When they differ nothing is called: the mismatch is
reported once and the result is value-initialised. The call itself goes
through a function pointer of the type you wrote, so the compiler handles
the calling convention.
Without gamedata
A function you find yourself is described in the call instead:
const Source2ModType params[] = { Source2ModType::Int32, Source2ModType::String };
ISource2ModFunction* fn = g_pSource2ModFunctions->Create(g_PluginID, address, nullptr, -1, false,
Source2ModType::Pointer, params, 2);
void* result = fn->Call<void*(int, const char*)>(-1, "weapon_awp");
g_pSource2ModFunctions->Destroy(fn); // or left to the plugin's unloadSource2ModFunction fn = Source2ModFunction.FromSignature("server", "48 89 5C 24 ? 57", Source2ModType_Pointer);
fn.AddParam(Source2ModType_Int32);
fn.AddParam(Source2ModType_String);FromGameConfig, FromSignatureRelative, FromSymbol, FromVirtualTable
and FromVirtual find a function the other ways; the table is on
Function hooks.
Hooking it
The same ISource2ModFunction is hooked with HookPoint::Function(fn)
(Source2ModHooks.HookFunction(fn, ...) in a script) — see
Function hooks. A function with a command
parameter can be hooked but not called.
Coming from SourceMod
| SourceMod | Source2Mod |
|---|---|
StartPrepSDKCall … PrepSDKCall_SetFromConf … EndPrepSDKCall | a "function" entry in gamedata + Find |
SDKCall(call, entity, ...) | Call(entity, ...) (C++: Call<Sig>(entity, ...)) |
SDKType_CBaseEntity / SDKType_PlainOldData / SDKType_Vector / SDKType_String | entity / int32, bool, float / vector / string |
SDKConf_Virtual / SDKConf_Signature | "offsets" / "signatures" in the same entry |
Writing Handlers
Handler signatures, contexts, HookReturn and the three actions, calling the original, changing arguments and return values, ids, owners and recursion.
Addresses
ISource2ModAddresses and the ADDR_* macros — calling and hooking the engine functions Source2Mod resolves, SOURCE2MOD_ORIGINAL, and HookChain on schema helpers.