Skip to content

About

Connection history and player analytics for CS2 / CounterStrikeSharp|SwiftlyS2|Metamod. Sessions open on join and close on leave, so a crashed server leaves a record instead of nothing — "who's online" is a plain SELECT. Stores map, geo, match stats and ping in MySQL; survives DB outages

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

ConnectHistory (CS2)

English | Русский

CI CSSharp CI SwiftlyS2 CI Metamod Release Downloads .NET 10 CounterStrikeSharp SwiftlyS2 Metamod:Source MySQL Platforms GeoLite2 License

Connection history and player analytics for CS2. Every session is written to MySQL with map, geo, match stats and connection quality — ready for a web panel.

Three implementations, three platforms, one database schema. The plugin exists separately for CounterStrikeSharp, SwiftlyS2 and native Metamod:Source. They share no code — they share the schema, and all three write the same tables. You can run different plugins on different servers and build a single report.

Platform Requirements Archive Installs into
CounterStrikeSharp CSSharp ≥ 1.0.369 (hence Metamod:Source) ConnectHistory_cssharp_<version>.zip addons/counterstrikesharp/plugins/ConnectHistory/
SwiftlyS2 SwiftlyS2 ≥ 1.4.9, Metamod not needed ConnectHistory_swiftly_<version>.zip addons/swiftlys2/plugins/ConnectHistory/
Metamod:Source Metamod:Source ≥ 2.0.0-git1460, nothing else ConnectHistory_metamod_{linux,windows}_<version>.zip addons/ConnectHistory/

The native target collects everything the C# targets do, with no other plugin required. Match results are read from the player controller, and reaching it needs exactly one game-version-dependent number — the offset of the entity system inside IGameResourceService. It lives in gamedata.json next to the other configs, is never overwritten by the plugin, and is edited without a rebuild when a CS2 update moves it (take the new value from CS2Fixes gamedata, key GameEntitySystem). Nothing else in the plugin depends on the game version. Details in Differences between targets.

Features

  • Sessions opened on join, closed on leave. A crashed server leaves a visible record instead of nothing.
  • "Who is online" is a plain SELECT — no RCON, no A2S queries.
  • Full session data: SteamID64, nickname, map, disconnect reason, country/city, IP (or a salted hash), kills/deaths/assists/damage/MVP/score, rounds, team, average/min/max ping.
  • Aggregates — total playtime, session count, first/last seen, nickname history.
  • Online snapshots for attendance charts.
  • Survives database outages — records are spooled to disk and re-sent automatically.
  • Never blocks the game thread — a single background writer, one bounded connection pool.
  • JSON config with a JSON Schema — your editor autocompletes fields and highlights typos.

Installation

Requires MySQL 5.7 / 8.0+ (or MariaDB 10.4+). Everything else depends on the platform — see the table above.

  1. Download the archive for your platform from the latest release and extract it into the root of your game server. The directory layout is already inside, together with the GeoLite2 databases.
CounterStrikeSharp
addons/counterstrikesharp/plugins/ConnectHistory/
├── ConnectHistory.dll
├── MySqlConnector.dll
├── MaxMind.GeoIP2.dll
├── MaxMind.Db.dll
├── GeoLite2-Country.mmdb
└── GeoLite2-City.mmdb
SwiftlyS2
addons/swiftlys2/plugins/ConnectHistory/
├── ConnectHistory.dll
├── MySqlConnector.dll
├── MaxMind.GeoIP2.dll
├── MaxMind.Db.dll
├── GeoLite2-Country.mmdb
└── GeoLite2-City.mmdb
Metamod:Source

One archive per OS — take ..._linux_... or ..._windows_....

addons/metamod/ConnectHistory.vdf
addons/configs/ConnectHistory/
├── Settings.json
├── Messages.json
├── Settings.schema.json
├── Messages.schema.json
└── README.txt
addons/ConnectHistory/
├── bin/linuxsteamrt64/ConnectHistory.so   (or bin/win64/ConnectHistory.dll)
├── GeoLite2-Country.mmdb
└── GeoLite2-City.mmdb

Configs live in the shared addons/configs/ tree, next to every other native CS2 plugin's — so there is no separate rule to remember for this one. The archive already contains them, so the plugin works even where the server directory is read-only.

The plugin looks in csgo/addons/configs/ConnectHistory/ regardless of where the binary itself sits, and says in the console which directory it read. Configs left at the pre-3.0.2 path addons/ConnectHistory/configs/ keep working and are preferred when present, so an upgrade never silently drops settings — the console says to move them.

When updating, do not unpack configs/ over an existing install — it would overwrite your Settings.json along with the database password.

  1. Start the server — the plugin creates any missing config files (parent directories included) and the database tables. If it cannot write them it says so in the console, naming the exact path, instead of running silently on defaults.
  2. Fill in the Database section of Settings.json and reload the configuration (css_ch_reload, sw_ch_reload or ch_reload, depending on the platform).

