Skip to content

Instantly share code, notes, and snippets.

@ericek111
Created November 20, 2020 02:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericek111/abe5829f6e52e4b25b3b97a0efd0b22b to your computer and use it in GitHub Desktop.
Save ericek111/abe5829f6e52e4b25b3b97a0efd0b22b to your computer and use it in GitHub Desktop.
CS:GO voice demo parser
#include <unistd.h>
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <sys/time.h>
#include <celt.h>
// gcc -o csgo csgo.c -I./celt-0.11.1/libcelt -L"$HOME/.steam/steam/steamapps/common/Counter-Strike Global Offensive/bin/linux64" -l:vaudio_celt_client.so && ./csgo && play -t raw -r 22050 -e signed -b 16 -c 1 out.raw
// export LD_LIBRARY_PATH="$HOME/.steam/steam/steamapps/common/Counter-Strike Global Offensive/bin/linux64"
// ./csgo
// play -t raw -r 22050 -e signed -b 16 -c 1 out.raw
// sox -t raw -r 22050 -e signed -b 16 -c 1 -L out.raw out.wav
#define BUF_SIZE 1024*1024
int main(int argc, char *argv[]) {
unsigned char buf[BUF_SIZE];
const unsigned int FRAME_SIZE = 512;
const unsigned int SAMPLE_RATE = 22050;
FILE *f = fopen("voicedata.dat", "r");
if (f == NULL) {
return 1;
}
size_t read = fread(buf, 1, BUF_SIZE, f);
fclose(f);
CELTMode *dm = celt_mode_create(SAMPLE_RATE, FRAME_SIZE, NULL);
CELTDecoder *d = celt_decoder_create_custom(dm, 1, NULL);
size_t outsize = (read / 64) * FRAME_SIZE * sizeof(celt_int16);
celt_int16* pcmout = malloc(outsize);
size_t done = 0;
int frames = 0;
for (; done < read; done += 64, frames++) {
int ret = 0;
if ((ret = celt_decode(d, buf + done, 64, pcmout + frames * FRAME_SIZE, FRAME_SIZE)) < 0) {
fprintf(stderr, "unable to decode... > %d (at %d/%d)\n", ret, done, read);
//return 1;
}
//printf("Done %d/%d (to %ld/%ld)\n", done, read, frames * FRAME_SIZE * 2, outsize);
}
FILE* file_p = fopen("out.raw", "w");
size_t written = fwrite(pcmout, outsize, 1, file_p);
fclose(file_p);
free(pcmout);
return 0;
}
@ubbs
Copy link

ubbs commented Dec 11, 2020

how am i supposed to use this. Could you please explain for a coding noob sir.

@ericek111
Copy link
Author

ericek111 commented Dec 11, 2020

Everything's in the issue. Use a demo parser of your choice to dump the voice packets (on "dem_packet" message where cmd == SVC_Messages.svc_VoiceData). My Java GODemolyser can do it, saul's node.js demofile can do it -- that one could be easier to use, since it works in the browser, but it failed on some demos for me and I don't really like JavaScript for this kind of stuff.

You should really learn to at least get the packet parsing working. My parser should be pretty easy to use and has no dependencies.

Then you'll pour the raw data into this little program that binds to CS:GO's voice library. Commands to compile it are commented right below the includes. You also need the CELT headers.

@michihupf
Copy link

Is there a way to do it the other way around and instead of extracting voice, injecting voice from let's say 5 wav files (1 for each player). That way you could use this to add external voice communication (for example from discord or teamspeak) and put it into the demo file for gameplay analysis with voice communication. I'd imagine this is significantly harder.

@ericek111
Copy link
Author

Actually, maybe it's not that hard. Look at my GODemolyser, even lacking lots of features, it's still the most understandable to me (and also the fastest). You could inject your own dem_packet-s into the file. Protobufs can be, just as easily as they are parsed, constructed from your own data. Here, basically revert this process: https://github.com/ericek111/GODemolyser/blob/master/GODemolyser/src/eu/lixko/godemolyser/parser/dem/PacketParser.java#L15

I think the greatest hurdle here would be implementing write support for the ByteArrayDataStream class, hah. It operates without copying, so inserting data mid-buffer would shift other data and mess up all slices created from it. Some things are also bit-indexed (Protobuf encoding), so the entire affected buffer would have to be bit-shifted on non-aligned writes. But, from what I recall, packets are aligned to bytes, to that shouldn't be an issue for your use case, just thinking aloud. Also, the DataStream class (from which implementations are extended) does not care about the underlying storage.

@michihupf
Copy link

So you can just "insert" the audio into the .dem file by just reversing this one process? How come noone ever thought of that?

@ericek111
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment