Game State Integration with Counter-Strike: Global Offensive

Click me to be taken to the GitHub repository for the project in this post!

Over the weekend, I messed around with the first-person shooter game Counter-Strike: Global Offensive’s game state integration library released in their winter update. When it was released, Valve provided next to no documentation, a common trend that I’m starting to notice with a lot of projects (or maybe I’m just falling victim to confirmation bias, who knows). After a lot of trial and error, I think I managed to get the hang of it. It seems that the mechanisms behind it work pretty simply.

When Counter-Strike: Global Offensive is launched, it looks through a directory full of configuration files for something matching the schema of a game state integration configuration file. The example configuration file given looks like this:

"Console Sample v.1"
 "uri" ""
 "timeout" "5.0"
 "buffer"  "0.1"
 "throttle" "0.5"
 "heartbeat" "60.0"
   "token" "CCWJu64ZV3JHDT8hZc"
   "provider"            "1"
   "map"                 "1"
   "round"               "1"
   "player_id"           "1"
   "player_state"        "1"
   "player_weapons"      "1"
   "player_match_stats"  "1"

Every time an event occurs, the game client will HTTP POST the event to uri if the time between the last request and the current time is more than throttle. For the value buffer, if multiple events happen quickly (which they do), the game client will throw away events unless you have a buffer set. Some people reported that you don’t need to have a buffer if you’re on LAN, but I recommend keeping a buffer. The heartbeat value is how often it should send POST a heartbeat request. Your timeout is your standard timeout value for sending a request. This value defaults to 1.1 if there is no value specified.

The possible data provided by the game client (denoted by the data key) was very scarce at release but a helpful Redditor put together a post detailing their findings. It looks like there’s a lot of information CS:GO is reporting, everything from the current score of a game to the current state of the player’s screen (“menu”, “playing”, or “textinput” [developer console accessible with `]). Like all of my projects, I started with the rudimentary idea of making something occur when an event happened. I decided that I wanted to make a program (in Python) that played a sound through the player’s microphone when they shot at someone.

Tracking when a gun was shot was very simple. I created a basic web server through Flask that treated the information posted as a JSON array. From there, it was simply the challenging of iterating through the different weapon slots (5 of them, including grenades) and then detecting a change.

I started looking around for how I would be able to play a sound through (splice audio in) the microphone in Python but it appeared that this feat would be impossible in Python. I would have had to write a driver in another language like C or C++ and didn’t want to deal with that. A friend of mine reminded me of programs like HLDJ and SLAM that do not require drivers to be installed and instead interface with the Source Engine. When I tried to find HLDJ’s source code, it appeared that the site shut down. Thankfully, though, SLAM is open-source. Through reading through his source code, I was able to figure out that it was employing a rather cunning method by using a client variable called “voice_inputfromfile”. While there is no documentation on this method (go figure), through SLAM’s source code I found that it plays a file named “voice_input.wav” inside the user’s CS:GO install directory (..\steamapps\common\Counter-Strike Global Offensive\voice_input.wav). I had the idea to then create a bind inside the client that ran this command.

I’ve always found the Source Engine’s binds off-putting but this wasn’t too bad. There were only three commands that needed to be ran to turn it on:

voice_loopback 1
voice_inputfromfile 1

To turn it off, it was:

voice_loopback 0
voice_inputfromfile 0

I then created a toggle for this:

alias audio_file_on "voice_loopback 1;voice_inputfromfile 1;+voicerecord;alias audio_file_toggle audio_file_off"
alias audio_file_off "voice_loopback 0;voice_inputfromfile 0;-voicerecord;alias audio_file_toggle audio_file_on"
alias audio_file_toggle "audio_file_on"

To use this, all we had to do now was bind a key (bind <key> audio_file_toggle) and we would play the file. I then made the program create a file in CS:GO’s configuration directory (..\steamapps\common\Counter-Strike Global Offensive\csgo\cfg) that had those lines. From there, it was all smooth sailing. I imported the AutoIt library for Python (PyAutoIt 0.3) and made it so that whenever the program detected a shot fired, it sent a key press.

To recap, here’s the process:

  1. Program starts up and copies a list of commands to be automatically executed when the user’s game starts.
  2. Game starts up and executes the binds.
  3. User joins a match.
  4. Receive data from the game client, proceed if there is a change in the amount of bullets in the clip.
  5. Send a key press, through PyAutoIt, that triggers the in-game bind.
  6. In-game bind is triggered, enable voice_loopback, voice_inputfromfile, and turn on the microphone (+voicerecord).
  7. When the file is over, the microphone stops playing and resets the microphone.
  8. Repeat until the program stops.

In the spirit of open-source, I have it all available on my GitHub account. If you have any questions, feel free to comment here create an issue on the GitHub repository.

Leave a Reply

Your email address will not be published. Required fields are marked *