CS2 GSI for Macro Deck is a Macro Deck 2 plugin that receives Counter-Strike 2 Game State Integration payloads locally and publishes them as Macro Deck variables.
It listens only on http://127.0.0.1:3333/. CS2 sends data to the plugin, and Macro Deck buttons can show values such as map, score, HP, armor, money, weapon, ammo, bomb state, all players, grenades, and observer-only data when CS2 provides it.
Official Valve GSI documentation:
https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Game_State_Integration
Current screenshots to add before release:
docs/images/macrodeck-dashboard-live.png- Macro Deck dashboard during a live round.docs/images/macrodeck-dashboard-bomb.png- Bomb planted / timer test.docs/images/macrodeck-dashboard-observer.png- Observer/spectator test withallplayersdata.
This project is in early development. It builds, loads locally in Macro Deck, and has been tested with real CS2 payloads, but it still needs final release cleanup before Macro Deck Extension Store submission.
Known pre-release work:
- Add final screenshots/GIFs.
- Do final clean build and release tag.
- Submit to the Macro Deck Extension Store.
- Windows
- Macro Deck 2
- Counter-Strike 2
- .NET SDK 9 or newer for development builds
- Macro Deck installed at
C:\Program Files\Macro Deck\Macro Deck 2.dllfor local compilation
Use this if you only want it to work.
- Install or copy the plugin into:
%AppData%\Macro Deck\plugins\LeoM.Cs2Gsi
- Create this file in the CS2 config folder:
Counter-Strike Global Offensive\game\csgo\cfg\gamestate_integration_cs2md.cfg
- Paste this config:
"CS2 Macro Deck GSI"
{
"uri" "http://127.0.0.1:3333/"
"timeout" "5.0"
"buffer" "0.1"
"throttle" "0.5"
"heartbeat" "10.0"
"auth"
{
"token" "cs2md_token_segreto"
}
"output"
{
"precision_time" "3"
"precision_position" "1"
"precision_vector" "3"
}
"data"
{
"provider" "1"
"map" "1"
"map_round_wins" "1"
"round" "1"
"player_id" "1"
"player_state" "1"
"player_weapons" "1"
"player_match_stats" "1"
"player_position" "1"
"bomb" "1"
"phase_countdowns" "1"
"allplayers_id" "1"
"allplayers_state" "1"
"allplayers_match_stats" "1"
"allplayers_weapons" "1"
"allplayers_position" "1"
"allgrenades" "1"
}
}
- Restart CS2.
- Restart Macro Deck.
- Create a Macro Deck button and use a label like:
{cs2md_map_name}
{cs2md_round_phase}
HP {cs2md_player_hp}
{cs2md_weapon_name}
{cs2md_weapon_ammo_clip}/{cs2md_weapon_ammo_clip_max}
Use this when you want to verify the connection and troubleshoot.
- Start Macro Deck.
- Open this local debug URL:
http://127.0.0.1:3333/state
- Start CS2 and enter a match or training session.
- Refresh
/state. - Check these values:
HasPayload = true
Provider.AppId = 730
Map.Name = de_inferno / de_mirage / ...
Player.Name = your CS2 name
Player.ActiveWeapon = weapon_...
- To inspect the exact raw payload received from CS2:
http://127.0.0.1:3333/raw
Useful Macro Deck status values:
| Value | Meaning |
|---|---|
starting |
Variables were initialized and the listener is starting. |
waiting_for_cs2 |
The listener is running, but no real CS2 payload has been received yet. |
connected |
A real CS2 payload has been received and variables were updated. |
token_invalid |
CS2 sent a payload with a token that does not match the plugin token. |
port_in_use |
The plugin could not bind to port 3333; another process is using it. |
listener_offline |
The plugin is polling /state, but no listener is reachable. |
restarting |
The reset action is restarting the listener. |
error |
An unexpected error occurred while publishing state. |
Open the plugin settings from Macro Deck to choose which variables are exposed.
The settings window includes:
- Listener token
- Listener port
- Variable categories and groups
- A button to copy a CS2 GSI config using the current token and port
Connection / Status variables are always enabled:
cs2md.connected
cs2md.status
Default enabled groups:
Info: provider, map, round, connection/statusPlayer: identity, status, health, economy, match stats, active weaponBomb: basic bomb state and timer
Default disabled groups:
Info: phase countdowns, map round winsPlayer: position, full weapon inventory slotsBomb: position and carrierOther Players: all observer player slots and their weaponsGrenades: grenade slotsRaw / Debug: raw JSON variables
When a variable is disabled, the plugin removes it from Macro Deck's variable list instead of only stopping updates. Existing button placeholders that reference disabled variables will not receive values until those variables are enabled again.
Changing the listener token or port restarts the local listener after saving settings. If you change either value, update the CS2 gamestate_integration_cs2md.cfg file and restart CS2.
Use this for observer dashboards, full match panels, or debugging all payload blocks.
Valve separates GSI blocks into normal player data and observer/spectator data. In Valve's official list, these blocks are under "must be spectating or observing":
allgrenades
allplayers_id
allplayers_match_stats
allplayers_position
allplayers_state
allplayers_weapons
bomb
phase_countdowns
player_position
This plugin exposes variables for those blocks, but CS2 may leave them empty during normal gameplay. In real local tests, bomb.state and bomb.timer arrived during planted/defused events, while bomb.position, bomb.carrier, and bomb.site stayed empty. That matches Valve's observer/spectator limitation.
If you discover a CS2 mode or config where these fields behave differently, please open a PR with the raw /raw payload and a short note explaining the mode, map, and camera state.
From the repository root:
dotnet build Cs2MacroDeck.slnxExpected current result:
- Errors:
0 - Warnings:
0
The plugin project enables both Windows Forms and WPF so the local build aligns with the installed Macro Deck desktop assemblies.
Build the solution, then copy the plugin build output to Macro Deck's plugin folder:
%AppData%\Macro Deck\plugins\LeoM.Cs2Gsi
The local plugin folder must contain at least:
Cs2MacroDeck.Plugin.dll
Cs2MacroDeck.Plugin.deps.json
ExtensionManifest.json
Plugin.png
Current development builds compile the shared GSI code directly into Cs2MacroDeck.Plugin.dll, so the plugin output does not require Cs2Gsi.Core.dll.
Restart Macro Deck after copying the files.
Useful log lines:
CS2 Macro Deck plugin enabled.
CS2 GSI server listening on http://127.0.0.1:3333/
Macro Deck label placeholders use underscores instead of dots.
Example:
Plugin variable: cs2md.player.hp
Button text: {cs2md_player_hp}
Slot numbers are part of the variable name.
Example:
Plugin variable: cs2md.ap01.name
Button text: {cs2md_ap01_name}
| Variable | Placeholder | Type | Meaning |
|---|---|---|---|
cs2md.connected |
{cs2md_connected} |
Bool | true after a real CS2 payload has been received. |
cs2md.status |
{cs2md_status} |
String | Plugin/listener status. |
| Variable | Placeholder | Type |
|---|---|---|
cs2md.provider.name |
{cs2md_provider_name} |
String |
cs2md.provider.appid |
{cs2md_provider_appid} |
Integer |
cs2md.provider.version |
{cs2md_provider_version} |
Integer |
cs2md.provider.steamid |
{cs2md_provider_steamid} |
String |
cs2md.provider.timestamp |
{cs2md_provider_timestamp} |
Integer |
| Variable | Placeholder | Type |
|---|---|---|
cs2md.map.name |
{cs2md_map_name} |
String |
cs2md.map.mode |
{cs2md_map_mode} |
String |
cs2md.map.phase |
{cs2md_map_phase} |
String |
cs2md.map.round |
{cs2md_map_round} |
Integer |
cs2md.map.num_matches_to_win_series |
{cs2md_map_num_matches_to_win_series} |
Integer |
cs2md.map.ct.consecutive_round_losses |
{cs2md_map_ct_consecutive_round_losses} |
Integer |
cs2md.map.ct.timeouts_remaining |
{cs2md_map_ct_timeouts_remaining} |
Integer |
cs2md.map.ct.matches_won_this_series |
{cs2md_map_ct_matches_won_this_series} |
Integer |
cs2md.map.t.consecutive_round_losses |
{cs2md_map_t_consecutive_round_losses} |
Integer |
cs2md.map.t.timeouts_remaining |
{cs2md_map_t_timeouts_remaining} |
Integer |
cs2md.map.t.matches_won_this_series |
{cs2md_map_t_matches_won_this_series} |
Integer |
cs2md.round.phase |
{cs2md_round_phase} |
String |
cs2md.round.win_team |
{cs2md_round_win_team} |
String |
cs2md.round.wins_ct |
{cs2md_round_wins_ct} |
Integer |
cs2md.round.wins_t |
{cs2md_round_wins_t} |
Integer |
| Variable | Placeholder | Type |
|---|---|---|
cs2md.rw.count |
{cs2md_rw_count} |
Integer |
cs2md.rw.history |
{cs2md_rw_history} |
String |
cs2md.rw.raw_json |
{cs2md_rw_raw_json} |
String |
cs2md.rw.01 to .30 |
{cs2md_rw_01} to {cs2md_rw_30} |
String |
| Variable | Placeholder | Type |
|---|---|---|
cs2md.phase_countdowns.phase |
{cs2md_phase_countdowns_phase} |
String |
cs2md.phase_countdowns.ends_in |
{cs2md_phase_countdowns_ends_in} |
String |
| Variable | Placeholder | Type |
|---|---|---|
cs2md.player.steamid |
{cs2md_player_steamid} |
String |
cs2md.player.name |
{cs2md_player_name} |
String |
cs2md.player.observer_slot |
{cs2md_player_observer_slot} |
Integer |
cs2md.player.activity |
{cs2md_player_activity} |
String |
cs2md.player.team |
{cs2md_player_team} |
String |
cs2md.player.hp |
{cs2md_player_hp} |
Integer |
cs2md.player.armor |
{cs2md_player_armor} |
Integer |
cs2md.player.helmet |
{cs2md_player_helmet} |
Bool |
cs2md.player.defusekit |
{cs2md_player_defusekit} |
Bool |
cs2md.player.flashed |
{cs2md_player_flashed} |
Integer |
cs2md.player.smoked |
{cs2md_player_smoked} |
Integer |
cs2md.player.burning |
{cs2md_player_burning} |
Integer |
cs2md.player.alive |
{cs2md_player_alive} |
Bool |
cs2md.player.money |
{cs2md_player_money} |
Integer |
cs2md.player.kills_round |
{cs2md_player_kills_round} |
Integer |
cs2md.player.headshot_kills_round |
{cs2md_player_headshot_kills_round} |
Integer |
cs2md.player.kills_total |
{cs2md_player_kills_total} |
Integer |
cs2md.player.assists |
{cs2md_player_assists} |
Integer |
cs2md.player.deaths |
{cs2md_player_deaths} |
Integer |
cs2md.player.mvps |
{cs2md_player_mvps} |
Integer |
cs2md.player.score |
{cs2md_player_score} |
Integer |
cs2md.player.equip_value |
{cs2md_player_equip_value} |
Integer |
cs2md.player.position |
{cs2md_player_position} |
String |
cs2md.player.forward |
{cs2md_player_forward} |
String |
| Variable | Placeholder | Type |
|---|---|---|
cs2md.weapon.name |
{cs2md_weapon_name} |
String |
cs2md.weapon.type |
{cs2md_weapon_type} |
String |
cs2md.weapon.paintkit |
{cs2md_weapon_paintkit} |
String |
cs2md.weapon.state |
{cs2md_weapon_state} |
String |
cs2md.weapon.ammo_clip |
{cs2md_weapon_ammo_clip} |
Integer |
cs2md.weapon.ammo_clip_max |
{cs2md_weapon_ammo_clip_max} |
Integer |
cs2md.weapon.ammo_reserve |
{cs2md_weapon_ammo_reserve} |
Integer |
The plugin keeps the weapon visible while weapon.state is active or reloading.
Current-player inventory is exposed as cs2md.pw01 through cs2md.pw08.
| Pattern | Placeholder Example | Type |
|---|---|---|
cs2md.pw.count |
{cs2md_pw_count} |
Integer |
cs2md.pw.raw_json |
{cs2md_pw_raw_json} |
String |
cs2md.pw01.slot |
{cs2md_pw01_slot} |
String |
cs2md.pw01.name |
{cs2md_pw01_name} |
String |
cs2md.pw01.type |
{cs2md_pw01_type} |
String |
cs2md.pw01.paint |
{cs2md_pw01_paint} |
String |
cs2md.pw01.state |
{cs2md_pw01_state} |
String |
cs2md.pw01.ammo |
{cs2md_pw01_ammo} |
Integer |
cs2md.pw01.ammo_max |
{cs2md_pw01_ammo_max} |
Integer |
cs2md.pw01.reserve |
{cs2md_pw01_reserve} |
Integer |
Replace 01 with 02 through 08 for the other slots.
| Variable | Placeholder | Type |
|---|---|---|
cs2md.bomb.state |
{cs2md_bomb_state} |
String |
cs2md.bomb.timer |
{cs2md_bomb_timer} |
String |
cs2md.bomb.position |
{cs2md_bomb_position} |
String |
cs2md.bomb.carrier |
{cs2md_bomb_carrier} |
String |
cs2md.bomb.site |
{cs2md_bomb_site} |
String |
cs2md.bomb.site is currently a compatibility alias for bomb.position; automatic A/B site detection is not implemented yet.
Observer/spectator payloads are exposed as cs2md.ap01 through cs2md.ap10. Short names are used because Macro Deck truncates long variable names in the picker.
| Pattern | Placeholder Example | Type |
|---|---|---|
cs2md.ap.count |
{cs2md_ap_count} |
Integer |
cs2md.ap.raw_json |
{cs2md_ap_raw_json} |
String |
cs2md.ap01.steamid |
{cs2md_ap01_steamid} |
String |
cs2md.ap01.name |
{cs2md_ap01_name} |
String |
cs2md.ap01.slot |
{cs2md_ap01_slot} |
Integer |
cs2md.ap01.activity |
{cs2md_ap01_activity} |
String |
cs2md.ap01.team |
{cs2md_ap01_team} |
String |
cs2md.ap01.hp |
{cs2md_ap01_hp} |
Integer |
cs2md.ap01.armor |
{cs2md_ap01_armor} |
Integer |
cs2md.ap01.helmet |
{cs2md_ap01_helmet} |
Bool |
cs2md.ap01.defusekit |
{cs2md_ap01_defusekit} |
Bool |
cs2md.ap01.flashed |
{cs2md_ap01_flashed} |
Integer |
cs2md.ap01.smoked |
{cs2md_ap01_smoked} |
Integer |
cs2md.ap01.burning |
{cs2md_ap01_burning} |
Integer |
cs2md.ap01.alive |
{cs2md_ap01_alive} |
Bool |
cs2md.ap01.money |
{cs2md_ap01_money} |
Integer |
cs2md.ap01.kr |
{cs2md_ap01_kr} |
Integer |
cs2md.ap01.hsr |
{cs2md_ap01_hsr} |
Integer |
cs2md.ap01.kt |
{cs2md_ap01_kt} |
Integer |
cs2md.ap01.assists |
{cs2md_ap01_assists} |
Integer |
cs2md.ap01.deaths |
{cs2md_ap01_deaths} |
Integer |
cs2md.ap01.mvps |
{cs2md_ap01_mvps} |
Integer |
cs2md.ap01.score |
{cs2md_ap01_score} |
Integer |
cs2md.ap01.equip |
{cs2md_ap01_equip} |
Integer |
cs2md.ap01.pos |
{cs2md_ap01_pos} |
String |
cs2md.ap01.forward |
{cs2md_ap01_forward} |
String |
cs2md.ap01.aw.name |
{cs2md_ap01_aw_name} |
String |
cs2md.ap01.aw.type |
{cs2md_ap01_aw_type} |
String |
cs2md.ap01.aw.paint |
{cs2md_ap01_aw_paint} |
String |
cs2md.ap01.aw.state |
{cs2md_ap01_aw_state} |
String |
cs2md.ap01.aw.ammo |
{cs2md_ap01_aw_ammo} |
Integer |
cs2md.ap01.aw.ammo_max |
{cs2md_ap01_aw_ammo_max} |
Integer |
cs2md.ap01.aw.reserve |
{cs2md_ap01_aw_reserve} |
Integer |
cs2md.ap01.w.count |
{cs2md_ap01_w_count} |
Integer |
cs2md.ap01.w.raw_json |
{cs2md_ap01_w_raw_json} |
String |
cs2md.ap01.w01.slot |
{cs2md_ap01_w01_slot} |
String |
cs2md.ap01.w01.name |
{cs2md_ap01_w01_name} |
String |
cs2md.ap01.w01.type |
{cs2md_ap01_w01_type} |
String |
cs2md.ap01.w01.paint |
{cs2md_ap01_w01_paint} |
String |
cs2md.ap01.w01.state |
{cs2md_ap01_w01_state} |
String |
cs2md.ap01.w01.ammo |
{cs2md_ap01_w01_ammo} |
Integer |
cs2md.ap01.w01.ammo_max |
{cs2md_ap01_w01_ammo_max} |
Integer |
cs2md.ap01.w01.reserve |
{cs2md_ap01_w01_reserve} |
Integer |
Replace the first 01 with 02 through 10 for the other players. Replace the second weapon 01 with 02 through 08 for each player's weapon slots.
Observer/spectator grenade payloads are exposed as cs2md.g01 through cs2md.g16.
| Pattern | Placeholder Example | Type |
|---|---|---|
cs2md.g.count |
{cs2md_g_count} |
Integer |
cs2md.g.raw_json |
{cs2md_g_raw_json} |
String |
cs2md.g01.id |
{cs2md_g01_id} |
String |
cs2md.g01.owner |
{cs2md_g01_owner} |
String |
cs2md.g01.type |
{cs2md_g01_type} |
String |
cs2md.g01.pos |
{cs2md_g01_pos} |
String |
cs2md.g01.vel |
{cs2md_g01_vel} |
String |
cs2md.g01.life |
{cs2md_g01_life} |
String |
cs2md.g01.effect |
{cs2md_g01_effect} |
String |
cs2md.g01.flames |
{cs2md_g01_flames} |
Integer |
cs2md.g01.raw_json |
{cs2md_g01_raw_json} |
String |
Replace 01 with 02 through 16 for the other grenade slots.
Basic live button:
{cs2md_map_name}
{cs2md_round_phase}
CT {cs2md_round_wins_ct} / T {cs2md_round_wins_t}
Player button:
{cs2md_player_name}
{cs2md_player_team} {cs2md_player_activity}
HP {cs2md_player_hp}
AR {cs2md_player_armor}
Weapon button:
{cs2md_weapon_name}
{cs2md_weapon_type}
{cs2md_weapon_state}
{cs2md_weapon_ammo_clip}/{cs2md_weapon_ammo_clip_max}
Bomb button:
BOMB {cs2md_bomb_state}
TIMER {cs2md_bomb_timer}
POS {cs2md_bomb_position}
Observer player slot:
P1 {cs2md_ap01_name}
{cs2md_ap01_team}
HP {cs2md_ap01_hp}
{cs2md_ap01_aw_name}
Grenade slot:
Nade {cs2md_g01_type}
owner {cs2md_g01_owner}
{cs2md_g01_pos}
Some variables are only available when CS2 actually sends that block.
Usually available during normal player gameplay:
providermaproundplayer_idplayer_stateplayer_weaponsplayer_match_stats
Often observer/spectator-only according to Valve:
allplayers_*allgrenadesbomb.positionbomb.carrierphase_countdownsplayer.positionplayer.forward
The plugin still exposes these variables, but empty values usually mean CS2 did not send that field.
The plugin provides these Macro Deck actions:
Refresh CS2 state: reads the latest local/stateresponse and updates Macro Deck variables.Reset CS2 listener: restarts the plugin's local listener and updatescs2md.status.
Open this URL locally while Macro Deck is running:
http://127.0.0.1:3333/state
If the endpoint returns HTTP 200 but all values are empty or zero, the plugin is listening but CS2 has not sent useful GSI data yet. Start CS2, enter a training session or match, and reload /state.
To inspect the latest raw CS2 GSI payload received by the plugin:
http://127.0.0.1:3333/raw
To run the optional console listener instead of the Macro Deck plugin:
tools\run-listener.cmdClose Macro Deck first, or free port 3333, before running the debug listener.
Port already in use:
- Close Macro Deck or the debug listener.
- Only one process can listen on
http://127.0.0.1:3333/. cs2md.statusis set toport_in_usewhen the plugin cannot bind to port3333.
/state is empty:
- Confirm CS2 was restarted after adding the GSI config.
- Enter a live match or training session.
- Confirm the config file is in the correct CS2
cfgfolder. cs2md.connectedremainsfalseandcs2md.statusremainswaiting_for_cs2until a real CS2 GSI payload is received.
Token mismatch:
- The CS2 config token must match the plugin default token.
- Current default token:
cs2md_token_segreto cs2md.statusis set totoken_invalidwhen the plugin receives a payload with the wrong token.
Macro Deck update check returns 404 for this plugin:
- This is expected for local development while the plugin is not published in the Extension Store.
The plugin listens only on 127.0.0.1 and receives local CS2 Game State Integration payloads. It does not send CS2 data to an external service.
The icon is an original radar/HUD-style design. It does not use Counter-Strike, Valve, or Macro Deck logo assets.
The GSI lettering uses Oxanium, released under the SIL Open Font License. See THIRD_PARTY_NOTICES.md.
Cs2MacroDeck.Plugin: Macro Deck plugin.Cs2Gsi.Core: CS2 GSI models, parser, defaults, and shared HTTP server.Cs2Gsi.Listener: optional console/debug listener for development.tools: helper launch files for the debug listener.
For normal use, run Macro Deck and let the plugin receive CS2 data directly. Do not run Cs2Gsi.Listener at the same time unless you are explicitly debugging, because both try to use port 3333.
- Add final Macro Deck screenshots/GIFs.
- Tag the first release as
v0.1.0. - Submit to the Macro Deck Extension Store.
This project is licensed under the MIT License. See LICENSE.