ISource2ModPlugin
Plugin interface and integration layer for Source2Mod.
This file defines:
- Plugin lifecycle interface (ISource2ModPlugin)
- Listener system (ISource2ModListener)
- Global variables and exposure macros
- Interface helper macros
Interface id ISource2ModGameHooks003 (SOURCE2MOD_GAMEHOOKS_INTERFACE) · Interface id Source2ModKHook001 (SOURCE2MOD_KHOOK_INTERFACE) · Interface id Source2ModKHookVersion001 (SOURCE2MOD_KHOOK_VERSION_INTERFACE) · Interface id Source2ModKHookHeader001 (SOURCE2MOD_KHOOK_HEADER_INTERFACE) · Header source2mod/ISource2ModPlugin.h
Types
PluginId
using PluginId = int;Methods
GetApiVersion
virtual int GetApiVersion();Load
virtual bool Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) = 0;Called when plugin is loaded.
| Parameter | Type | Description |
|---|---|---|
id | PluginId | Plugin identifier |
api | ISource2ModAPI* | Source2Mod API instance |
error | char* | Buffer for error message |
maxlen | size_t | Error buffer size |
late | bool | False only when Source2Mod's startup pass loads the plugin. True for every load after that: "s2m load", "source2mod refresh" and the file watcher's hot reload. Players and entities may already exist. |
Returns bool — True on success, false on failure
Unload
virtual bool Unload(char* error, size_t maxlen);Called when plugin is unloaded.
| Parameter | Type |
|---|---|
error | char* |
maxlen | size_t |
Returns bool — True on success
GetAuthor
virtual const char* GetAuthor() = 0;Plugin author name
GetName
virtual const char* GetName() = 0;Plugin name
GetDescription
virtual const char* GetDescription() = 0;Plugin description
GetVersion
virtual const char* GetVersion() = 0;Plugin version
GetKHookCommit
virtual const char* GetKHookCommit();The KHook commit this plugin was built against (the SDK's vendor/khook), "" when built without git.
GetRawHookCount
virtual int GetRawHookCount();How many KHook hooks the plugin declared through the KHOOK_* macros: none means the plugin is on the stable tier and an engine update is the core's problem alone.
ISource2ModListener
Listener for source2mod and engine events.
Allows plugins to react to lifecycle and map events.
OnPluginLoad
virtual void OnPluginLoad(PluginId id, const char* name);Called when a source2mod plugin (.smx) has loaded, after its Load() returned. The id is a source2mod plugin id -- the one "s2m list" shows. Fires for your own plugin too (a listener added in Load() gets it right after Load() returns); compare against your own id to skip it. The name is what the plugin's GetName() returns.
| Parameter | Type |
|---|---|
id | PluginId |
name | const char* |
OnPluginUnload
virtual void OnPluginUnload(PluginId id, const char* name);Called when a source2mod plugin (.smx) is about to unload, before its Unload() runs. Fires for your own plugin too, like OnPluginLoad().
| Parameter | Type |
|---|---|
id | PluginId |
name | const char* |
OnMetamodPluginLoad
virtual void OnMetamodPluginLoad(PluginId id);Called when a Metamod:Source plugin is loaded.
The id is Metamod's, from a different numbering than Source2Mod's -- which is why this is not the same callback as OnPluginLoad().
| Parameter | Type |
|---|---|
id | PluginId |
OnMetamodPluginUnload
virtual void OnMetamodPluginUnload(PluginId id);Called when a Metamod:Source plugin is unloaded.
| Parameter | Type |
|---|---|
id | PluginId |
OnAllSource2ModPluginsLoaded
virtual void OnAllSource2ModPluginsLoaded();Called when all source2mod plugins are loaded
OnAllMetamodPluginsLoaded
virtual void OnAllMetamodPluginsLoaded();Called when all Metamod plugins are loaded
OnLevelInit
virtual void OnLevelInit(const char* mapName, const char* mapEntities, const char* oldLevel, const char* landmarkName, bool loadGame, bool background);Called when a map is initialized.
| Parameter | Type |
|---|---|
mapName | const char* |
mapEntities | const char* |
oldLevel | const char* |
landmarkName | const char* |
loadGame | bool |
background | bool |
OnLevelShutdown
virtual void OnLevelShutdown();Called when a map is shutting down
OnSource2ModQuery
virtual void* OnSource2ModQuery(const char* iface, int* ret);Called when an interface is requested from source2mod.
| Parameter | Type |
|---|---|
iface | const char* |
ret | int* |
Returns void* — Pointer to interface or nullptr
OnMetamodQuery
virtual void* OnMetamodQuery(const char* iface, int* ret);Called when a Metamod:Source plugin asks for an interface.
A Metamod plugin reaching for something through g_SMAPI->MetaFactory() ends up here, so a source2mod plugin can hand its own interfaces to code that did not load through Source2Mod.
By default this answers exactly like OnSource2ModQuery(), which is what a plugin usually wants -- an interface worth exposing is worth exposing to either caller. Override it to answer a Metamod plugin differently, or return nullptr to keep an interface for source2mod plugins only. The two are separate callbacks precisely so that choice exists: OnSource2ModQuery() cannot tell who is asking.
| Parameter | Type |
|---|---|
iface | const char* |
ret | int* |
Returns void* — Pointer to interface or nullptr
OnGameFrame
virtual void OnGameFrame(bool simulating, bool firstTick, bool lastTick);Post ISource2Server::GameFrame: once a frame, after Source2Mod's own per-frame work (timers, menus).
| Parameter | Type |
|---|---|
simulating | bool |
firstTick | bool |
lastTick | bool |
OnStartupServer
virtual void OnStartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession* session, const char* mapName);Post INetworkServerService::StartupServer: a new server session; the entity system of the new map is already Source2Mod's.
| Parameter | Type |
|---|---|
config | const GameSessionConfiguration_t& |
session | ISource2WorldSession* |
mapName | const char* |
OnClientConnect
virtual bool OnClientConnect(CPlayerSlot slot, const char* name, uint64 xuid, const char* networkId, bool unk1, CBufferString* rejectReason);Pre ISource2GameClients::ClientConnect: a player asks to join (the engine does not call it for bots), right after OnClientConnected. networkId is the player's "ip:port" here. Return false to refuse; the reason goes into rejectReason (the CS2 client shows its own "rejected by game" text whatever it says). The first plugin to refuse ends it -- the ones after it are not asked.
| Parameter | Type |
|---|---|
slot | CPlayerSlot |
name | const char* |
xuid | uint64 |
networkId | const char* |
unk1 | bool |
rejectReason | CBufferString* |
OnClientConnected
virtual void OnClientConnected(CPlayerSlot slot, const char* name, uint64 xuid, const char* networkId, const char* address, bool fakePlayer);Post ISource2GameClients::OnClientConnected: a player or a bot (fakePlayer) got a slot. For a player the engine calls this before ClientConnect, so it also comes for a connection that is then refused. networkId is the SteamID ("[U:1:...]"), address the player's "ip:port".
| Parameter | Type |
|---|---|
slot | CPlayerSlot |
name | const char* |
xuid | uint64 |
networkId | const char* |
address | const char* |
fakePlayer | bool |
OnClientPutInServer
virtual void OnClientPutInServer(CPlayerSlot slot, const char* name, int type, uint64 xuid);Post ISource2GameClients::ClientPutInServer: the player (type 0) or bot (type 1) is in the game and has a controller.
| Parameter | Type |
|---|---|
slot | CPlayerSlot |
name | const char* |
type | int |
xuid | uint64 |
OnClientDisconnect
virtual void OnClientDisconnect(CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* name, uint64 xuid, const char* networkId);Pre ISource2GameClients::ClientDisconnect: the player is leaving and everything of theirs is still there.
| Parameter | Type |
|---|---|
slot | CPlayerSlot |
reason | ENetworkDisconnectionReason |
name | const char* |
xuid | uint64 |
networkId | const char* |
OnClientDisconnectPost
virtual void OnClientDisconnectPost(CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* name, uint64 xuid, const char* networkId);Post ISource2GameClients::ClientDisconnect: the game is done with the player; Source2Mod forgets the slot's permissions and cookies only after this.
| Parameter | Type |
|---|---|
slot | CPlayerSlot |
reason | ENetworkDisconnectionReason |
name | const char* |
xuid | uint64 |
networkId | const char* |
OnClientVoice
virtual void OnClientVoice(CPlayerSlot slot);Post ISource2GameClients::ClientVoice: the player in this slot is talking.
| Parameter | Type |
|---|---|
slot | CPlayerSlot |
OnClientSettingsChanged
virtual void OnClientSettingsChanged(CPlayerSlot slot);Post ISource2GameClients::ClientSettingsChanged: the player's convars (name, language, ...) changed.
| Parameter | Type |
|---|---|
slot | CPlayerSlot |
OnGameServerSteamAPIActivated
virtual void OnGameServerSteamAPIActivated();Post ISource2Server::GameServerSteamAPIActivated: the Steam API is up.
OnGameServerSteamAPIDeactivated
virtual void OnGameServerSteamAPIDeactivated();Pre ISource2Server::GameServerSteamAPIDeactivated: the Steam API is about to go.
OnLoadEventsFromFile
virtual void OnLoadEventsFromFile(IGameEventManager2* manager, const char* filename, bool searchAll);Post IGameEventManager2::LoadEventsFromFile, with the manager it was called on: the place to keep it, or to register a .gameevents file of your own.
| Parameter | Type |
|---|---|
manager | IGameEventManager2* |
filename | const char* |
searchAll | bool |
OnClientAuthorized
virtual void OnClientAuthorized(CPlayerSlot slot, uint64 steamId);Steam validated the player in this slot: from now on they are checked under steamId whatever SteamAuthMode says. Not called for bots.
| Parameter | Type |
|---|---|
slot | CPlayerSlot |
steamId | uint64 |
OnClientAuthorizeFailed
virtual void OnClientAuthorizeFailed(CPlayerSlot slot, uint64 steamId);Steam refused the ticket of the player in this slot; steamId is the one they claimed.
| Parameter | Type |
|---|---|
slot | CPlayerSlot |
steamId | uint64 |
OnPermissionsChanged
virtual void OnPermissionsChanged(uint64 steamId);What this SteamID64 holds may have changed -- a grant, a group, a reload. 0 when a change can touch anybody (a group was edited, permissions.json was re-read).
| Parameter | Type |
|---|---|
steamId | uint64 |
Functions
Source2ModKHookVersionMatches
inline bool Source2ModKHookVersionMatches(const char* coreHeader, const char* coreCommit, char* error, size_t maxlen, const char** note);The load-time half of the KHook check; KHOOK_INIT() calls it.
Compares the khook.hpp hashes when both sides have one, else the commits (a core older than the header hash). Returns false and fills error only when the compared values are known and differ. A side that cannot say -- a core too old to answer, a plugin built without git -- makes the check impossible, not failed: that goes out through note and the load goes on.
| Parameter | Type |
|---|---|
coreHeader | const char* |
coreCommit | const char* |
error | char* |
maxlen | size_t |
note | const char** |
Source2ModRawHookCount
int Source2ModRawHookCount();
inline int Source2ModRawHookCount();Defined by ISource2ModHooks.h for a plugin; the core never asks itself.
Macros
SOURCE2MOD_KHOOK_COMMIT
#define SOURCE2MOD_KHOOK_COMMIT "unknown"Set by the SDK's CMakeLists.txt / AMBuildScript from vendor/khook (the core: from metamod's copy). A build that bypassed both gets "unknown", which the check reports and lets through.
SOURCE2MOD_KHOOK_HEADER
#define SOURCE2MOD_KHOOK_HEADER "unknown"Set by the SDK's CMakeLists.txt / AMBuildScript: the first 16 hex digits of the SHA-256 of khook.hpp with CRs dropped. A build that bypassed both gets "unknown", and the check falls back to the commit.
SOURCE2MOD_INTERFACE_NAME
#define SOURCE2MOD_INTERFACE_NAME "Source2ModPlugin001"Plugin interface name
SOURCE2MOD_PLAPI_VERSION
#define SOURCE2MOD_PLAPI_VERSION 4Plugin API version
SOURCE2MOD_PLAPI_NAME
#define SOURCE2MOD_PLAPI_NAME "ISource2ModPlugin"Plugin API interface name
SOURCE2MOD_EXPOSURE_FUNC
#define SOURCE2MOD_EXPOSURE_FUNC(name, var) EXPOSE_SINGLE_INTERFACE_GLOBALVAR(ISource2ModPlugin, ISource2ModPlugin, SOURCE2MOD_PLAPI_NAME, var);Exposes plugin interface to engine.
SOURCE2MOD_DEFINE_GLOBALVARS
#define SOURCE2MOD_DEFINE_GLOBALVARS() \
ISource2ModAPI* g_Source2ModAPI = nullptr;
ISource2ModPlugin* g_PluginAPI = nullptr;
PluginId g_PluginID = 0;
const char* g_Source2ModCoreKHookCommit = nullptr;
const char* g_Source2ModCoreKHookHeader = nullptr;
...Defines Source2Mod globals -- the API, the plugin identity and one pointer per source2mod interface.
Part of SOURCE2MOD_EXPOSE for a plugin. The core expands it on its own too and fills the pointers with SOURCE2MOD_FILLVARS(), so SDK code reads the same globals (ADDR_*, g_pSource2ModGameConfig, ...) whichever binary it is linked into.
SOURCE2MOD_EXPOSE
#define SOURCE2MOD_EXPOSE(name, var) \
SOURCE2MOD_DEFINE_GLOBALVARS()
namespace KHook { KHook::IKHook* __exported__khook = nullptr; }
SOURCE2MOD_EXPOSURE_FUNC(name, var)Defines global plugin variables.
Creates:
- everything SOURCE2MOD_DEFINE_GLOBALVARS() does
- KHook::__exported__khook, the detour engine (see SOURCE2MOD_KHOOK_INTERFACE)
SOURCE2MOD_GLOBALVARS
#define SOURCE2MOD_GLOBALVARS() \
extern ISource2ModAPI* g_Source2ModAPI;
extern ISource2ModPlugin* g_PluginAPI;
extern PluginId g_PluginID;
extern const char* g_Source2ModCoreKHookCommit;
extern const char* g_Source2ModCoreKHookHeader;
extern ISource2ModAddresses* g_pSource2ModAddresses;
...Declares external globals.
SOURCE2MOD_FILLVARS
#define SOURCE2MOD_FILLVARS(api) \
g_pSource2ModAddresses = (ISource2ModAddresses*) (api)->Source2ModFactory(SOURCE2MOD_ADDRESSES_INTERFACE, nullptr, nullptr);
g_pSource2ModCommands = (ISource2ModCommands*) (api)->Source2ModFactory(SOURCE2MOD_COMMANDS_INTERFACE, nullptr, nullptr);
...Points every source2mod interface global at what the API hands out.
Part of SOURCE2MOD_SAVEVARS() for a plugin; the core calls it once its interfaces exist (see SOURCE2MOD_DEFINE_GLOBALVARS()).
SOURCE2MOD_SAVEVARS
#define SOURCE2MOD_SAVEVARS() \
g_Source2ModAPI = api;
/* s2sdk's GameEntitySystem() (entity2/entitysystem.cpp) reads it */
g_pGameResourceServiceServer = api->GetGameResourceService();
g_PluginAPI = static_cast<ISource2ModPlugin*>(this);
g_PluginID = id;
KHook::__exported__khook = static_cast<KHook::IKHook*>(api->Source2ModFactory(SOURCE2MOD_KHOOK_INTERFACE, nullptr, nullptr));
...Initializes global variables inside Load().
SOURCE2MOD_LOG
#define SOURCE2MOD_LOG g_Source2ModAPI->LogShortcut for logging
SOURCE2MOD_CONPRINT
#define SOURCE2MOD_CONPRINT g_Source2ModAPI->ConPrintConsole print
SOURCE2MOD_CONPRINTF
#define SOURCE2MOD_CONPRINTF g_Source2ModAPI->ConPrintfConsole printf
GET_VALVE_IFACE_CURRENT
#define GET_VALVE_IFACE_CURRENT(v_factory, v_var, v_type, v_name) \
v_var = (v_type *)g_Source2ModAPI->QueryInterface(g_Source2ModAPI->v_factory(), v_name);
if (!v_var)
{
if (error && maxlen)
{
g_Source2ModAPI->Format(error, maxlen, "Could not find interface: %s", v_name);
}
return false;
}Macro for automatically getting a current or newer Valve interface.
GET_VALVE_IFACE_ANY
#define GET_VALVE_IFACE_ANY(v_factory, v_var, v_type, v_name) \
v_var = (v_type *)g_Source2ModAPI->QueryInterface(g_Source2ModAPI->v_factory(), v_name, 0);
if (!v_var)
{
if (error && maxlen)
{
g_Source2ModAPI->Format(error, maxlen, "Could not find interface: %s", v_name);
}
return false;
}Same as GET_IFACE, except searches for any.
GET_SOURCE2MOD_IFACE
#define GET_SOURCE2MOD_IFACE(v_var, v_type, v_name, v_ret) \
v_var = (v_type *)g_Source2ModAPI->Source2ModFactory(v_name, &(v_ret), nullptr);
if (!v_var)
{
(v_ret) = SOURCE2MOD_IFACE_FAILED;
}Macro for automatically getting a Source2Mod plugin exposed interface.
Unlike the GET_VALVE_IFACE_* macros this one never returns on its own, so it is usable outside Load() -- in OnAllSource2ModPluginsLoaded(), say, where the interface belongs to another plugin and its absence is an ordinary outcome rather than a fatal one. Test v_ret against SOURCE2MOD_IFACE_OK and decide.
GET_METAMOD_IFACE
#define GET_METAMOD_IFACE(v_var, v_type, v_name, v_ret) \
v_var = (v_type *)g_Source2ModAPI->MetaFactory(v_name, &(v_ret), nullptr);
if (!v_var)
{
(v_ret) = SOURCE2MOD_IFACE_FAILED;
}Macro for automatically getting a Metamod:Source plugin exposed interface.
Same contract as GET_SOURCE2MOD_IFACE: it never returns on its own, it only reports through v_ret. Metamod fills v_ret with META_IFACE_OK/META_IFACE_FAILED, which are binary-compatible with the SOURCE2MOD_IFACE_* values, so compare against those rather than pulling a metamod header in here.