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/khooksubmodule, pinned to the commit Metamod is built with; - the CMake (and AMBuild) build configuration, including the
add_source2mod_pluginhelper; - s2sdk — AlliedModders' Source 2 SDK, formerly hl2sdk's
cs2branch — as thevendor/s2sdksubmodule, unless you point it at your own checkout; - the game protobufs, generated from s2sdk's own
.protofiles into the SDK'sProtobufslibrary.
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 --recursivegit clone --recursive https://github.com/Source2Mod/source2mod-sdk.gitUse 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 initinit 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 updateupdate 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 envAn 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:
| Variable | Description |
|---|---|
S2SDK | Path 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" # optionalCSGO_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:
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:
#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 ReleaseThe 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/entity2sources, 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,protobufandsteam_apifrom s2sdk. SOURCE2MOD_KHOOK_COMMIT, the KHook commit baked into your plugin for the load-time check.- The
.smxoutput name.
Platform notes
Linux
- The SDK target passes
_GLIBCXX_USE_CXX11_ABI=0(the oldstd::stringABI) to everything that links it. Every static library you link into the plugin must be built with the same setting, or linking fails onstd::string/std::listsymbols. - 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 ownCMakeLists.txtbeforeadd_source2mod_plugin, as in the example above; otherwise the linker reports aRuntimeLibrarymismatch (LNK2038). - A Linux
.smxand a Windows.smxare different binaries: build each on its own platform.
Compatibility
What a core update means for a plugin — the stable and raw tiers, interface revisions and the compat adapters, the plugin API version, the KHook check, and what s2m list shows.
SDK Dependencies
Where the SDK gets s2sdk, how tools/deps.py sets it up, keeps KHook on the core's commit, and how the build finds it.