Writing Extensions from Metamod:Source Plugins

From AlliedModders Wiki
Jump to: navigation, search

SourceMod's "External Extensions" feature lets developers implement an extension simply by passing in an object instance, rather than implementing a full library file. This is typically done from Metamod:Source plugins which wish to:

  • Enable optional, third party extensibility via SourceMod's scripting layer.
  • For whatever reason, do not want to move their "standalone" plugin to a full SourceMod extension.
  • Or any of the above, such that new features are added in the presence of SourceMod, but the plugin does not require SourceMod to be present.

Overview

Attaching to SourceMod will be fairly easy for any developer who has authored a Metamod:Source plugin. However, unlike Metamod:Source, SourceMod has fairly stricter design standards and a much richer API set. It is unacceptable to not account for details such as unloading, and special attention must be taken care to free and manage SourceMod resources properly. Remember crashes or Handle leaks will affect all of SourceMod's loaded plugins/extensions, instead of just your plugin.

The general steps are:

  1. Implement the IExtensionInterface interface, from public/IExtensionSys.h.
  2. In ISmmPlugin::AllPluginsLoaded(), perform the attach if possible.
  3. Detect SourceMod loads in IMetamodListener::OnMetamodQuery().
  4. Store global pointers once bound, such as the self-referencing IExtension *.
  5. On ISmmPlugin::Unload(), detach from SourceMod.

Note that SourceMod requires each extension to have a unique name. This is not necessarily a file name, although usually it resembles one. This name is to used, in conjunction with include files, to make sure plugin requirements are met when they load.

Detailed Steps

The information below is for a more detailed overview. The sample SDK provides a full implementation that is recommended over simply copying and pasting the sample code provided here. The sample SDK is explained further on.

Implementing the Extension

Implementing the actual Extension itself means simply inheriting the IExtensionInterface class as noted earlier. For example:

class MyExtension : public IExtensionInterface
{
public:
    virtual bool OnExtensionLoad(IExtension *me,
        IShareSys *sys,
        char *error,
        size_t maxlength,
        bool late);
    virtual void OnExtensionUnload();
    virtual void OnExtensionsAllLoaded();
    virtual void OnExtensionPauseChange(bool pause);
    virtual bool QueryRunning(char *error, size_t maxlength);
    virtual bool IsMetamodExtension();
    virtual const char *GetExtensionName();
    virtual const char *GetExtensionURL();
    virtual const char *GetExtensionTag();
    virtual const char *GetExtensionAuthor();
    virtual const char *GetExtensionVerString();
    virtual const char *GetExtensionDescription();
    virtual const char *GetExtensionDateString();
};

Most functions are self explanatory. A few important notes:

  • OnExtensionLoad is used to provide essential interfaces: IShareSys (acquires/shares objects in the SourceMod heirarchy), and IExtension (self-referencing pointer used for identification and unloading).
  • OnExtensionLoad is also used to query for any extra interfaces that the extension requires. For example, if the Handle System is needed, it would be queried and its pointer cached. If it couldn't be found, then OnExtensionLoad would return false to deny loading.
  • OnExtensionUnload() is used to free resources. If a resource (interface) is being used from another extension, and that extension is unloaded, separate cases must be taken care of because of dynamic unloadability. These (unusual) cases are explained further in the Writing Extensions article.
  • IsMetamodExtension() must return false. No exceptions. Only pure-SourceMod extensions which optionally bind to Metamod:Source can return true here.

Examples:

IShareSys *sharesys = NULL;
IExtension *myself = NULL;
 
bool MyExtension::OnExtensionLoad(IExtension *me,
        IShareSys *sys,
        char *error,
        size_t maxlength,
        bool late)
{
    sharesys = sys;
    myself = me;
 
    /* Get anything important we rely on */
 
    return true;
}

Binding to SourceMod

Binding to SourceMod requires a few steps:

  1. Acquire the IExtensionManager interface.
  2. If it exists, bind the extension.
  3. If not, wait for SourceMod to load.

IExtensionSys.h provides a macro, SOURCEMOD_INTERFACE_EXTENSIONSYS, which can be used via ISmmAPI::MetamodQuery(). If you do not recall, that is the facility Metamod:Source uses for inter-plugin communication. An example implementation of binding to SourceMod might look like:

bool SM_LoadExtension(char *error, size_t maxlength)
{
    if ((smexts = (IExtensionManager *)g_SMAPI->MetaFactory(
            SOURCEMOD_INTERFACE_EXTENSIONS,
            NULL,
            NULL)) == NULL)
    {
        UTIL_Format(error, maxlength, SOURCEMOD_INTERFACE_EXTENSIONS " interface not found");
        return false;
    }
 
    char path[256];
    g_SMAPI->PathFormat(path, sizeof(path),
#if defined __linux__
        "addons/myplugin/bin/myplugin_mm_i486.so"
#else
        "addons\\myplugin\\bin\\myplugin_mm.dll"
#endif
        );
 
    if ((myself = smexts->LoadExternal(&g_SMExt,
            path,
            "myplugin_mm.ext",
            error,
            maxlength)) == NULL)
    {
        /* Error was supplied by call */
        return false;
    }
 
    return true;
}

Detecting SourceMod Loads

This is also fairly easy:

  1. If not already done: Inherit IMetamodListener, implement OnMetamodQuery().
  2. If not already done: Call ISmmAPI::AddListener for your IMetamodListener interface.
  3. Watch for occurrences of SOURCEMOD_NOTICE_EXTENSIONS.

Example of such an implementation:

bool StubPlugin::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late)
{
    PLUGIN_SAVEVARS();
 
    /* Blah blah blah */    
 
    ismm->AddListener(this, this);
 
    return true;
}
 
void StubPlugin::AllPluginsLoaded()
{
    /* BIND TO SOURCMEOD IF POSSIBLE */
}
 
void *StubPlugin::OnMetamodQuery(const char *iface, int *ret)
{
    if (strcmp(iface, SOURCEMOD_NOTICE_EXTENSIONS) == 0)
    {
        /* BIND TO SOURCEMOD */
    }
 
    if (ret != NULL)
    {
        *ret = IFACE_OK;
    }
 
    return NULL;
}

It is very important to return NULL even when this interface is detected. Otherwise, you may block other extensions from receiving the callback.


Detaching from SourceMod

Detaching is essential; if your plugin unloads without detaching, SourceMod will very quickly crash trying to deference your memory. Luckily, doing so is trivial. For example, in your ISmmPlugin::Unload() callback:

bool StubPlugin::Unload(char *error, size_t maxlen)
{
    smexts->UnloadExtension(myself);
}

Where smexts is the IExtensionManager pointer, and myself is the IExtension pointer.


SDK and Sample Implementation

The SourceMod SDK contains a sample implementation in public/mms_sample_ext. It is the Metamod:Source "stub plugin," modified to load as a SourceMod extension.

The following files have been added:

  • sm_sdk_config.h - Contains two function for mapping and unmapping SourceMod Core interfaces. Interfaces can be enabled or disabled by toggling comments next to preprocessor macros here.
  • sm_sdk_config.cpp - Implementation of sm_sdk_config.h. There is no need to edit this.
  • stub_util.h/cpp - Contains a platform-safe wrapper for text formatting.
  • sm_ext.cpp/sm_ext.h - Sample implementation IExtensionInterface.h and the binding/detaching code.

Additionally, stub_mm.cpp/stub_mm.h have been modified. IMetamodListener is inherited, and OnMetamodQuery is implemented as described earlier. Also, ISmmAPI::AllPluginsLoaded attempts a SourceMod binding.

Note: sm_sdk_config.h provides SM_AcquireInterfaces. It must be called to acquire the interfaces that are uncommented in the same file, unless you intend to query them manually.

Note: It is highly recommend that developers read the added files, and at the very least, include the sm_sdk_config files, which greatly simplify the acquisition of interfaces.

Note: The Makefiles are provided as a convenience/example, and for our own testing. As our build process requires special vcproj/sln changes (which are not yet documented), no Visual Studio project files are provided for mms_sample_ext. As most developers will already have an MM:S plugin implemented, this should largely be irrelevant. Otherwise, the project files from stub_mm can be used instead.


Renamed Variables/Macros

The extension SDK has a few renamed macros/variables from the default SDK. This is to prevent potential name clashes for Metamod:Source plugins, which have pre-existing code.

Changed global variables:

  • g_pForwards, forwards -> sm_forwards
  • g_pHandleSys, handlesys -> sm_handlesys
  • g_pSM -> sm_main
  • playerhelpers -> sm_players
  • dbi -> sm_dbi
  • gameconfs -> sm_gameconfs
  • memutils -> sm_memutils
  • gamehelpers -> sm_gamehelpers
  • timersys -> sm_timersys
  • threader -> sm_threader
  • libsys -> sm_libsys
  • plsys -> sm_plsys
  • textparsers -> sm_text
  • adminsys -> sm_adminsys
  • menus -> sm_menus

Changed macros:

  • SM_GET_IFACE -> SM_FIND_IFACE_OR_FAIL
  • SM_GET_LATE_IFACE -> SM_FIND_IFACE


Example of a Successful Load

meta load addons/stub_mm
Plugin "Stub Plugin" loaded with id 3.
sm exts list
[SM] Displaying 3 extensions:
[01] SDK Tools (1.0.0.1336): Source SDK Tools
[02] BinTools (1.0.0.1336): Low-level C/C++ Calling API
[03] Stub Plugin (1.0.0.0): Sample empty plugin
sm exts info 3
 File: myplugin_mm.ext
 Loaded: Yes (version 1.0.0.0)
 Name: Stub Plugin (Sample empty plugin)
 Author: AlliedModders LLC (http://www.sourcemm.net/)
 Binary info: API version 2 (compiled Nov 10 2007)
 Method: Loaded by Metamod:Source, attached to SourceMod
meta unload 3
Plugin 3 unloaded.
sm exts list
[SM] Displaying 2 extensions:
[01] SDK Tools (1.0.0.1336): Source SDK Tools
[02] BinTools (1.0.0.1336): Low-level C/C++ Calling API


Doing Something Useful

SourceMod has over a dozen interfaces for interacting with plugins and greatly simplifying the Half-Life 2 development process. It contains memory abstraction, fast data structures, an abstracted menu and voting system, the ability to create and invoke plugin callbacks ("forwards"), the ability to provide new native functions to plugins, text processing, game data abstraction, threading abstraction, signature scanning, timers, and an object-oriented SQL layer.

For more information, see the Writing Extensions article. Note that the article uses different global names. See the earlier section for finding which names changed.

Further Reading