Skip to content

Instantly share code, notes, and snippets.

@sigsegv-mvm
Created October 14, 2015 04:44
Show Gist options
  • Save sigsegv-mvm/5bc501fb1412006c340d to your computer and use it in GitHub Desktop.
Save sigsegv-mvm/5bc501fb1412006c340d to your computer and use it in GitHub Desktop.
Tool for stripping stringtable data out of SourceTV demos
#include <stdio.h>
#include <err.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
struct demo_header {
char header[8];
int32_t demo_proto_version;
int32_t net_proto_version;
char server_name[260];
char client_name[260];
char map_name[260];
char game_dir[260];
float playback_time;
int32_t ticks;
int32_t frames;
int32_t signon_length;
};
enum {
DEM_SIGNON = 1,
DEM_PACKET = 2,
DEM_SYNCTICK = 3,
DEM_CONSOLECMD = 4,
DEM_USERCMD = 5,
DEM_DATATABLES = 6,
DEM_STOP = 7,
DEM_STRINGTABLES = 8,
};
static const char *msgtypenames[] = {
"0",
"SIGNON",
"PACKET",
"SYNCTICK",
"CONSOLECMD",
"USERCMD",
"DATATABLES",
"STOP",
"STRINGTABLES",
};
char path_in[1024];
char path_out[1024];
FILE *f_in = NULL;
FILE *f_out = NULL;
struct demo_header hdr;
int main(int argc, char **argv)
{
fprintf(stderr, "SourceTV demo stringtable remover (by sigsegv)\n\n");
fprintf(stderr,
"WARNING: this is a hacky tool meant solely for use as a temporary workaround;\n"
" make sure to keep your original demo file (it won't be overwritten)\n\n");
if (argc != 2) {
fprintf(stderr, "usage: %s <demoname>\n"
"(reads from demoname.dem, writes to demoname_fixed.dem)\n",
argv[0]);
return 1;
}
const char *path_base = argv[1];
snprintf(path_in, sizeof(path_in), "%s.dem", path_base);
snprintf(path_out, sizeof(path_out), "%s_fixed.dem", path_base);
f_in = fopen(path_in, "r");
if (f_in == NULL) {
err(1, "could not open '%s'", path_in);
}
if (fread(&hdr, 1, sizeof(hdr), f_in) != sizeof(hdr)) {
err(1, "could not read header from '%s'", path_in);
}
bool strtab_found = false;
int32_t strtab_off = -1;
int32_t strtab_len = 0;
int32_t stop_at = hdr.signon_length;
if (stop_at == 0) {
stop_at = INT32_MAX;
}
int32_t off = sizeof(hdr);
while (!strtab_found && off < stop_at) {
int msg_type;
if ((msg_type = fgetc(f_in)) == EOF) {
if (feof(f_in)) {
break;
} else {
err(1, "could not read msg type from '%s', offset %x",
path_in, off);
}
}
++off;
warnx("off 0x%x: msg_type %d %s",
off, msg_type, msgtypenames[msg_type]);
if (msg_type == DEM_STOP) {
break;
}
int32_t msg_tick;
if (fread(&msg_tick, 1, sizeof(msg_tick), f_in) != sizeof(msg_tick)) {
err(1, "could not read msg tick from '%s', "
"msg_type %d, offset 0x%x",
path_in, msg_type, off);
}
off += 4;
if (msg_type == DEM_SYNCTICK) {
continue;
}
int skip = 0;
if (msg_type == DEM_SIGNON || msg_type == DEM_PACKET) {
skip = 0x54;
} else if (msg_type == DEM_USERCMD) {
skip = 0x4;
}
if (skip != 0) {
if (fseek(f_in, skip, SEEK_CUR) != 0) {
err(1, "could not seek 0x%x bytes forward in '%s', "
"msg_type %d, offset 0x%x",
skip, path_in, msg_type, off);
}
}
off += skip;
int32_t msg_len;
if (fread(&msg_len, 1, sizeof(msg_len), f_in) != sizeof(msg_len)) {
err(1, "could not read msg len from '%s', "
"msg_type %d, offset 0x%x",
path_in, msg_type, off);
}
off += 4;
if (msg_type == DEM_STRINGTABLES) {
strtab_off = off - 9;
strtab_len = msg_len + 9;
strtab_found = true;
warnx("found DEM_STRINGTABLES: offset 0x%x, total len %d",
strtab_off, strtab_len);
}
if (fseek(f_in, msg_len, SEEK_CUR) != 0) {
err(1, "could not seek 0x%x bytes forward in '%s', "
"msg_type %d, offset 0x%x",
msg_len, path_in, msg_type, off);
}
off += msg_len;
}
if (strtab_found) {
warnx("will now write '%s'", path_out);
f_out = fopen(path_out, "w");
if (f_out == NULL) {
err(1, "could not open '%s'", path_out);
}
if (hdr.signon_length != 0) {
warnx("adjusting demo header for changed signon length\n"
" was: %d\n"
" now: %d",
hdr.signon_length,
hdr.signon_length - strtab_len);
hdr.signon_length -= strtab_len;
}
if (fwrite(&hdr, 1, sizeof(hdr), f_out) != sizeof(hdr)) {
err(1, "could not write header to '%s'", path_out);
}
char buf[4096];
/* data copy:
* from: right after the header
* to: right before DEM_STRINGTABLES
*/
if (fseek(f_in, sizeof(hdr), SEEK_SET) != 0) {
err(1, "could not seek back to the header in '%s'", path_in);
}
int32_t len = strtab_off - sizeof(hdr);
int32_t off = sizeof(hdr);
while (len > 0) {
int32_t this_len = sizeof(buf);
if (len < this_len) {
this_len = len;
}
if (fread(buf, 1, this_len, f_in) != this_len) {
err(1, "could not read %d bytes from '%s' at offset 0x%x",
this_len, path_in, off);
}
if (fwrite(buf, 1, this_len, f_out) != this_len) {
err(1, "could not write %d bytes to '%s' at offset 0x%x",
this_len, path_out, off);
}
len -= this_len;
off += this_len;
}
/* data copy:
* from: right after DEM_STRINGTABLES
* to: end-of-file
*/
if (fseek(f_in, strtab_off + strtab_len, SEEK_SET) != 0) {
err(1, "could not seek to post-DEM_STRINGTABLES location in '%s'",
path_in);
}
bool done = false;
do {
int32_t this_len;
if ((this_len = fread(buf, 1, sizeof(buf), f_in)) != sizeof(buf)) {
if (feof(f_in)) {
done = true;
} else {
err(1, "could not read %d bytes from '%s' at offset 0x%x",
sizeof(buf), path_in, off);
}
}
if (fwrite(buf, 1, this_len, f_out) != this_len) {
err(1, "could not write %d bytes to '%s' at offset 0x%x",
this_len, path_out, off);
}
len -= this_len;
off += this_len;
} while (!done);
fclose(f_out);
} else {
warnx("no DEM_STRINGTABLES msg found, no need to write a new file");
}
fclose(f_in);
warnx("success");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment