Server plugins

From Valve Developer Community
(Redirected from Plugin unload)
Jump to navigation Jump to search
English (en)Deutsch (de)Español (es)Русский (ru)Translate (Translate)

Server plugins, commonly called Valve Server Plugins or VSPs, are native C++ libraries loaded by the Source engine to modify or extend the behaviour of a game server. They are commonly used for administration, maintenance, logging and other server-side functionality.

Valve Server Plugins use a small callback-based interface provided by the engine. They should not be confused with Metamod:Source plugins, which use a separate plugin API and hooking system.

Installing

Source automatically loads plugins defined in files matching <game>/addons/*.vdf.

.vdf format

These files should be formatted like this:

Plugin
{
	file		"<path to plugin>"
}

The path is relative to the folder containing gameinfo.txt, normally the game or mod base folder.

Managing

The following console commands are provided:

  • plugin_print — lists loaded plugins and their IDs
  • plugin_load
  • plugin_unload
  • plugin_pause — stops the engine from forwarding normal callbacks to the plugin
  • plugin_unpause
  • plugin_pause_all
  • plugin_unpause_all
Note.pngNote: The exact commands available and their behaviour can differ between Source engine branches.

Coding

A sample plugin project is available at src/utils/serverplugin_sample/ in the Source SDK 2013 repository.

A Valve Server Plugin must expose an object implementing IServerPluginCallbacks through its exported CreateInterface function. The engine retrieves this interface when loading the library and calls its methods at predefined points during server execution.

A plugin may also implement IGameEventListener2 and register itself with IGameEventManager2, but this is separate from the mandatory IServerPluginCallbacks interface.

The plugin receives two interface factories during IServerPluginCallbacks::Load():

  • An engine or application-system factory.
  • A GameDLL factory.

These factories allow the plugin to request public interfaces exposed by the engine and GameDLL.

Limitations

The Valve Server Plugin system is a deliberately limited callback interface. It is not a general-purpose engine or GameDLL hooking system.

The complete callback interface can be seen in iserverplugin.h.

Fixed callback interface

A Valve Server Plugin is called only at locations explicitly exposed through IServerPluginCallbacks. These include:

  • Plugin loading, unloading, pausing and unpausing.
  • Level initialization, activation and shutdown.
  • Per-frame processing.
  • Client connection and disconnection.
  • Client commands and settings changes.
  • Network ID validation.
  • Client ConVar query results.
  • Edict allocation and release.

A VSP cannot use the standard interface to hook an arbitrary engine, physics or GameDLL function. It cannot request a post-hook, change arbitrary function parameters or intercept a function that is not exposed through IServerPluginCallbacks.

In the engine implementation, most callbacks are sent to plugins before the corresponding GameDLL function is called. No matching post-callback is provided. See sv_plugin.cpp.

Limited override support

Only the following callbacks return PLUGIN_RESULT:

  • ClientConnect
  • ClientCommand
  • NetworkIDValidated

The meanings of PLUGIN_CONTINUE, PLUGIN_OVERRIDE and PLUGIN_STOP depend on the callback.

For example, in the referenced engine implementation:

  • ClientConnect can reject a connection, stop further processing or replace the GameDLL's final return value. If multiple plugins return PLUGIN_OVERRIDE, only the first override is retained.
  • ClientCommand checks for PLUGIN_STOP, which prevents the command from reaching later plugins and the GameDLL. It does not provide separate PLUGIN_OVERRIDE behaviour.
  • NetworkIDValidated can stop the notification from reaching later VSPs, but there is no GameDLL return value to replace.

All other callbacks are notifications. Their return types do not allow the plugin to suppress or replace the corresponding engine or GameDLL operation.

Plugin ordering and conflicts

VSP callbacks are dispatched sequentially through the engine's internal plugin list. The VSP API does not provide a standard mechanism for:

  • Assigning callback priorities.
  • Registering pre-hooks and post-hooks.
  • Calling the original function while controlling whether other hooks are fired.
  • Tracking ownership of privately installed hooks.
  • Automatically removing privately installed hooks.
  • Resolving conflicts when multiple plugins modify the same function.

A PLUGIN_STOP result may prevent later VSPs from receiving the same callback. Plugins should therefore not assume that every callback will reach every loaded VSP.

Access to server classes

The public VSP API exposes interfaces and edict_t objects. It does not expose private GameDLL classes such as CBaseEntity, CBasePlayer or game-specific player and weapon classes as a stable plugin interface.

An edict represents an engine-visible entity slot, but not every internal server object necessarily has an edict. The OnEdictAllocated and OnEdictFreed callbacks therefore do not provide complete lifetime notifications for every internal game or engine object.

Warning.pngWarning: Including a private GameDLL class declaration and casting an engine pointer to that class does not make the class part of the supported plugin API. The plugin becomes dependent on the exact class layout, virtual table, compiler ABI, game version and processor architecture. A game update can silently invalidate these assumptions and cause memory corruption or crashes.

Public interfaces obtained through the engine and GameDLL factories should be used wherever possible. Their interface version strings provide some compatibility checking, although interfaces can still differ between engine branches.

Private hooks and detours

A Valve Server Plugin can technically install its own virtual-table hooks, binary detours or memory patches. These techniques operate outside the Valve Server Plugin interface.

When using private hooks:

  • The plugin must locate the target function or object itself.
  • Signatures, offsets and layouts may differ between games, updates and processor architectures.
  • The plugin must restore all modified state when paused or unloaded.
  • Other plugins may attempt to patch the same function or virtual table.
  • There is no central system for determining hook order or safely chaining competing hooks.
  • Incomplete cleanup can leave dangling callbacks into an unloaded library.

Pausing a VSP stops normal callback forwarding and calls its Pause() method. It does not automatically remove hooks, detours or patches installed by the plugin itself.

Runtime unloading should therefore be handled carefully. A plugin must explicitly detach every hook and release every resource it registered.

Why Metamod:Source exists

Metamod:Source was created because the Valve Server Plugin interface was too limited for plugins requiring broader and coordinated access to engine and GameDLL functions.

The original design goals and development model are described in the Metamod:Source development documentation.

Its primary hooking component, SourceHook, provides a centralized system for hooking C++ virtual functions. Instead of allowing several unrelated plugins to patch the same virtual-table entry independently, Metamod:Source maintains a shared hook chain.

Depending on the hook and API version, Metamod:Source plugins can:

  • Register pre-hooks and post-hooks.
  • Override or supersede virtual function calls.
  • Inspect or change function parameters.
  • Replace return values.
  • Call the original function while controlling whether other hooks are fired.
  • Receive notifications when other Metamod:Source plugins are paused or unloaded.
  • Have SourceHook-managed hooks removed when their owning plugin is unloaded.

This reduces conflicts between plugins and provides a common API for functionality that would otherwise require every VSP to implement its own private hooking system.

Warning.pngWarning: Metamod:Source does not make private classes, non-virtual functions or binary layouts stable. Hooks using private virtual offsets, signatures or detours can still require game-, version- and architecture-specific configuration. SourceHook primarily coordinates virtual-function hooks; arbitrary non-virtual detours remain version-specific native modifications.

Metamod:Source loaded through the VSP loader

On many Source engine branches, Metamod:Source itself is loaded through a metamod.vdf file. This uses the Valve Server Plugin mechanism only to bootstrap Metamod:Source into the server process.

Plugins subsequently loaded by Metamod:Source implement its own plugin interface and use the Metamod:Source and SourceHook APIs. They are not ordinary Valve Server Plugins and are not restricted to the normal IServerPluginCallbacks callback model.

A regular VSP and Metamod:Source can run at the same time. Privately installed hooks can still conflict if they modify the same functions outside SourceHook.

Choosing between VSP and Metamod:Source

A Valve Server Plugin is suitable when the required functionality can be implemented using:

  • The callbacks already exposed through IServerPluginCallbacks.
  • Game events.
  • Public engine and GameDLL interfaces.
  • Commands and ConVars.
  • Edict allocation and release notifications.
  • Client connection rejection or client command blocking.

Metamod:Source is more suitable when the plugin requires:

  • Arbitrary virtual-function hooks.
  • Pre-hooks and post-hooks.
  • Parameter or return-value modification.
  • Coordinated hooks shared by several plugins.
  • Automatic hook ownership and cleanup.
  • A broader native plugin framework.

Neither interface provides a stable public API for private classes, non-virtual functions or internal engine data. Accessing those still requires version-specific native code.

Tip.pngTip: Use a Valve Server Plugin when its existing callbacks and public interfaces are sufficient. Use Metamod:Source when coordinated virtual-function hooking or a broader native plugin framework is required. For higher-level administration and gameplay scripting, consider SourceMod, which runs on top of Metamod:Source.
Tip.pngTip: Do not access interfaces during global or static object initialization. Wait until IServerPluginCallbacks::Load() has supplied valid interface factories.

Compiling

Windows
Build with the Visual Studio project provided; see Compiling under VS2008 or Compiling under VS2010 for help with upgrading.
Linux
Navigate to src/linux_sdk/, edit Makefile.plugin to include your files, and then execute make plugin.
If you are compiling for Source 2009, you must change the names of some Valve libraries in Makefile and Makefile.vpcm:
Tip.pngTip: It is probably best to make a copy of the linux_sdk folder before doing this, so that you do not break Source 2007 builds.
  • tier0_i486 → libtier0
  • vstdlib_i486 → libvstdlib
  • steam_api_i486 → libsteam_api
Use ldd -d <plugin.so> to check for dependencies. It will not be able to find the game libraries outside the game environment; check that their expected names are correct.
Mac
Not possible yet.

To debug your plugin, launch the server with -allowdebug where supported.

Listening to events

The IGameEventManager2 interface, or IGameEventManager on older branches, allows a plugin to listen for game events. Game events are fired by a mod when something of interest happens, such as a player dying or a bomb being planted.

Call IGameEventManager2::AddListener for each event the plugin wants to receive. The listener's FireGameEvent method is called when one of those events is fired.

Event fields are described by event resource files, particularly:

  • hl2/resource/serverevents.res
  • hl2/resource/GameEvents.res
  • <mod dir>/resource/ModEvents.res

Creating ConVars and Commands

ConVars let users configure plugin behaviour. ConCommands expose commands implemented by the plugin.

The following example creates a command named empty_version and a ConVar named plugin_empty. Commands can be run by the server or a client. Use the index supplied through SetCommandClient when determining the source of a command where applicable.

CON_COMMAND( empty_version, "Prints the version of the plugin" )
{
	Msg( "Version: 1.0.0.0\n" );
}

static ConVar empty_cvar( "plugin_empty", "0", 0, "Example plugin ConVar" );

Other tricks

Get player entities:

static CGlobalVars *gpGlobals;
static IVEngineServer *engine;

CON_COMMAND( list_players, "Prints the name of each connected player" )
{
	for ( int i = 1; i <= gpGlobals->maxClients; i++ )
	{
		edict_t *edict = engine->PEntityOfEntIndex( i );

		if ( !edict )
			continue;

		IPlayerInfo *playerInfo = playerinfomanager->GetPlayerInfo( edict );

		if ( playerInfo )
			Msg( "%s\n", playerInfo->GetName() );
	}
}

Add and remove a server tag:

#include <string>

void AddTag( const char *tag )
{
	static ConVar *sv_tags = cvar->FindVar( "sv_tags" );
	std::string tags = sv_tags->GetString();

	if ( tags.find( tag ) != std::string::npos )
		return;

	if ( !tags.empty() && tags.back() != ',' )
		tags.append( "," );

	tags.append( tag );
	sv_tags->SetValue( tags.c_str() );
}

void RemoveTag( const char *tag )
{
	static ConVar *sv_tags = cvar->FindVar( "sv_tags" );
	std::string tags = sv_tags->GetString();

	size_t start = tags.find( tag );

	if ( start == std::string::npos )
		return;

	size_t end = tags.find( ',', start );

	if ( end == std::string::npos )
		tags.erase( start );
	else
		tags.erase( start, end - start + 1 );

	sv_tags->SetValue( tags.c_str() );
}

See also