Development

SDK & Building

Get the Source2Mod SDK, set up its dependencies and build a plugin into a .smx with CMake.

The Source2Mod SDK is what you compile plugins against. It contains:

  • Source2Mod's public headers (public/source2mod/ISource2Mod*.h) and the generated schema classes;
  • KHook headers — Metamod:Source's own detour library — as the vendor/khook submodule, pinned to the commit Metamod is built with;
  • the CMake (and AMBuild) build configuration, including the add_source2mod_plugin helper;
  • s2sdk — AlliedModders' Source 2 SDK, formerly hl2sdk's cs2 branch — as the vendor/s2sdk submodule, unless you point it at your own checkout;
  • the game protobufs, generated from s2sdk's own .proto files into the SDK's Protobufs library.

Requirements: CMake 3.18+, a C++20 compiler (GCC or Clang on Linux, MSVC on Windows), Python 3 and git.

Getting the SDK

Add it to your plugin as a git submodule, or clone it next to your projects:

git submodule add https://github.com/Source2Mod/source2mod-sdk.git vendor/source2mod-sdk
git submodule update --init --recursive
git clone --recursive https://github.com/Source2Mod/source2mod-sdk.git

Use git, not a zip download. The build reads the KHook commit from vendor/khook with git rev-parse; without git metadata the plugin is built with the commit unknown and the core cannot check that your plugin and the core use the same KHook.

Dependencies

s2sdk (alliedmodders/s2sdk, cs2 branch) comes with the SDK: it is the vendor/s2sdk submodule, pinned to the commit Source2Mod headers are built against, and the --recursive above already fetched it. The game protobufs are s2sdk's own .proto files, so there is nothing else to fetch: a fresh clone builds as it is.

tools/deps.py is there for when you would rather use an s2sdk checkout of your own, and to keep everything current:

python tools/deps.py init

init asks whether to use a checkout you already have — found through an environment variable (below), or a path you type — or the vendor/s2sdk submodule. The answer is saved in deps.json next to the SDK (ignored by git), and from then on one command brings everything up to date:

python tools/deps.py update

update pulls your own checkouts (a checkout with local changes is left alone), puts vendor/s2sdk on the pinned commit, moves vendor/khook to the KHook commit that alliedmodders/metamod-source master pins — Source2Mod core is built against that one, and a plugin built against any other KHook refuses to load — and moves vendor/hl2sdk-manifests to its latest. python tools/deps.py status shows what is configured and which commits are checked out. The full walkthrough, flags included, is on the SDK dependencies page.

The questions can be answered up front, which is what a CI script wants:

python tools/deps.py init --s2sdk submodule
python tools/deps.py init --s2sdk env

An explicit choice wins over the environment: deps.py writes deps.cmake, which CMake includes before it looks at any variable. Without deps.json, s2sdk comes from the first variable below that is set, else vendor/s2sdk.

By hand

If you would rather not use the script, set the variable before running CMake:

VariableDescription
S2SDKPath to your own s2sdk checkout. Optional — without it the vendor/s2sdk submodule is used. S2SDK_CS2, S2SDKCS2, HL2SDK_CS2 and the old HL2SDKCS2 work too; the first one set, in the order S2SDK_CS2, S2SDKCS2, S2SDK, HL2SDK_CS2, HL2SDKCS2, wins.
# Linux
export S2SDK=/path/to/s2sdk          # optional
# Windows (PowerShell)
$env:S2SDK = "C:\path\to\s2sdk"       # optional

CSGO_PROTO is gone: the protobufs no longer come from a separate SteamTracking/Protobufs checkout but from s2sdk itself, so you can drop it from your environment.

Coming from hl2sdk? Its cs2 branch is no longer maintained; s2sdk continues it with the same history. Clone https://github.com/alliedmodders/s2sdk.git (branch cs2) and point S2SDK at it, or simply unset HL2SDKCS2 and let the submodule be used.

KHook needs nothing from you: it is the vendor/khook submodule. MMSOURCE_DEV is only read when building the s2m core itself.

Project setup

A typical plugin project looks like this:

my-plugin/
├── CMakeLists.txt
├── src/
│   └── plugin.cpp
└── vendor/
    └── source2mod-sdk/

