Auto Tournament
SDK

Build your first plugin

Build the example plugin, load it on a server and hot reload it.

In development

The plugin API is at version 1.6. New minor versions only add functions, so plugins built for an older 1.x keep working. Details on this page can still change before the first release.

A Ready Up plugin is a Linux shared library (.so) in game/csgo/readyup/plugins/. It talks to the server only through a table of C functions that the core hands it. It never touches the engine itself. When CS2 updates, the core gets fixed and your plugin keeps working without a rebuild.

The match flow, practice, skins, the fleet link and the other in-house plugins are all built this way, and use nothing but the public API. This page walks through hello, the example plugin in the repo. It is plain C and about 200 lines.

Repo layout

PathWhat it is
core/The core: loader, engine surface, hooks, events, plugin host, status endpoint.
core/include/readyup/plugin_api.hThe plugin API. A plugin includes this file.
core/include/readyup/*_iface.hInterfaces that plugins publish to each other and to the core. See API reference.
plugins/match/The match plugin, match.so.
plugins/essentials/, plugins/practice/, plugins/whitelist/, plugins/midas/The essentials, practice, whitelist and Midas plugins.
plugins/skins/The skins plugin, skins.so.
plugins/fleet/The fleet link, fleet.so.
plugins/hello/The example plugin.
libs/readyup/Shared code that does not touch the engine (JSON, HTTP, the JSON file store, the status snapshot), as static libraries.
gamedata/engine-surface.json, and engine-surface.skins.json for skins.
tools/readyup_sigcheck, readyup_hookcheck and the plugin host test.
scripts/livetest/Bot-only live tests against a real test server.

What you need

  • Linux, CMake and GCC or Clang.
  • A CS2 test server with Ready Up installed, where you can use the console.
  • sv_hibernate_when_empty 0 on that server. Plugins load and reload in server frames, and a hibernating server runs none.

Build hello

git clone https://github.com/Auto-Tournament/ready-up.git
cd ready-up
./build.sh

./build.sh runs CMake and builds everything into build/: the core (build/libserver.so) and the plugins (build/plugins/*.so). The fleet plugin needs a libcurl with WebSocket support. Without one, the build skips fleet.so with a warning.

To build only one plugin:

cmake --build build --target readyup_plugin_hello

To build in the same Debian image the dev server uses:

BUILD_TARGET=readyup_plugin_hello scripts/docker-build.sh   # -> build-docker/plugins/hello.so

Load it

Copy the file

Copy hello.so to game/csgo/readyup/plugins/hello.so on the server. The file name must match the name the plugin reports, here hello.

Load it

The core loads every *.so in that folder on the first server frame, in name order. On a running server, load it by hand:

ru plugin load hello

Check it

ru plugin list

It shows each plugin with its version, the API version it was built for, and how many commands, ticks and subscriptions it has.

Type .hello in chat, or hello_status in the console. Every 10 seconds the console shows a tick heartbeat line from the plugin.

What hello shows

FeatureAPI
.hello in chat, hello_status in the consoleregister_chat_command, register_console_command
A heartbeat every 10 secondson_tick
Map, round and player events in the logsubscribe
A load counter that survives a reloadstash_put, stash_get
A greeting setting from [hello] in readyup.cfg or cfg/ReadyUp/hello.cfgconfig_get
Kills with the weaponsubscribe_game_event("player_death")
A count of server log linessubscribe_log_line
ru hello in the console, .ru hello in chatregister_ru_subcommand
.hellohide, which does not show in chatregister_chat_command_ex with RU_CMD_HIDE
Watching sv_cheats while the engine still runs itregister_console_command_ex with RU_CMD_OBSERVE
A frame counter that also runs when the server does not simulateon_frame

hello asks for API 1.0 and checks each newer function with RU_API_HAS, so it also loads on an older core.

Hot reload

Reload swaps a plugin for the file on disk without a server restart:

ru plugin reload hello

Admins can also type .ru plugin reload hello in chat. The command only queues the reload. It runs at the start of the next server frame, when no plugin code is running. The console then shows plugin: reloaded hello, or plugin: reload hello failed: ....

Try it: change HELLO_VERSION in hello.c, build, copy, reload. .hello answers with the new version.

When you copy a new file over a loaded one, do not write into the old file. Copy it next to it and rename it into place (mv). The server still has the old file mapped.

A reloaded plugin starts with no state, unless it kept some with stash_put. If the new file fails to load, the plugin stays unloaded. Put the old file back and reload again. See What survives a reload.

dev-deploy.sh

scripts/dev-deploy.sh --plugin <name> does all of this in one step. It builds only that plugin in Docker, copies it to the server, renames it into place, keeps the old file as <name>.so.prev, types ru plugin reload <name> into the server's tmux console, and waits up to 15 seconds for the result.

scripts/dev-deploy.sh --plugin hello

It is written for the Ready Up test box. Point it at your own server with these options (or the environment variables in brackets):

OptionDefaultWhat it is
--target DIR (RU_TARGET)/home/cs2servermanager/readyup-testServer folder, with game/ in it.
--ssh USER@HOST (RU_SSH)cs2servermanager@localhostThe user that owns the server files.
--session NAME (RU_SESSION)ru-testThe tmux session that runs the server.
--no-buildDeploy the last build.

The script reads the result from console.log in the server folder. Changes to the core still need a full deploy and a restart: scripts/dev-deploy.sh --restart. For skins, it also copies engine-surface.skins.json. The core only reads that file at start, so add --restart the first time.

Turning plugins off

  • ru plugin unload <name> unloads a plugin until the next restart.
  • Rename the file, for example to hello.so.off, to stop it from loading at start.
  • READYUP_PLUGINS=0 loads no plugins at all.

Tests

Offline tests run without a server:

(cd build && ctest --output-on-failure)

They include the plugin host test (it loads hello.so, sends it commands and events, then reloads a second build and checks that the old code is gone), a test that runs match.so in the real plugin host, the status endpoint, and the fleet link against a mock platform.

scripts/livetest/run.sh plays a short bot-only match on a real test server and checks each step in the console log: selftest, match load, warmup, knife round, side pick, live, halftime, map end, postgame, idle. --scrim does the same for a bots-only scrim, using dev_bots_scrim. No one has to join. See scripts/livetest/README.md in the repo.

Your own plugin

Inside the repo, add a folder under plugins/ and register it in CMake:

plugins/myplugin/CMakeLists.txt
readyup_add_plugin(myplugin myplugin.c)

Then add add_subdirectory(plugins/myplugin) to the top-level CMakeLists.txt. readyup_add_plugin sets the flags a plugin needs: hidden symbols, --no-undefined, a version script that exports only the three entry points, and -fno-gnu-unique for C++. See Threading and ABI.

A plugin exports three functions:

#include "readyup/plugin_api.h"

READYUP_PLUGIN_EXPORT const ru_plugin_info* readyup_plugin_info(void);
READYUP_PLUGIN_EXPORT int readyup_plugin_load(const ru_api* api, uint32_t core_api_version);
READYUP_PLUGIN_EXPORT void readyup_plugin_unload(void);

Start from plugins/hello/hello.c. The API reference lists every function.

A plugin built outside the repo only needs plugin_api.h (and any *_iface.h it uses). Build it with the same flags.

On this page