rapier
Version:
A JavaScript Dota 2 (Source 2) replay parsing library.
1,432 lines (1,424 loc) • 2.24 MB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (Buffer){
/**
* Converts a given buffer of bytes to a stream of bits and provides methods for reading individual bits (non-aligned reads)
**/
var Long = require('long');
//accepts a native buffer object
var BitStream = function(buf) {
this.offset = 0;
this.limit = buf.length * 8;
this.bytes = buf;
};
/**
* Reads the specified number of bits (possibly non-aligned) and returns as 32bit int
**/
BitStream.prototype.readBits = function(n) {
if (n > (this.limit - this.offset)) {
throw "not enough bits left in stream to read!";
}
var bitOffset = this.offset % 8;
var bitsToRead = bitOffset + n;
var bytesToRead = ~~(bitsToRead / 8);
//if reading a multiple of 8 bits, read an additional byte
if (bitsToRead % 8) {
bytesToRead += 1;
}
var value = null;
if (!bitOffset && n === 8) {
//if we are byte-aligned and only want one byte, we can read quickly without shifting operations
value = this.bytes.readUInt8(this.offset / 8);
}
//32 bit shifting
else if (bitsToRead <= 31) {
value = 0;
//console.error(bits, this.offset, bitOffset, bitsToRead,bytesToRead);
for (var i = 0; i < bytesToRead; i++) {
//extract the byte from the backing buffer
var m = this.bytes[~~(this.offset / 8) + i];
//move these 8 bits to the correct location
//looks like most significant 8 bits come last, so this flips endianness
value += (m << (i * 8));
}
//drop the extra bits, since we started from the beginning of the byte regardless of offset
value >>= bitOffset;
//shift a single 1 over, subtract 1 to form a bit mask that removes the first bit
value &= ((1 << n) - 1);
}
else {
//trying to read 32+ bits with native JS probably won't work because we must then read five bytes from the backing buffer
//this means in practice we may have difficulty with n >= 25 bits (since offset can be up to 7)
//can't fit that into a 32 bit int unless we use JS Long, which is slow
console.error(bitsToRead);
//64 bit shifting, we only need this if our operations cant fit into 32 bits
value = new Long();
//console.error(bits, this.offset, bitOffset, bitsToRead,bytesToRead);
for (var i = 0; i < bytesToRead; i++) {
//extract the byte from the backing buffer
var m64 = this.bytes[~~(this.offset / 8) + i];
//console.error(m, this.bytes);
//copy m into a 64bit holder so we can shift bits around more
m64 = new Long.fromNumber(m64);
//shift to get the bits we want
value = value.add(m64.shiftLeft(i * 8));
}
value = value.shiftRight(bitOffset);
//shift a single 1 over, subtract 1 to form a bit mask
value = value.and((1 << n) - 1);
value = value.toInt();
}
this.offset += n;
return value;
};
/**
* Reads the specified number of bits into a Buffer and returns
**/
BitStream.prototype.readBuffer = function(bits) {
var bytes = Math.ceil(bits / 8);
var result = new Buffer(bytes);
var offset = 0;
result.length = bytes;
while (bits > 0) {
//read up to 8 bits at a time (we may read less at the end if not aligned)
var bitsToRead = Math.min(bits, 8);
result.writeUInt8(this.readBits(bitsToRead), offset);
offset += 1;
bits -= bitsToRead;
}
return result;
};
BitStream.prototype.readBoolean = function() {
return this.readBits(1);
};
/**
* Reads until we reach a null terminator character and returns the result as a string
**/
BitStream.prototype.readNullTerminatedString = function() {
var str = "";
while (true) {
var byteInt = this.readBits(8);
if (!byteInt) {
break;
}
var byteBuf = new Buffer(1);
byteBuf.writeUInt8(byteInt);
str += byteBuf.toString();
}
//console.log(str);
return str;
};
BitStream.prototype.readVarUInt = function() {
var max = 32;
var m = ((max + 6) / 7) * 7;
var value = 0;
var shift = 0;
while (true) {
var byte = this.readBits(8);
value |= (byte & 0x7F) << shift;
shift += 7;
if ((byte & 0x80) === 0 || shift == m) {
return value;
}
}
};
BitStream.prototype.readUBitVar = function() {
// Thanks to Robin Dietrich for providing a clean version of this code :-)
// The header looks like this: [XY00001111222233333333333333333333] where everything > 0 is optional.
// The first 2 bits (X and Y) tell us how much (if any) to read other than the 6 initial bits:
// Y set -> read 4
// X set -> read 8
// X + Y set -> read 28
var v = this.readBits(6);
//bitwise & 0x30 (0b110000) (determines whether the first two bits are set)
switch (v & 0x30) {
case 0x10:
v = (v & 15) | (this.readBits(4) << 4);
break;
case 0x20:
v = (v & 15) | (this.readBits(8) << 4);
break;
case 0x30:
v = (v & 15) | (this.readBits(28) << 4);
break;
}
return v;
};
module.exports = BitStream;
}).call(this,require("buffer").Buffer)
},{"buffer":28,"long":33}],2:[function(require,module,exports){
(function (global,Buffer){
/**
* Class creating a Source 2 Dota 2 replay parser
**/
var ProtoBuf = require('protobufjs');
var snappy = require('./snappy');
var BitStream = require('./BitStream');
var EventEmitter = require('events').EventEmitter;
var async = require('async');
var stream = require('stream');
var types = require('./build/types.json');
var protos = require('./build/protos.json');
var packetTypes = types.packets;
var demTypes = types.dems;
//read the protobufs and build a dota object for reference
var builder = ProtoBuf.newBuilder();
ProtoBuf.loadJson(protos, builder);
var dota = builder.build();
//CDemoSignonPacket is a special case and should be decoded with CDemoPacket since it doesn't have its own protobuf
//it appears that things like the gameeventlist and createstringtables calls are here?
dota["CDemoSignonPacket"] = dota["CDemoPacket"];
//console.error(Object.keys(dota));
var Parser = function(input) {
//if a JS ArrayBuffer, convert to native node buffer
if (input.byteLength) {
var buffer = new Buffer(input.byteLength);
var view = new Uint8Array(input);
for (var i = 0; i < buffer.length; i++) {
buffer[i] = view[i];
}
input = buffer;
}
//wrap a passed buffer in a stream
if (Buffer.isBuffer(input)) {
var bufferStream = new stream.PassThrough();
bufferStream.end(input);
input = bufferStream;
}
var stop = false;
var p = new EventEmitter();
//expose the gameeventdescriptor, stringtables, types, entities to the user and have the parser update them as it parses
p.types = types;
p.game_event_descriptors = {};
p.string_tables = {
tables: [],
byName: {}
};
p.entities = {};
p.start = function start(cb) {
input.on('end', function() {
stop = true;
input.removeAllListeners();
return cb();
});
async.series({
"header": function(cb) {
readString(8, function(err, header) {
//verify the file magic number is correct
cb(err || header.toString() !== "PBDEMS2\0", header);
});
},
//two uint32s related to replay size
"size1": readUint32,
"size2": readUint32,
"demo": function(cb) {
//keep parsing demo messages until it hits a stop condition
async.until(function() {
return stop;
}, readDemoMessage, cb);
}
}, cb);
};
/**
* Internal listeners to automatically process certain packets.
* We abstract this away from the user so they don't need to worry about it.
* For optimal speed we could allow the user to disable the ones they don't need
**/
p.on("CDemoStop", function(data) {
//don't stop on CDemoStop since some replays have CDemoGameInfo after it
//stop = true;
});
//p.on("CDemoStringTables", readCDemoStringTables);
p.on("CDemoSignonPacket", readCDemoPacket);
p.on("CDemoPacket", readCDemoPacket);
p.on("CDemoFullPacket", function(data) {
//console.error(data);
//readCDemoStringTables(data.string_table);
readCDemoPacket(data.packet);
});
//string tables may mutate over the lifetime of the replay.
//Therefore we listen for create/update events and modify the table as needed.
p.on("CSVCMsg_CreateStringTable", createStringTable);
p.on("CSVCMsg_UpdateStringTable", updateStringTable);
//this packet sets up our game event descriptors
p.on("CMsgSource1LegacyGameEventList", function(data) {
//console.error(data);
var gameEventDescriptors = p.game_event_descriptors;
for (var i = 0; i < data.descriptors.length; i++) {
gameEventDescriptors[data.descriptors[i].eventid] = data.descriptors[i];
}
});
//TODO entities. huffman trees, property decoding?! requires parsing CDemoClassInfo, and instancebaseline string table?
return p;
/**
* Reads the next DEM message from the replay (outer message)
**/
function readDemoMessage(cb) {
async.series({
command: readVarint32,
tick: readVarint32,
size: readVarint32
}, function(err, result) {
if (err) {
return cb(err);
}
readBytes(result.size, function(err, buf) {
// Read a command header, which includes both the message type
// well as a flag to determine whether or not whether or not the
// message is compressed with snappy.
var command = result.command;
var tick = result.tick;
var size = result.size;
// Extract the type and compressed flag out of the command
//msgType: = int32(command & ^ dota.EDemoCommands_DEM_IsCompressed)
//msgCompressed: = (command & dota.EDemoCommands_DEM_IsCompressed) == dota.EDemoCommands_DEM_IsCompressed
var demType = command & ~dota.EDemoCommands.DEM_IsCompressed;
var isCompressed = (command & dota.EDemoCommands.DEM_IsCompressed) === dota.EDemoCommands.DEM_IsCompressed;
// Read the tick that the message corresponds with.
//tick: = p.reader.readVarUint32()
// This appears to actually be an int32, where a -1 means pre-game.
/*
if tick == 4294967295 {
tick = 0
}
*/
if (tick === 4294967295) {
tick = 0;
}
if (isCompressed) {
buf = snappy.uncompressSync(buf);
}
var dem = {
tick: tick,
type: demType,
size: size,
data: buf
};
//console.error(dem);
if (demType in demTypes) {
//lookup the name of the protobuf message to decode with
var name = demTypes[demType];
if (dota[name]) {
if (listening(name)) {
dem.data = dota[name].decode(dem.data);
dem.data.proto_name = name;
p.emit("*", dem.data);
p.emit(name, dem.data);
}
}
else {
console.error("no proto definition for dem type %s", demType);
}
}
else {
console.error("no proto name for dem type %s", demType);
}
return cb(err);
});
});
}
// Internal parser for callback OnCDemoPacket, responsible for extracting
// multiple inner packets from a single CDemoPacket. This is the main structure
// that contains all other data types in the demo file.
function readCDemoPacket(msg) {
/*
message CDemoPacket {
optional int32 sequence_in = 1;
optional int32 sequence_out_ack = 2;
optional bytes data = 3;
}
*/
var priorities = {
"CNETMsg_Tick": -10,
"CSVCMsg_CreateStringTable": -10,
"CSVCMsg_UpdateStringTable": -10,
"CNETMsg_SpawnGroup_Load": -10,
"CSVCMsg_PacketEntities": 5,
"CMsgSource1LegacyGameEvent": 10
};
//the inner data of a CDemoPacket is raw bits (no longer byte aligned!)
var packets = [];
//extract the native buffer from the ByteBuffer decoded by protobufjs
//rewrap it in a new Buffer to force usage of node buffer shim rather than ArrayBuffer when in browser
var buf = new Buffer(msg.data.toBuffer());
//convert the buffer object into a bitstream so we can read bits from it
var bs = new BitStream(buf);
//read until less than 8 bits left
while (bs.limit - bs.offset >= 8) {
var t = bs.readUBitVar();
var s = bs.readVarUInt();
var d = bs.readBuffer(s * 8);
var pack = {
type: t,
size: s,
data: d,
position: packets.length
};
packets.push(pack);
}
//sort the inner packets by priority in order to ensure we parse dependent packets last
packets.sort(function(a, b) {
//we must use a stable sort here in order to preserve order of packets when possible (for example, string tables)
var p1 = priorities[packetTypes[a.type]] || 0;
var p2 = priorities[packetTypes[b.type]] || 0;
if (p1 === p2) {
return a.position - b.position;
}
return p1 - p2;
});
for (var i = 0; i < packets.length; i++) {
var packet = packets[i];
var packType = packet.type;
if (packType in packetTypes) {
//lookup the name of the proto message for this packet type
var name = packetTypes[packType];
if (dota[name]) {
if (listening(name)) {
packet.data = dota[name].decode(packet.data);
packet.data.proto_name = name;
p.emit("*", packet.data);
p.emit(name, packet.data);
}
}
else {
console.error("no proto definition for packet name %s", name);
}
}
else {
console.error("no proto name for packet type %s", packType);
}
}
}
function createStringTable(data) {
//create a stringtable
//console.error(data);
//extract the native buffer from the string_data ByteBuffer, with the offset removed
var buf = new Buffer(data.string_data.toBuffer());
if (data.data_compressed) {
//decompress the string data with snappy
//early source 2 replays may use LZSS, we can detect this by reading the first four bytes of buffer
buf = snappy.uncompressSync(buf);
}
//pass the buffer and parse string table data from it
var items = parseStringTableData(buf, data.num_entries, data.user_data_fixed_size, data.user_data_size);
//console.error(items);
//remove the buf and replace with items, which is a decoded version of it
data.string_data = {};
// Insert the items into the table as an object
items.forEach(function(it) {
data.string_data[it.index] = it;
});
/*
// Apply the updates to baseline state
if t.name == "instancebaseline" {
p.updateInstanceBaseline()
}
*/
p.string_tables.byName[data.name] = data;
p.string_tables.tables.push(data);
}
function updateStringTable(data) {
//update a string table
//retrieve table by id
var table = p.string_tables.tables[data.table_id];
//extract native buffer
var buf = new Buffer(data.string_data.toBuffer());
if (table) {
var items = parseStringTableData(buf, data.num_changed_entries, table.user_data_fixed_size, table.user_data_size);
var string_data = table.string_data;
items.forEach(function(it) {
//console.error(it);
if (!string_data[it.index]) {
//we don't have this item in the string table yet, add it
string_data[it.index] = it;
}
else {
//we're updating an existing item
//only update key if the new key is not blank
if (it.key) {
//console.error("updating key %s->%s at index %s on %s, id %s", string_data[it.index].key, it.key, it.index, table.name, data.table_id);
string_data[it.index].key = it.key;
//string_data[it.index].key = [].concat(string_data[it.index].key).concat(it.key);
}
//only update value if the new item has a nonempty value buffer
if (it.value.length) {
//console.error("updating value length %s->%s at index %s on %s", string_data[it.index].value.length, it.value.length, it.index, table.name);
string_data[it.index].value = it.value;
}
}
});
}
else {
throw "string table doesn't exist!";
}
/*
// Apply the updates to baseline state
if t.name == "instancebaseline" {
p.updateInstanceBaseline()
}
*/
}
/**
* Parses a buffer of string table data and returns an array of decoded items
**/
function parseStringTableData(buf, num_entries, userDataFixedSize, userDataSize) {
// Some tables have no data
if (!buf.length) {
return [];
}
var items = [];
var bs = new BitStream(buf);
// Start with an index of -1.
// If the first item is at index 0 it will use a incr operation.
var index = -1;
var STRINGTABLE_KEY_HISTORY_SIZE = 32;
// Maintain a list of key history
// each entry is a string
var keyHistory = [];
// Loop through entries in the data structure
// Each entry is a tuple consisting of {index, key, value}
// Index can either be incremented from the previous position or overwritten with a given entry.
// Key may be omitted (will be represented here as "")
// Value may be omitted
for (var i = 0; i < num_entries; i++) {
var key = null;
var value = new Buffer(0);
// Read a boolean to determine whether the operation is an increment or
// has a fixed index position. A fixed index position of zero should be
// the last data in the buffer, and indicates that all data has been read.
var incr = bs.readBoolean();
if (incr) {
index += 1;
}
else {
index = bs.readVarUInt() + 1;
}
// Some values have keys, some don't.
var hasKey = bs.readBoolean();
if (hasKey) {
// Some entries use reference a position in the key history for
// part of the key. If referencing the history, read the position
// and size from the buffer, then use those to build the string
// combined with an extra string read (null terminated).
// Alternatively, just read the string.
var useHistory = bs.readBoolean();
if (useHistory) {
var pos = bs.readBits(5);
var size = bs.readBits(5);
if (pos >= keyHistory.length) {
//history doesn't have this position, just read
key = bs.readNullTerminatedString();
}
else {
var s = keyHistory[pos];
if (size > s.length) {
//our target size is longer than the key stored in history
//pad the remaining size with a null terminated string from stream
key = (s + bs.readNullTerminatedString());
}
else {
//we only want a piece of the historical string, slice it out and read the null terminator
key = s.slice(0, size) + bs.readNullTerminatedString();
}
}
}
else {
//don't use the history, just read the string
key = bs.readNullTerminatedString();
}
keyHistory.push(key);
if (keyHistory.length > STRINGTABLE_KEY_HISTORY_SIZE) {
//drop the oldest key if we hit the cap
keyHistory.shift();
}
}
// Some entries have a value.
var hasValue = bs.readBoolean();
if (hasValue) {
// Values can be either fixed size (with a size specified in
// bits during table creation, or have a variable size with
// a 14-bit prefixed size.
if (userDataFixedSize) {
value = bs.readBuffer(userDataSize);
}
else {
var valueSize = bs.readBits(14);
//TODO mysterious 3 bits of data?
bs.readBits(3);
value = bs.readBuffer(valueSize * 8);
}
}
items.push({
index: index,
key: key,
value: value
});
}
//console.error(keyHistory, items, num_entries);
return items;
}
/**
* Returns whether there is an attached listener for this message name.
**/
function listening(name) {
return p.listeners(name).length || p.listeners("*").length;
}
function readCDemoStringTables(data) {
//rather than processing when we read this demo message, we want to create when we read the packet CSVCMsg_CreateStringTable
//this packet is just emitted as a state dump at intervals
return;
}
function readByte(cb) {
readBytes(1, function(err, buf) {
cb(err, buf.readInt8(0));
});
}
function readString(size, cb) {
readBytes(size, function(err, buf) {
cb(err, buf.toString());
});
}
function readUint32(cb) {
readBytes(4, function(err, buf) {
cb(err, buf.readUInt32LE(0));
});
}
function readVarint32(cb) {
readByte(function(err, tmp) {
if (tmp >= 0) {
return cb(err, tmp);
}
var result = tmp & 0x7f;
readByte(function(err, tmp) {
if (tmp >= 0) {
result |= tmp << 7;
return cb(err, result);
}
else {
result |= (tmp & 0x7f) << 7;
readByte(function(err, tmp) {
if (tmp >= 0) {
result |= tmp << 14;
return cb(err, result);
}
else {
result |= (tmp & 0x7f) << 14;
readByte(function(err, tmp) {
if (tmp >= 0) {
result |= tmp << 21;
return cb(err, result);
}
else {
result |= (tmp & 0x7f) << 21;
readByte(function(err, tmp) {
result |= tmp << 28;
if (tmp < 0) {
err = "malformed varint detected";
}
return cb(err, result);
});
}
});
}
});
}
});
});
}
function readBytes(size, cb) {
if (!size) {
//return an empty buffer if reading 0 bytes
return cb(null, new Buffer(""));
}
var buf = input.read(size);
if (buf) {
return cb(null, buf);
}
else {
input.once('readable', function() {
return readBytes(size, cb);
});
}
}
};
global.Parser = Parser;
module.exports = Parser;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer)
},{"./BitStream":1,"./build/protos.json":3,"./build/types.json":4,"./snappy":35,"async":5,"buffer":28,"events":8,"protobufjs":34,"stream":26}],3:[function(require,module,exports){
module.exports={
"package": null,
"options": {
"optimize_for": "SPEED",
"cc_generic_services": false
},
"messages": [
{
"name": "CMsgProtoBufHeader",
"options": {
"(msgpool_soft_limit)": 256,
"(msgpool_hard_limit)": 1024
},
"fields": [
{
"rule": "optional",
"type": "fixed64",
"name": "client_steam_id",
"id": 1
},
{
"rule": "optional",
"type": "int32",
"name": "client_session_id",
"id": 2
},
{
"rule": "optional",
"type": "uint32",
"name": "source_app_id",
"id": 3
},
{
"rule": "optional",
"type": "fixed64",
"name": "job_id_source",
"id": 10,
"options": {
"default": 18446744073709552000
}
},
{
"rule": "optional",
"type": "fixed64",
"name": "job_id_target",
"id": 11,
"options": {
"default": 18446744073709552000
}
},
{
"rule": "optional",
"type": "string",
"name": "target_job_name",
"id": 12
},
{
"rule": "optional",
"type": "int32",
"name": "eresult",
"id": 13,
"options": {
"default": 2
}
},
{
"rule": "optional",
"type": "string",
"name": "error_message",
"id": 14
},
{
"rule": "optional",
"type": "GCProtoBufMsgSrc",
"name": "gc_msg_src",
"id": 200,
"options": {
"default": "GCProtoBufMsgSrc_Unspecified"
}
},
{
"rule": "optional",
"type": "uint32",
"name": "gc_dir_index_source",
"id": 201
}
]
},
{
"name": "CMsgWebAPIKey",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "status",
"id": 1,
"options": {
"default": 255
}
},
{
"rule": "optional",
"type": "uint32",
"name": "account_id",
"id": 2,
"options": {
"default": 0
}
},
{
"rule": "optional",
"type": "uint32",
"name": "publisher_group_id",
"id": 3,
"options": {
"default": 0
}
},
{
"rule": "optional",
"type": "uint32",
"name": "key_id",
"id": 4
},
{
"rule": "optional",
"type": "string",
"name": "domain",
"id": 5
}
]
},
{
"name": "CMsgHttpRequest",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "request_method",
"id": 1
},
{
"rule": "optional",
"type": "string",
"name": "hostname",
"id": 2
},
{
"rule": "optional",
"type": "string",
"name": "url",
"id": 3
},
{
"rule": "repeated",
"type": "RequestHeader",
"name": "headers",
"id": 4
},
{
"rule": "repeated",
"type": "QueryParam",
"name": "get_params",
"id": 5
},
{
"rule": "repeated",
"type": "QueryParam",
"name": "post_params",
"id": 6
},
{
"rule": "optional",
"type": "bytes",
"name": "body",
"id": 7
},
{
"rule": "optional",
"type": "uint32",
"name": "absolute_timeout",
"id": 8
}
],
"messages": [
{
"name": "RequestHeader",
"fields": [
{
"rule": "optional",
"type": "string",
"name": "name",
"id": 1
},
{
"rule": "optional",
"type": "string",
"name": "value",
"id": 2
}
]
},
{
"name": "QueryParam",
"fields": [
{
"rule": "optional",
"type": "string",
"name": "name",
"id": 1
},
{
"rule": "optional",
"type": "bytes",
"name": "value",
"id": 2
}
]
}
]
},
{
"name": "CMsgWebAPIRequest",
"fields": [
{
"rule": "optional",
"type": "string",
"name": "UNUSED_job_name",
"id": 1
},
{
"rule": "optional",
"type": "string",
"name": "interface_name",
"id": 2
},
{
"rule": "optional",
"type": "string",
"name": "method_name",
"id": 3
},
{
"rule": "optional",
"type": "uint32",
"name": "version",
"id": 4
},
{
"rule": "optional",
"type": "CMsgWebAPIKey",
"name": "api_key",
"id": 5
},
{
"rule": "optional",
"type": "CMsgHttpRequest",
"name": "request",
"id": 6
},
{
"rule": "optional",
"type": "uint32",
"name": "routing_app_id",
"id": 7
}
]
},
{
"name": "CMsgHttpResponse",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "status_code",
"id": 1
},
{
"rule": "repeated",
"type": "ResponseHeader",
"name": "headers",
"id": 2
},
{
"rule": "optional",
"type": "bytes",
"name": "body",
"id": 3
}
],
"messages": [
{
"name": "ResponseHeader",
"fields": [
{
"rule": "optional",
"type": "string",
"name": "name",
"id": 1
},
{
"rule": "optional",
"type": "string",
"name": "value",
"id": 2
}
]
}
]
},
{
"name": "CMsgAMFindAccounts",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "search_type",
"id": 1
},
{
"rule": "optional",
"type": "string",
"name": "search_string",
"id": 2
}
]
},
{
"name": "CMsgAMFindAccountsResponse",
"fields": [
{
"rule": "repeated",
"type": "fixed64",
"name": "steam_id",
"id": 1
}
]
},
{
"name": "CMsgNotifyWatchdog",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "source",
"id": 1
},
{
"rule": "optional",
"type": "uint32",
"name": "alert_type",
"id": 2
},
{
"rule": "optional",
"type": "uint32",
"name": "alert_destination",
"id": 3
},
{
"rule": "optional",
"type": "bool",
"name": "critical",
"id": 4
},
{
"rule": "optional",
"type": "uint32",
"name": "time",
"id": 5
},
{
"rule": "optional",
"type": "uint32",
"name": "appid",
"id": 6
},
{
"rule": "optional",
"type": "string",
"name": "text",
"id": 7
}
]
},
{
"name": "CMsgAMGetLicenses",
"fields": [
{
"rule": "optional",
"type": "fixed64",
"name": "steamid",
"id": 1
}
]
},
{
"name": "CMsgPackageLicense",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "package_id",
"id": 1
},
{
"rule": "optional",
"type": "uint32",
"name": "time_created",
"id": 2
},
{
"rule": "optional",
"type": "uint32",
"name": "owner_id",
"id": 3
}
]
},
{
"name": "CMsgAMGetLicensesResponse",
"fields": [
{
"rule": "repeated",
"type": "CMsgPackageLicense",
"name": "license",
"id": 1
},
{
"rule": "optional",
"type": "uint32",
"name": "result",
"id": 2
}
]
},
{
"name": "CMsgAMGetUserGameStats",
"fields": [
{
"rule": "optional",
"type": "fixed64",
"name": "steam_id",
"id": 1
},
{
"rule": "optional",
"type": "fixed64",
"name": "game_id",
"id": 2
},
{
"rule": "repeated",
"type": "uint32",
"name": "stats",
"id": 3
}
]
},
{
"name": "CMsgAMGetUserGameStatsResponse",
"fields": [
{
"rule": "optional",
"type": "fixed64",
"name": "steam_id",
"id": 1
},
{
"rule": "optional",
"type": "fixed64",
"name": "game_id",
"id": 2
},
{
"rule": "optional",
"type": "int32",
"name": "eresult",
"id": 3,
"options": {
"default": 2
}
},
{
"rule": "repeated",
"type": "Stats",
"name": "stats",
"id": 4
},
{
"rule": "repeated",
"type": "Achievement_Blocks",
"name": "achievement_blocks",
"id": 5
}
],
"messages": [
{
"name": "Stats",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "stat_id",
"id": 1
},
{
"rule": "optional",
"type": "uint32",
"name": "stat_value",
"id": 2
}
]
},
{
"name": "Achievement_Blocks",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "achievement_id",
"id": 1
},
{
"rule": "optional",
"type": "uint32",
"name": "achievement_bit_id",
"id": 2
},
{
"rule": "optional",
"type": "fixed32",
"name": "unlock_time",
"id": 3
}
]
}
]
},
{
"name": "CMsgGCGetCommandList",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "app_id",
"id": 1
},
{
"rule": "optional",
"type": "string",
"name": "command_prefix",
"id": 2
}
]
},
{
"name": "CMsgGCGetCommandListResponse",
"fields": [
{
"rule": "repeated",
"type": "string",
"name": "command_name",
"id": 1
}
]
},
{
"name": "CGCMsgMemCachedGet",
"fields": [
{
"rule": "repeated",
"type": "string",
"name": "keys",
"id": 1
}
]
},
{
"name": "CGCMsgMemCachedGetResponse",
"fields": [
{
"rule": "repeated",
"type": "ValueTag",
"name": "values",
"id": 1
}
],
"messages": [
{
"name": "ValueTag",
"fields": [
{
"rule": "optional",
"type": "bool",
"name": "found",
"id": 1
},
{
"rule": "optional",
"type": "bytes",
"name": "value",
"id": 2
}
]
}
]
},
{
"name": "CGCMsgMemCachedSet",
"fields": [
{
"rule": "repeated",
"type": "KeyPair",
"name": "keys",
"id": 1
}
],
"messages": [
{
"name": "KeyPair",
"fields": [
{
"rule": "optional",
"type": "string",
"name": "name",
"id": 1
},
{
"rule": "optional",
"type": "bytes",
"name": "value",
"id": 2
}
]
}
]
},
{
"name": "CGCMsgMemCachedDelete",
"fields": [
{
"rule": "repeated",
"type": "string",
"name": "keys",
"id": 1
}
]
},
{
"name": "CGCMsgMemCachedStats",
"fields": []
},
{
"name": "CGCMsgMemCachedStatsResponse",
"fields": [
{
"rule": "optional",
"type": "uint64",
"name": "curr_connections",
"id": 1
},
{
"rule": "optional",
"type": "uint64",
"name": "cmd_get",
"id": 2
},
{
"rule": "optional",
"type": "uint64",
"name": "cmd_set",
"id": 3
},
{
"rule": "optional",
"type": "uint64",
"name": "cmd_flush",
"id": 4
},
{
"rule": "optional",
"type": "uint64",
"name": "get_hits",
"id": 5
},
{
"rule": "optional",
"type": "uint64",
"name": "get_misses",
"id": 6
},
{
"rule": "optional",
"type": "uint64",
"name": "delete_hits",
"id": 7
},
{
"rule": "optional",
"type": "uint64",
"name": "delete_misses",
"id": 8
},
{
"rule": "optional",
"type": "uint64",
"name": "bytes_read",
"id": 9
},
{
"rule": "optional",
"type": "uint64",
"name": "bytes_written",
"id": 10
},
{
"rule": "optional",
"type": "uint64",
"name": "limit_maxbytes",
"id": 11
},
{
"rule": "optional",
"type": "uint64",
"name": "curr_items",
"id": 12
},
{
"rule": "optional",
"type": "uint64",
"name": "evictions",
"id": 13
},
{
"rule": "optional",
"type": "uint64",
"name": "bytes",
"id": 14
}
]
},
{
"name": "CGCMsgSQLStats",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "schema_catalog",
"id": 1
}
]
},
{
"name": "CGCMsgSQLStatsResponse",
"fields": [
{
"rule": "optional",
"type": "uint32",
"name": "threads",
"id": 1
},
{
"rule": "optional",
"type": "uint32",
"name": "threads_connected",
"id": 2
},
{
"rule": "optional",
"type": "uint32",
"name": "threads_active",
"id": 3
},
{
"rule": "optional",
"type": "uint32",
"name": "operations_su