CMake configuration

Add the SDK as a subdirectory and create the plugin with add_source2mod_plugin:

CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(my-plugin CXX)

set(CMAKE_CXX_STANDARD 20)

# Windows: the SDK is built with the static MSVC runtime, the plugin has to match.
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded")

add_subdirectory(vendor/source2mod-sdk)

add_source2mod_plugin(my_plugin
    src/plugin.cpp
)

add_source2mod_plugin(name sources...) creates a shared library, links it against the source2mod-sdk target (include paths, definitions, s2sdk and protobuf libraries come with it) and names the output <name>.smx with no lib prefix. It is an ordinary CMake target afterwards, so target_include_directories, target_link_libraries and set_target_properties work as usual.

To have the build drop the plugin straight into an addons/ tree, set its output directory — this is what the official sample does:

set_target_properties(my_plugin PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/addons/source2mod/plugins
    # The .dll on Windows is a RUNTIME artifact.
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/addons/source2mod/plugins
)

The sample plugin (samples/cs2_sample in the core repository) finds the SDK through a SOURCE2MOD_SDK environment variable instead of a submodule, so one SDK checkout serves several plugins. Both approaches work; add_subdirectory just needs a binary directory as second argument when the SDK lives outside your source tree: add_subdirectory(${SOURCE2MOD_SDK} ${CMAKE_BINARY_DIR}/source2mod-sdk).

Example plugin

The smallest plugin that loads:

src/plugin.cpp
#include "source2mod/ISource2ModPlugin.h"
#include "source2mod/ISource2ModApi.h"

SOURCE2MOD_GLOBALVARS();

class Plugin final : public ISource2ModPlugin
{
public:
    bool Load(PluginId id, ISource2ModAPI* api, char* error, size_t maxlen, bool late) override
    {
        SOURCE2MOD_SAVEVARS();
        SOURCE2MOD_LOG(this, "Hello World! Plugin loaded.\n");
        return true;
    }

    bool Unload(char* error, size_t maxlen) override
    {
        SOURCE2MOD_LOG(this, "Plugin unloaded.\n");
        return true;
    }

    const char* GetName() override { return "My Plugin"; }
    const char* GetVersion() override { return "1.0.0"; }
    const char* GetAuthor() override { return "You"; }
    const char* GetDescription() override { return "My first plugin"; }
};

Plugin g_Plugin;
SOURCE2MOD_EXPOSE(my_plugin, g_Plugin);

Your First Plugin walks through a fuller version with a listener, a command and a game event, and explains every macro used here.

Building the plugin

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release

The result is my_plugin.smx in the build directory (or in the output directory you set). Copy it to the server's

game/csgo/addons/source2mod/plugins/

and restart the server, or run s2m load my_plugin in its console.

What the SDK configures

  • C++20, platform compiler flags and the definitions s2sdk expects.
  • The SDK's own helper code (schema helpers, hook wrappers) and s2sdk's tier1 / entity2 sources, compiled into a static library that becomes part of your plugin.
  • Include directories for Source2Mod, s2sdk, KHook and the generated protobufs.
  • Linking against tier0, interfaces, mathlib, protobuf and steam_api from s2sdk.
  • SOURCE2MOD_KHOOK_COMMIT, the KHook commit baked into your plugin for the load-time check.
  • The .smx output name.

Platform notes

Linux

  • The SDK target passes _GLIBCXX_USE_CXX11_ABI=0 (the old std::string ABI) to everything that links it. Every static library you link into the plugin must be built with the same setting, or linking fails on std::string / std::list symbols.
  • Code is built with -fPIC, and protobuf's symbols are kept out of the plugin's exports (--exclude-libs=libprotobuf.a).

Windows

  • MSVC is required; configuring with MinGW stops with MSVC restricted.
  • The SDK library is built with the static MSVC runtime (/MT, CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded"). CMake applies that variable per directory, so set it in your own CMakeLists.txt before add_source2mod_plugin, as in the example above; otherwise the linker reports a RuntimeLibrary mismatch (LNK2038).
  • A Linux .smx and a Windows .smx are different binaries: build each on its own platform.

On this page