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
This is the main entry point for plugin developers.

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.

This is the main initialization function.
ParameterTypeDescription
idPluginIdPlugin identifier
apiISource2ModAPI*Source2Mod API instance
errorchar*Buffer for error message
maxlensize_tError buffer size
lateboolFalse 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.

ParameterType
errorchar*
maxlensize_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.

ParameterType
idPluginId
nameconst 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().

ParameterType
idPluginId
nameconst 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().

ParameterType
idPluginId

OnMetamodPluginUnload

virtual void OnMetamodPluginUnload(PluginId id);

Called when a Metamod:Source plugin is unloaded.

ParameterType
idPluginId

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.

ParameterType
mapNameconst char*
mapEntitiesconst char*
oldLevelconst char*
landmarkNameconst char*
loadGamebool
backgroundbool

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.

ParameterType
ifaceconst char*
retint*

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.

ParameterType
ifaceconst char*
retint*

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).

ParameterType
simulatingbool
firstTickbool
lastTickbool

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.

ParameterType
configconst GameSessionConfiguration_t&
sessionISource2WorldSession*
mapNameconst 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.

ParameterType
slotCPlayerSlot
nameconst char*
xuiduint64
networkIdconst char*
unk1bool
rejectReasonCBufferString*

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".

ParameterType
slotCPlayerSlot
nameconst char*
xuiduint64
networkIdconst char*
addressconst char*
fakePlayerbool

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.

ParameterType
slotCPlayerSlot
nameconst char*
typeint
xuiduint64

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.

ParameterType
slotCPlayerSlot
reasonENetworkDisconnectionReason
nameconst char*
xuiduint64
networkIdconst 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.

ParameterType
slotCPlayerSlot
reasonENetworkDisconnectionReason
nameconst char*
xuiduint64
networkIdconst char*

OnClientVoice

virtual void OnClientVoice(CPlayerSlot slot);

Post ISource2GameClients::ClientVoice: the player in this slot is talking.

ParameterType
slotCPlayerSlot

OnClientSettingsChanged

virtual void OnClientSettingsChanged(CPlayerSlot slot);

Post ISource2GameClients::ClientSettingsChanged: the player's convars (name, language, ...) changed.

ParameterType
slotCPlayerSlot

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.

ParameterType
managerIGameEventManager2*
filenameconst char*
searchAllbool

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.

ParameterType
slotCPlayerSlot
steamIduint64

OnClientAuthorizeFailed

virtual void OnClientAuthorizeFailed(CPlayerSlot slot, uint64 steamId);

Steam refused the ticket of the player in this slot; steamId is the one they claimed.

ParameterType
slotCPlayerSlot
steamIduint64

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).

ParameterType
steamIduint64

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.

ParameterType
coreHeaderconst char*
coreCommitconst char*
errorchar*
maxlensize_t
noteconst 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 4

Plugin 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().

Must be called in plugin Load(), with its parameters under their declared names (id, api, error, maxlen).

SOURCE2MOD_LOG

#define SOURCE2MOD_LOG g_Source2ModAPI->Log

Shortcut for logging

SOURCE2MOD_CONPRINT

#define SOURCE2MOD_CONPRINT g_Source2ModAPI->ConPrint

Console print

SOURCE2MOD_CONPRINTF

#define SOURCE2MOD_CONPRINTF g_Source2ModAPI->ConPrintf

Console 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.

On this page