Settings.json holds the database password, so the plugin keeps it at mode 600. The archives carry only the template with an empty password — a release with a filled-in one fails the build.

Configuration

Platform Config directory
CounterStrikeSharp csgo/addons/counterstrikesharp/configs/plugins/ConnectHistory/
SwiftlyS2 csgo/addons/swiftlys2/configs/plugins/ConnectHistory/
Metamod:Source csgo/addons/configs/ConnectHistory/ (fixed — not next to the binary)

The file format is identical: Settings.json and Messages.json move between platforms unchanged.

File Purpose
Settings.json database, what to collect, storage, commands
Messages.json texts for player commands, per language
*.schema.json JSON Schema, regenerated on every load
README.txt short reference, regenerated on every load
gamedata.json native target only: the one game-version-dependent offset; never overwritten

Key options:

{
  "DisplayTimeZone": "UTC",   // what players see: "Europe/Moscow", "UTC" or "Local".
                              // The database always stores UTC — this never affects it.
  "Server": {
    "Id": 1,                  // must differ between servers. Was a top-level "ServerId"
                              // before 3.0.1; the old spelling is still read.
    "PublicAddress": ""       // "ip:port" or "host:port". Empty means auto-detect, but
                              // the server process does not know its own public address:
                              // ConVar ip returns the socket bind address, usually 0.0.0.0.
  },
  "Database": {
    "Host": "127.0.0.1",
    "SslMode": "Required",    // anything but localhost should use this
    "TablePrefix": "ch_"
  },
  "Collect": {
    "GeoIp": true,
    "PlayerIp": true,         // personal data — turn off if you don't need it
    "IpHash": true,
    "IpHashSalt": "",         // empty salt disables hashing (a saltless hash is reversible)
    "MatchStats": true,
    "Ping": true,
    "CountSpectatorTime": true,  // false — do not count spectator and
                                // unassigned time as playtime
    "OnlineSnapshots": true
  }
}

Commands

Action CounterStrikeSharp SwiftlyS2 Metamod
connectivity, queue, last error css_ch_status (@css/root) sw_ch_status ch_status (server console)
reload configuration css_ch_reload (@css/root) sw_ch_reload ch_reload (server console)
own playtime css_playtime (player) sw_playtime (player) !playtime / ch_playtime (player), ch_playtime <steamid64> (console)
recent sessions css_lastseen (player) sw_lastseen (player) !lastseen / ch_lastseen (player), ch_lastseen <steamid64> (console)

In the native target the player commands answer in chat, like everywhere else — both !playtime and /playtime work, and only your own commands are swallowed, so other chat plugins keep seeing everything else. From the server console the same commands take a SteamID64 and answer in the console, which is how you ask about a player who is not on the server right now.

Database

Six tables, all created by the plugin: ch_sessions, ch_players, ch_nicknames, ch_servers, ch_online_snapshots, ch_schema_version.

Full schema and a query cookbook: docs/DATABASE.md.

A ready-made section for the Flute CMS panel: ConnectHistory-Flute — online list, statistics, filters and charts on top of these tables. Installation and wiring are documented in that repository.

Give the plugin its own MySQL user — it never needs DROP or DELETE:

GRANT SELECT, INSERT, UPDATE, CREATE, INDEX, ALTER ON connect_history.* TO 'ch_plugin'@'%';

Build

Each target builds on its own.

export PATH="$HOME/.dotnet:$PATH"

cd cssharp && ./build.sh      # tests + archive in bin/Release/net10.0/
cd swiftly && ./build.sh      # same

MySQL integration tests are skipped unless CH_TEST_MYSQL is set:

docker run --rm -d -p 3399:3306 -e MYSQL_ROOT_PASSWORD=test -e MYSQL_DATABASE=ch mysql:8
export CH_TEST_MYSQL="server=127.0.0.1;port=3399;user=root;password=test;database=ch"
dotnet test cssharp/ConnectHistory.sln

The native target is split in two. Its core knows nothing about hl2sdk or MySQL and is verified on any machine in a second:

cd metamod && make -f Makefile.tests -j8 && ./build-tests/ch_tests

Building the plugin itself needs hl2sdk, Metamod:Source and a static MariaDB client — that lives in CI. Details: metamod/README.md.

Credits

GeoLite2 data by MaxMind, distributed under the GeoLite2 End User License Agreement.

License

GPL-3.0-or-later.

About

Connection history and player analytics for CS2 / CounterStrikeSharp|SwiftlyS2|Metamod. Sessions open on join and close on leave, so a crashed server leaves a record instead of nothing — "who's online" is a plain SELECT. Stores map, geo, match stats and ping in MySQL; survives DB outages

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages