mirror of
https://github.com/demostf/demo.js
synced 2026-06-04 00:54:14 +02:00
code organization and some es6
This commit is contained in:
parent
8e286a17b1
commit
653bd862bd
34 changed files with 471 additions and 417 deletions
20
src/Data/Entity.js
Normal file
20
src/Data/Entity.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export class Entity {
|
||||
constructor(serverClass, sentTable, entityIndex, serialNumber) {
|
||||
this.serverClass = serverClass;
|
||||
this.sendTable = sentTable;
|
||||
this.entityIndex = entityIndex;
|
||||
this.serialNumber = serialNumber;
|
||||
this.props = [];
|
||||
this.inPVS = false;
|
||||
}
|
||||
|
||||
getPropByDefinition(definition) {
|
||||
for (let i = 0; i < this.props; i++) {
|
||||
if (this.props[i].definition === definition) {
|
||||
return this.props[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
15
src/Data/SendProp.js
Normal file
15
src/Data/SendProp.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import clone from 'clone';
|
||||
|
||||
export class SentProp {
|
||||
constructor(definition) {
|
||||
this.definition = definition;
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
clone() {
|
||||
const prop = new SentProp(this.definition);
|
||||
prop.value = clone(this.value);
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
90
src/Data/SendPropDefinition.js
Normal file
90
src/Data/SendPropDefinition.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
export class SendPropDefinition {
|
||||
constructor(type, name, flags) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.flags = flags;
|
||||
this.excludeDTName = null;
|
||||
this.lowValue = 0;
|
||||
this.highValue = 0;
|
||||
this.bitCount = 0;
|
||||
this.table = null;
|
||||
this.numElements = null;
|
||||
this.arrayProperty = null;
|
||||
}
|
||||
|
||||
hasFlag(flag) {
|
||||
return (this.flags & flag) != 0;
|
||||
}
|
||||
|
||||
isExcludeProp() {
|
||||
return this.hasFlag(SendPropDefinition.flags.SPROP_EXCLUDE);
|
||||
}
|
||||
|
||||
inspect() {
|
||||
return {
|
||||
name : this.name,
|
||||
type : SendPropDefinition.formatType(this.type),
|
||||
flags: SendPropDefinition.formatFlags(this.flags)
|
||||
}
|
||||
}
|
||||
|
||||
static formatFlags(flags) {
|
||||
let names = [];
|
||||
for (const name in SendPropDefinition.flags) {
|
||||
if (SendPropDefinition.flags.hasOwnProperty(name)) {
|
||||
if (flags & SendPropDefinition.flags[name]) {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
static formatType(type) {
|
||||
for (let name in SendPropDefinition.types) {
|
||||
if (SendPropDefinition.types.hasOwnProperty(name)) {
|
||||
if (SendPropDefinition.types[name] === type) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
SendPropDefinition.types = {
|
||||
DPT_Int : 0,
|
||||
DPT_Float : 1,
|
||||
DPT_Vector : 2,
|
||||
DPT_VectorXY : 3,// Only encodes the XY of a vector, ignores Z
|
||||
DPT_String : 4,
|
||||
DPT_Array : 5,
|
||||
DPT_DataTable : 6,
|
||||
DPT_NUMSendPropTypes: 7
|
||||
};
|
||||
|
||||
SendPropDefinition.flags = {
|
||||
SPROP_UNSIGNED : (1 << 0),// Unsigned integer data.
|
||||
SPROP_COORD : (1 << 1),// If this is set, the float/vector is treated like a world coordinate.
|
||||
// Note that the bit count is ignored in this case.
|
||||
SPROP_NOSCALE : (1 << 2),// For floating point, don't scale into range, just take value as is.
|
||||
SPROP_ROUNDDOWN : (1 << 3),// For floating point, limit high value to range minus one bit unit
|
||||
SPROP_ROUNDUP : (1 << 4),// For floating point, limit low value to range minus one bit unit
|
||||
SPROP_NORMAL : (1 << 5),// If this is set, the vector is treated like a normal (only valid for vectors)
|
||||
SPROP_EXCLUDE : (1 << 6),// This is an exclude prop (not excludED, but it points at another prop to be excluded).
|
||||
SPROP_XYZE : (1 << 7),// Use XYZ/Exponent encoding for vectors.
|
||||
SPROP_INSIDEARRAY : (1 << 8),// This tells us that the property is inside an array, so it shouldn't be put into the
|
||||
// flattened property list. Its array will point at it when it needs to.
|
||||
SPROP_PROXY_ALWAYS_YES : (1 << 9),// Set for datatable props using one of the default datatable proxies like
|
||||
// SendProxy_DataTableToDataTable that always send the data to all clients.
|
||||
SPROP_CHANGES_OFTEN : (1 << 10),// this is an often changed field, moved to head of sendtable so it gets a small index
|
||||
SPROP_IS_A_VECTOR_ELEM : (1 << 11),// Set automatically if SPROP_VECTORELEM is used.
|
||||
SPROP_COLLAPSIBLE : (1 << 12),// Set automatically if it's a datatable with an offset of 0 that doesn't change the pointer
|
||||
// (ie: for all automatically-chained base classes).
|
||||
// In this case, it can get rid of this SendPropDataTable altogether and spare the
|
||||
// trouble of walking the hierarchy more than necessary.
|
||||
SPROP_COORD_MP : (1 << 13),// Like SPROP_COORD, but special handling for multiplayer games
|
||||
SPROP_COORD_MP_LOWPRECISION: (1 << 14),// Like SPROP_COORD, but special handling for multiplayer games where the fractional component only gets a 3 bits instead of 5
|
||||
SPROP_COORD_MP_INTEGRAL : (1 << 15),// SPROP_COORD_MP, but coordinates are rounded to integral boundaries
|
||||
SPROP_VARINT : (1 << 5) //reuse normal
|
||||
};
|
||||
70
src/Data/SendTable.js
Normal file
70
src/Data/SendTable.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import {SendPropDefinition} from './SendPropDefinition';
|
||||
|
||||
export class SendTable {
|
||||
constructor(name) {
|
||||
this.name = name;
|
||||
this.props = [];
|
||||
this._flattenedProps = [];
|
||||
}
|
||||
|
||||
addProp(prop) {
|
||||
this.props.push(prop);
|
||||
}
|
||||
|
||||
flatten() {
|
||||
let excludes = [];
|
||||
let props = [];
|
||||
this.getAllProps(excludes, props);
|
||||
|
||||
// sort often changed props before the others
|
||||
let start = 0;
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
if (props[i].hasFlag(SendPropDefinition.flags.SPROP_CHANGES_OFTEN)) {
|
||||
if (i != start) {
|
||||
const temp = props[i];
|
||||
props[i] = props[start];
|
||||
props[start] = temp;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
}
|
||||
this._flattenedProps = props;
|
||||
}
|
||||
|
||||
getAllProps(excludes, props) {
|
||||
let localProps = [];
|
||||
this.getAllPropsIteratorProps(excludes, localProps, props);
|
||||
for (let i = 0; i < localProps.length; i++) {
|
||||
props.push(localProps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
getAllPropsIteratorProps(excludes, props, childProps) {
|
||||
for (let i = 0; i < this.props.length; i++) {
|
||||
const prop = this.props[i];
|
||||
if (prop.type === SendPropDefinition.types.DPT_DataTable) {
|
||||
if (prop.hasFlag(SendPropDefinition.flags.SPROP_EXCLUDE)) {
|
||||
excludes.push(prop.table);
|
||||
} else if (excludes.indexOf(this) === -1) {
|
||||
if (prop.hasFlag(SendPropDefinition.flags.SPROP_COLLAPSIBLE)) {
|
||||
prop.table.getAllPropsIteratorProps(excludes, props, childProps);
|
||||
} else {
|
||||
prop.table.getAllProps(excludes, childProps);
|
||||
}
|
||||
}
|
||||
} else if (!prop.hasFlag(SendPropDefinition.flags.SPROP_EXCLUDE)) {
|
||||
props.push(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get flattenedProps(){
|
||||
if (this._flattenedProps.length === 0) {
|
||||
this.flatten();
|
||||
}
|
||||
return this._flattenedProps;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
7
src/Data/Vector.js
Normal file
7
src/Data/Vector.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export class Vector {
|
||||
constructor(x, y, z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
}
|
||||
135
src/Parser/SendPropParser.js
Normal file
135
src/Parser/SendPropParser.js
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import {SendPropDefinition} from '../Data/SendPropDefinition';
|
||||
import {Vector} from "../Data/Vector";
|
||||
|
||||
|
||||
const readBitVar = function (stream, signed) {
|
||||
switch (stream.readBits(2)) {
|
||||
case 0:
|
||||
return stream.readBits(4, signed);
|
||||
case 1:
|
||||
return stream.readBits(8, signed);
|
||||
case 2:
|
||||
return stream.readBits(12, signed);
|
||||
case 3:
|
||||
return stream.readBits(32, signed);
|
||||
}
|
||||
};
|
||||
|
||||
export class SendPropParser {
|
||||
static decode(propDefinition, stream) {
|
||||
switch (propDefinition.type) {
|
||||
case SendPropDefinition.types.DPT_Int:
|
||||
return SendPropParser.readInt(propDefinition, stream);
|
||||
case SendPropDefinition.types.DPT_Vector:
|
||||
return SendPropParser.readVector(propDefinition, stream);
|
||||
case SendPropDefinition.types.DPT_VectorXY:
|
||||
return SendPropParser.readVectorXY(propDefinition, stream);
|
||||
case SendPropDefinition.types.DPT_Float:
|
||||
return SendPropParser.readFloat(propDefinition, stream);
|
||||
case SendPropDefinition.types.DPT_String:
|
||||
return SendPropParser.readString(stream);
|
||||
case SendPropDefinition.types.DPT_Array:
|
||||
return SendPropParser.readArray(propDefinition, stream);
|
||||
}
|
||||
}
|
||||
|
||||
static readInt(propDefinition, stream) {
|
||||
if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_VARINT)) {
|
||||
return readBitVar(stream, !propDefinition.hasFlag(SendPropDefinition.flags.SPROP_UNSIGNED));
|
||||
} else {
|
||||
return stream.readBits(propDefinition.bitCount, !propDefinition.hasFlag(SendPropDefinition.flags.SPROP_UNSIGNED));
|
||||
}
|
||||
}
|
||||
|
||||
static readArray(propDefinition, stream) {
|
||||
let maxElements = propDefinition.numElements;
|
||||
let numBits = 1;
|
||||
while ((maxElements >>= 1) != 0)
|
||||
numBits++;
|
||||
|
||||
const count = stream.readBits(numBits);
|
||||
const values = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
values.push(SendPropParser.decode(propDefinition.arrayProperty, stream));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static readString(stream) {
|
||||
const length = stream.readBits(9);
|
||||
return stream.readASCIIString(length);
|
||||
}
|
||||
|
||||
static readVector(propDefinition, stream) {
|
||||
const x = SendPropParser.readFloat(propDefinition, stream);
|
||||
const y = SendPropParser.readFloat(propDefinition, stream);
|
||||
const z = (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_NORMAL)) ? SendPropParser.readFloat(propDefinition, stream) : 0;
|
||||
return new Vector(x, y, z);
|
||||
}
|
||||
|
||||
static readVectorXY(propDefinition, stream) {
|
||||
const x = SendPropParser.readFloat(propDefinition, stream);
|
||||
const y = SendPropParser.readFloat(propDefinition, stream);
|
||||
return new Vector(x, y, 0);
|
||||
}
|
||||
|
||||
static readFloat(propDefinition, stream) {
|
||||
if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_COORD)) {
|
||||
throw new Error("not implemented");
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_COORD_MP)) {
|
||||
return SendPropParser.readBitCoord(stream, false, false);
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_COORD_MP_LOWPRECISION)) {
|
||||
return SendPropParser.readBitCoord(stream, false, true);
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_COORD_MP_INTEGRAL)) {
|
||||
return SendPropParser.readBitCoord(stream, true, false);
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_NOSCALE)) {
|
||||
return stream.readFloat32();
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_NORMAL)) {
|
||||
throw new Error("not implemented");
|
||||
} else {
|
||||
const raw = stream.readBits(propDefinition.bitCount);
|
||||
const percentage = raw / ((1 << propDefinition.bitCount) - 1);
|
||||
return propDefinition.lowValue + (propDefinition.highValue - propDefinition.lowValue) * percentage;
|
||||
}
|
||||
}
|
||||
|
||||
static readBitCoord(stream, isIntegral, isLowPrecision) {
|
||||
let value = 0;
|
||||
let isNegative = false;
|
||||
const inBounds = stream.readBoolean();
|
||||
|
||||
const hasIntVal = stream.readBoolean();
|
||||
if (isIntegral) {
|
||||
if (hasIntVal) {
|
||||
isNegative = stream.readBoolean();
|
||||
|
||||
if (inBounds) {
|
||||
value = stream.readBits(11) + 1;
|
||||
} else {
|
||||
value = stream.readBits(14) + 1;
|
||||
if (value < (1 << 11)) {
|
||||
throw new Error("Something's fishy...");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isNegative = stream.readBoolean();
|
||||
if (hasIntVal) {
|
||||
if (inBounds) {
|
||||
value = stream.readBits(11) + 1;
|
||||
} else {
|
||||
value = stream.readBits(14) + 1;
|
||||
if (value < (1 << 11)) {
|
||||
throw new Error("Something's fishy...");
|
||||
}
|
||||
}
|
||||
}
|
||||
const fractalVal = stream.readBits(isLowPrecision ? 3 : 5);
|
||||
value += fractalVal * (1 / (1 << (isLowPrecision ? 3 : 5)));
|
||||
}
|
||||
if (isNegative) {
|
||||
value = -value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
89
src/StreamParser.js
Normal file
89
src/StreamParser.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
var util = require('util');
|
||||
var Parser = require('./parser');
|
||||
var BitStream = require('bit-buffer').BitStream;
|
||||
|
||||
var StreamParser = function (stream) {
|
||||
this.stream = stream;
|
||||
this.match = new Match();
|
||||
this.on('packet', this, match.handlePacket.bind(this.match));
|
||||
this.header = null;
|
||||
this.buffer = new Buffer(0);
|
||||
};
|
||||
|
||||
util.inherits(StreamParser, Parser);
|
||||
|
||||
function shrinkBuffer(buffer, length) {
|
||||
if (length < 0) {
|
||||
throw 'cant shrink by negative length ' + length;
|
||||
}
|
||||
return buffer.slice(length, buffer.length);
|
||||
}
|
||||
|
||||
StreamParser.prototype.eatBuffer = function (length) {
|
||||
this.buffer = shrinkBuffer(this.buffer, length);
|
||||
};
|
||||
|
||||
StreamParser.prototype.start = function () {
|
||||
this.stream.on('data', this.handleData.bind(this));
|
||||
this.stream.on('end', function () {
|
||||
this.emit('done', this.match);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
StreamParser.prototype.handleData = function (data) {
|
||||
this.buffer = Buffer.concat([this.buffer, data]);
|
||||
if (this.header === null) {
|
||||
if (this.buffer.length > 1072) {
|
||||
this.header = this.parseHeader(new BitStream(this.buffer));
|
||||
this.eatBuffer(1072);
|
||||
}
|
||||
} else {
|
||||
this.readMessage();
|
||||
}
|
||||
};
|
||||
|
||||
StreamParser.prototype.readMessage = function () {
|
||||
if (this.buffer.length < 9) { // 9 byte minimum message header (type, tick, length)
|
||||
return;
|
||||
}
|
||||
var stream = new BitStream(this.buffer);
|
||||
var type = stream.readBits(8);
|
||||
if (type === Parser.MessageType.Stop) {
|
||||
console.log('stop');
|
||||
return;
|
||||
}
|
||||
var tick = stream.readInt32();
|
||||
|
||||
var headerSize = 5;
|
||||
var extraHeader = 0;
|
||||
|
||||
switch (type) {
|
||||
case Parser.MessageType.Sigon:
|
||||
case Parser.MessageType.Packet:
|
||||
extraHeader += 0x54; // command/sequence info
|
||||
break;
|
||||
case Parser.MessageType.UserCmd:
|
||||
extraHeader += 0x04; // unknown / outgoing sequence
|
||||
break;
|
||||
case Parser.MessageType.Stop:
|
||||
case Parser.MessageType.SyncTick:
|
||||
this.eatBuffer(headerSize);
|
||||
return;
|
||||
}
|
||||
stream.byteIndex += extraHeader;
|
||||
var length = stream.readInt32();
|
||||
headerSize += extraHeader + 4;
|
||||
|
||||
if (this.buffer.length < (headerSize + length)) {
|
||||
console.log('wants ' + length);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('got message ' + tick);
|
||||
var messageBuffer = this.buffer.slice(headerSize, headerSize + length);
|
||||
this.eatBuffer(headerSize + length);
|
||||
var message = this.parseMessage(messageBuffer, type, tick, length);
|
||||
this.handleMessage(message);
|
||||
};
|
||||
|
||||
module.exports = StreamParser;
|
||||
15
src/consolecmd.js
Normal file
15
src/consolecmd.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
var ConsoleCmd = function (type, tick, stream, length, match) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
this.match = match;
|
||||
};
|
||||
|
||||
ConsoleCmd.prototype.parse = function () {
|
||||
var cmd = this.stream.readUTF8String();
|
||||
//console.log("cmd " + cmd);
|
||||
return cmd;
|
||||
};
|
||||
|
||||
module.exports = ConsoleCmd;
|
||||
113
src/datatable.js
Normal file
113
src/datatable.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import {SendTable} from './Data/SendTable';
|
||||
import {SendPropDefinition} from './Data/SendPropDefinition';
|
||||
|
||||
var DataTableParser = function (type, tick, stream, length, match) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
this.match = match;
|
||||
};
|
||||
|
||||
DataTableParser.prototype.parse = function () {
|
||||
// https://github.com/LestaD/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/engine/dt_common_eng.cpp#L356
|
||||
// https://github.com/LestaD/SourceEngine2007/blob/43a5c90a5ada1e69ca044595383be67f40b33c61/src_main/engine/dt_recv_eng.cpp#L310
|
||||
// https://github.com/PazerOP/DemoLib/blob/master/DemoLib/Commands/DemoDataTablesCommand.cs
|
||||
var tables = [];
|
||||
var i, j;
|
||||
while (this.stream.readBoolean()) {
|
||||
var needsDecoder = this.stream.readBoolean();
|
||||
var tableName = this.stream.readASCIIString();
|
||||
var numProps = this.stream.readBits(10);
|
||||
var table = new SendTable(tableName);
|
||||
|
||||
// get props metadata
|
||||
var arrayElementProp;
|
||||
for (i = 0; i < numProps; i++) {
|
||||
var propType = this.stream.readBits(5);
|
||||
var propName = this.stream.readASCIIString();
|
||||
var nFlagsBits = 16; // might be 11 (old?), 13 (new?), 16(networked) or 17(??)
|
||||
var flags = this.stream.readBits(nFlagsBits);
|
||||
var prop = new SendPropDefinition(propType, propName, flags);
|
||||
if (propType === SendPropDefinition.types.DPT_DataTable) {
|
||||
prop.excludeDTName = this.stream.readASCIIString();
|
||||
} else {
|
||||
if (prop.isExcludeProp()) {
|
||||
prop.excludeDTName = this.stream.readASCIIString();
|
||||
} else if (prop.type === SendPropDefinition.types.DPT_Array) {
|
||||
prop.numElements = this.stream.readBits(10);
|
||||
} else {
|
||||
prop.lowValue = this.stream.readFloat32();
|
||||
prop.highValue = this.stream.readFloat32();
|
||||
prop.bitCount = this.stream.readBits(7);
|
||||
}
|
||||
}
|
||||
|
||||
if (prop.hasFlag(SendPropDefinition.flags.SPROP_NOSCALE)) {
|
||||
if (prop.type === SendPropDefinition.types.DPT_Float) {
|
||||
prop.bitCount = 32;
|
||||
} else if (prop.type === SendPropDefinition.types.DPT_Vector) {
|
||||
if (!prop.hasFlag(SendPropDefinition.flags.SPROP_NORMAL)) {
|
||||
prop.bitCount = 32 * 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (arrayElementProp) {
|
||||
if (!prop.type === SendPropDefinition.types.DPT_Array) {
|
||||
throw "expected prop of type array";
|
||||
}
|
||||
prop.arrayProperty = arrayElementProp;
|
||||
arrayElementProp = null;
|
||||
}
|
||||
|
||||
if (prop.hasFlag(SendPropDefinition.flags.SPROP_INSIDEARRAY)) {
|
||||
arrayElementProp = prop;
|
||||
} else {
|
||||
table.addProp(prop);
|
||||
}
|
||||
}
|
||||
tables.push(table);
|
||||
}
|
||||
this.match.sendTables = tables;
|
||||
|
||||
// link referenced tables
|
||||
for (i = 0; i < tables.length; i++) {
|
||||
for (j = 0; j < tables[i].props.length; j++) {
|
||||
if (tables[i].props[j].type === SendPropDefinition.types.DPT_DataTable) {
|
||||
tables[i].props[j].table = this.match.getSendTable(tables[i].props[j].excludeDTName);
|
||||
tables[i].props[j].excludeDTName = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var serverClasses = this.stream.readUint16(); // short
|
||||
if (serverClasses <= 0) {
|
||||
throw "expected one or more serverclasses";
|
||||
}
|
||||
|
||||
for (i = 0; i < serverClasses; i++) {
|
||||
var classId = this.stream.readUint16();
|
||||
if (classId > serverClasses) {
|
||||
throw "invalid class id";
|
||||
}
|
||||
var className = this.stream.readASCIIString();
|
||||
var dataTable = this.stream.readASCIIString();
|
||||
this.match.serverClasses.push(new ServerClass(classId, className, dataTable));
|
||||
}
|
||||
|
||||
var bitsLeft = (this.length * 8) - this.stream._index;
|
||||
if (bitsLeft > 7) {
|
||||
throw "unexpected remaining data in datatable (" + bitsLeft + " bits)";
|
||||
}
|
||||
|
||||
return tables;
|
||||
};
|
||||
|
||||
var ServerClass = function (id, name, dataTable) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.dataTable = dataTable;
|
||||
};
|
||||
|
||||
module.exports = DataTableParser;
|
||||
43
src/demo.js
Normal file
43
src/demo.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
var BitStream = require('bit-buffer').BitStream;
|
||||
var Parser = require('./parser');
|
||||
var StreamParser = require('./StreamParser');
|
||||
|
||||
var Demo = function (arrayBuffer) {
|
||||
this.stream = new BitStream(arrayBuffer);
|
||||
};
|
||||
|
||||
Demo.prototype.getParser = function () {
|
||||
return new Parser(this.stream);
|
||||
};
|
||||
|
||||
var StreamDemo = function (nodeStream) {
|
||||
this.stream = nodeStream;
|
||||
};
|
||||
|
||||
StreamDemo.prototype.getParser = function () {
|
||||
return new StreamParser(this.stream);
|
||||
};
|
||||
|
||||
Demo.fromNodeBuffer = function (nodeBuffer) {
|
||||
var arrayBuffer = new ArrayBuffer(nodeBuffer.length);
|
||||
var view = new Uint8Array(arrayBuffer);
|
||||
for (var i = 0; i < nodeBuffer.length; ++i) {
|
||||
view[i] = nodeBuffer[i];
|
||||
}
|
||||
return new Demo(arrayBuffer);
|
||||
};
|
||||
|
||||
Demo.fromNodeStream = function (nodeStream) {
|
||||
return new StreamDemo(nodeStream);
|
||||
};
|
||||
|
||||
Demo.fromPath = function (path) {
|
||||
var arrayBuffer = new ArrayBuffer(nodeBuffer.length);
|
||||
var view = new Uint8Array(arrayBuffer);
|
||||
for (var i = 0; i < nodeBuffer.length; ++i) {
|
||||
view[i] = nodeBuffer[i];
|
||||
}
|
||||
return new Demo(arrayBuffer);
|
||||
};
|
||||
|
||||
module.exports = Demo;
|
||||
47
src/handlers/packet/bspDecal.js
Normal file
47
src/handlers/packet/bspDecal.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
var getCoord = function (stream) {
|
||||
var hasInt = !!stream.readBits(1);
|
||||
var hasFract = !!stream.readBits(1);
|
||||
var value = 0;
|
||||
if (hasInt || hasFract) {
|
||||
var sign = !!stream.readBits(1);
|
||||
if (hasInt) {
|
||||
value += stream.readBits(14) + 1;
|
||||
}
|
||||
if (hasFract) {
|
||||
value += stream.readBits(5) * (1 / 32);
|
||||
}
|
||||
if (sign) {
|
||||
value = -value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
var getVecCoord = function (stream) {
|
||||
var hasX = !!stream.readBits(1);
|
||||
var hasY = !!stream.readBits(1);
|
||||
var hasZ = !!stream.readBits(1);
|
||||
return {
|
||||
x: hasX ? getCoord(stream) : 0,
|
||||
y: hasY ? getCoord(stream) : 0,
|
||||
z: hasZ ? getCoord(stream) : 0
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = function (stream) { // 21: BSPDecal
|
||||
var position = getVecCoord(stream);
|
||||
var textureIndex = stream.readBits(9);
|
||||
if (stream.readBits(1)) {
|
||||
var entIndex = stream.readBits(11);
|
||||
var modelIndex = stream.readBits(12);
|
||||
}
|
||||
var lowPriority = !!stream.readBits(1);
|
||||
return {
|
||||
packetType : 'BSPDecal',
|
||||
position : position,
|
||||
textureIndex: textureIndex,
|
||||
entIndex : entIndex,
|
||||
modelIndex : modelIndex,
|
||||
lowPriority : lowPriority
|
||||
}
|
||||
};
|
||||
30
src/handlers/packet/classInfo.js
Normal file
30
src/handlers/packet/classInfo.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
function logBase2(num) {
|
||||
var result = 0;
|
||||
while ((num >>= 1) != 0) {
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = function (stream) { // 10: classInfo
|
||||
var number = stream.readBits(16);
|
||||
var create = !!stream.readBits(1);
|
||||
var entries = [];
|
||||
if (!create) {
|
||||
var bits = logBase2(number) + 1;
|
||||
for (var i = 0; i < number; i++) {
|
||||
var entry = {
|
||||
'classId' : stream.readBits(bits),
|
||||
'className' : stream.readASCIIString(),
|
||||
'dataTableName': stream.readASCIIString()
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
return {
|
||||
'packetType': 'classInfo',
|
||||
number : number,
|
||||
create : create,
|
||||
entries : entries
|
||||
}
|
||||
};
|
||||
10
src/handlers/packet/createStringTable.js
Normal file
10
src/handlers/packet/createStringTable.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
var PacketStringTable = require('../../packetstringtable');
|
||||
|
||||
module.exports = function (stream) { // 12: createStringTable
|
||||
var stringTable = new PacketStringTable(stream);
|
||||
var tables = stringTable.parse();
|
||||
return {
|
||||
packetType: 'createStringTable',
|
||||
table : tables
|
||||
};
|
||||
};
|
||||
9
src/handlers/packet/entityMessage.js
Normal file
9
src/handlers/packet/entityMessage.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
var ParserGenerator = require('../../parsergenerator');
|
||||
|
||||
var baseParser = ParserGenerator.make('entityMessage', 'index{11}classId{9}length{11}data{$length}');
|
||||
|
||||
module.exports = function (stream) { // 24: entityMessage
|
||||
var data = baseParser(stream); //todo parse data further?
|
||||
// console.log(data.index);
|
||||
return data;
|
||||
};
|
||||
50
src/handlers/packet/gameEvent.js
Normal file
50
src/handlers/packet/gameEvent.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
var parseGameEvent = function (eventId, stream, events) {
|
||||
if (!events[eventId]) {
|
||||
return 'unknown';
|
||||
}
|
||||
var eventDescription = events[eventId];
|
||||
var values = {};
|
||||
for (var i = 0; i < eventDescription.entries.length; i++) {
|
||||
var entry = eventDescription.entries[i];
|
||||
values[entry.name] = getGameEventValue(stream, entry);
|
||||
}
|
||||
return {
|
||||
name : eventDescription.name,
|
||||
type : eventDescription.type,
|
||||
values: values
|
||||
};
|
||||
};
|
||||
|
||||
var getGameEventValue = function (stream, entry) {
|
||||
switch (entry.type) {
|
||||
case 1:
|
||||
return stream.readUTF8String();
|
||||
case 2:
|
||||
return stream.readFloat32();
|
||||
case 3:
|
||||
return stream.readInt32();
|
||||
case 4:
|
||||
return stream.readBits(16);
|
||||
case 5:
|
||||
return stream.readBits(8);
|
||||
case 6:
|
||||
return !!stream.readBits(1);
|
||||
case 7:
|
||||
return 'local value';
|
||||
default:
|
||||
throw 'invalid game event type';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = function (stream, events) { // 25: game event
|
||||
var length = stream.readBits(11);
|
||||
var end = stream._index + length;
|
||||
var eventId = stream.readBits(9);
|
||||
var event = parseGameEvent(eventId, stream, events);
|
||||
stream._index = end;
|
||||
return {
|
||||
packetType: 'gameEvent',
|
||||
event : event
|
||||
}
|
||||
};
|
||||
29
src/handlers/packet/gameEventList.js
Normal file
29
src/handlers/packet/gameEventList.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
module.exports = function (stream, events) { // 30: gameEventList
|
||||
// list of game events and parameters
|
||||
var numEvents = stream.readBits(9);
|
||||
var length = stream.readBits(20);
|
||||
for (var i = 0; i < numEvents; i++) {
|
||||
var id = stream.readBits(9);
|
||||
var name = stream.readASCIIString();
|
||||
var type = stream.readBits(3);
|
||||
var entries = [];
|
||||
while (type !== 0) {
|
||||
var entryName = stream.readASCIIString();
|
||||
entries.push({
|
||||
type: type,
|
||||
name: entryName
|
||||
});
|
||||
type = stream.readBits(3);
|
||||
}
|
||||
events[id] = {
|
||||
id : id,
|
||||
name : name,
|
||||
type : type,
|
||||
entries: entries
|
||||
};
|
||||
}
|
||||
return {
|
||||
packetType: 'gameEventList',
|
||||
events : events
|
||||
}
|
||||
};
|
||||
195
src/handlers/packet/packetEntities.js
Normal file
195
src/handlers/packet/packetEntities.js
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
import {SendPropParser} from '../../Parser/SendPropParser';
|
||||
import {Entity} from '../../Data/Entity';
|
||||
import {SentProp} from '../../Data/SendProp';
|
||||
|
||||
var PVS = {
|
||||
PRESERVE: 0,
|
||||
ENTER : 1,
|
||||
LEAVE : 2,
|
||||
DELETE : 4
|
||||
};
|
||||
|
||||
function readPVSType(stream) {
|
||||
// https://github.com/skadistats/smoke/blob/a2954fbe2fa3936d64aee5b5567be294fef228e6/smoke/io/stream/entity.pyx#L24
|
||||
var pvs;
|
||||
var hi = stream.readBoolean();
|
||||
var low = stream.readBoolean();
|
||||
if (low && !hi) {
|
||||
pvs = PVS.ENTER;
|
||||
} else if (!(hi || low)) {
|
||||
pvs = PVS.PRESERVE;
|
||||
} else if (hi) {
|
||||
pvs = (low) ? (PVS.LEAVE | PVS.DELETE) : PVS.LEAVE;
|
||||
} else {
|
||||
pvs = -1;
|
||||
}
|
||||
return pvs;
|
||||
}
|
||||
|
||||
function readEnterPVS(stream, entityId, match, baseLine) {
|
||||
// https://github.com/PazerOP/DemoLib/blob/5f9467650f942a4a70f9ec689eadcd3e0a051956/TF2Net/NetMessages/NetPacketEntitiesMessage.cs#L198
|
||||
var serverClass = match.serverClasses[stream.readBits(match.classBits)];
|
||||
console.log(serverClass);
|
||||
var sendTable = match.getSendTable(serverClass.dataTable);
|
||||
var serialNumber = stream.readBits(10);
|
||||
|
||||
var entity = (match.entities[entityId]) ? match.entities[entityId] : new Entity(serverClass, sendTable, entityId, serialNumber);
|
||||
|
||||
var decodedBaseLine = match.instanceBaselines[baseLine][entityId];
|
||||
if (decodedBaseLine) {
|
||||
for (var i = 0; i < decodedBaseLine.length; i++) {
|
||||
var newProp = decodedBaseLine[i];
|
||||
if (!entity.getPropByDefinition(newProp.definition)) {
|
||||
entity.props.push(newProp.clone(entity));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var staticBaseLine = match.staticBaseLines[serverClass.id];
|
||||
if (staticBaseLine) {
|
||||
var streamStart = staticBaseLine._index;
|
||||
applyEntityUpdate(entity, staticBaseLine);
|
||||
staticBaseLine._index = streamStart;
|
||||
}
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
function readLeavePVS(match, entityId, shouldDelete) {
|
||||
if (shouldDelete) {
|
||||
match.entities[entityId] = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function (stream, events, entities, match) { //26: packetEntities
|
||||
// https://github.com/skadistats/smoke/blob/master/smoke/replay/handler/svc_packetentities.pyx
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Handler/PacketEntitesHandler.cs
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Entity.cs
|
||||
// https://github.com/PazerOP/DemoLib/blob/5f9467650f942a4a70f9ec689eadcd3e0a051956/TF2Net/NetMessages/NetPacketEntitiesMessage.cs
|
||||
// todo
|
||||
var maxEntries = stream.readBits(11);
|
||||
var isDelta = !!stream.readBits(1);
|
||||
if (isDelta) {
|
||||
var delta = stream.readInt32();
|
||||
} else {
|
||||
delta = null;
|
||||
}
|
||||
var baseLine = stream.readBits(1);
|
||||
var updatedEntries = stream.readBits(11);
|
||||
var length = stream.readBits(20);
|
||||
var updatedBaseLine = stream.readBoolean();
|
||||
var end = stream._index + length;
|
||||
var entityId = -1;
|
||||
|
||||
if (updatedBaseLine) {
|
||||
if (baseLine === 0) {
|
||||
match.instanceBaselines[1] = match.instanceBaselines[0];
|
||||
match.instanceBaselines[0] = new Array((1 << 11)); // array of SendPropDefinition with size MAX_EDICTS
|
||||
} else {
|
||||
match.instanceBaselines[0] = match.instanceBaselines[1];
|
||||
match.instanceBaselines[1] = new Array((1 << 11)); // array of SendPropDefinition with size MAX_EDICTS
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < updatedEntries; i++) {
|
||||
var diff = readUBitVar(stream);
|
||||
console.log(diff);
|
||||
entityId += 1 + diff;
|
||||
var pvs = readPVSType(stream);
|
||||
if (pvs === PVS.ENTER) {
|
||||
var entity = readEnterPVS(stream, entityId, match, baseLine);
|
||||
applyEntityUpdate(entity, stream);
|
||||
match.entities[entityId] = entity;
|
||||
|
||||
if (updatedBaseLine) {
|
||||
match.instanceBaselines[baseLine][entityId] = [].concat(entity.props);
|
||||
}
|
||||
entity.inPVS = true;
|
||||
} else if (pvs === PVS.PRESERVE) {
|
||||
entity = match.entities[entityId];
|
||||
if (!entity) {
|
||||
console.log(entityId, match.entities.length);
|
||||
throw new Error("unknown entity");
|
||||
}
|
||||
applyEntityUpdate(entity, stream);
|
||||
} else {
|
||||
entity = match.entities[entityId];
|
||||
if (entity) {
|
||||
entity.inPVS = false;
|
||||
}
|
||||
readLeavePVS(match, entityId, pvs === PVS.DELETE);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDelta) {
|
||||
while (stream.readBoolean()) {
|
||||
var ent = stream.readBits(11);
|
||||
match.entities[ent] = null;
|
||||
}
|
||||
}
|
||||
|
||||
stream._index = end;
|
||||
//var ent = {
|
||||
// packetType : 'packetEntities',
|
||||
// maxEntries : maxEntries,
|
||||
// isDelta : isDelta,
|
||||
// delta : delta,
|
||||
// baseLine : baseLine,
|
||||
// updatedEntries : updatedEntries,
|
||||
// length : length,
|
||||
// updatedBaseLine: updatedBaseLine
|
||||
//};
|
||||
//console.log(ent);
|
||||
//console.log(entities);
|
||||
//process.exit();
|
||||
return {
|
||||
packetType: 'packetEntities',
|
||||
entities : entities
|
||||
};
|
||||
};
|
||||
|
||||
var readFieldIndex = function (stream, lastIndex) {
|
||||
if (!stream.readBoolean()) {
|
||||
return -1;
|
||||
}
|
||||
var diff = readUBitVar(stream);
|
||||
return lastIndex + diff + 1;
|
||||
};
|
||||
|
||||
var applyEntityUpdate = function (entity, stream) {
|
||||
var index = -1;
|
||||
var allProps = entity.sendTable.flattenedProps;
|
||||
while ((index = readFieldIndex(stream, index)) != -1) {
|
||||
if (index > 4096) {
|
||||
throw new Error('prop index out of bounds');
|
||||
}
|
||||
console.log(index);
|
||||
var propDefinition = allProps[index];
|
||||
var existingProp = entity.getPropByDefinition(propDefinition);
|
||||
var prop;
|
||||
if (existingProp) {
|
||||
prop = existingProp;
|
||||
} else {
|
||||
prop = new SentProp(propDefinition);
|
||||
}
|
||||
prop.value = SendPropParser.decode(propDefinition, stream);
|
||||
console.log(prop);
|
||||
|
||||
if (!existingProp) {
|
||||
entity.props.push(prop);
|
||||
}
|
||||
}
|
||||
return entity;
|
||||
};
|
||||
|
||||
var readUBitVar = function (stream) {
|
||||
switch (stream.readBits(2)) {
|
||||
case 0:
|
||||
return stream.readBits(4);
|
||||
case 1:
|
||||
return stream.readBits(8);
|
||||
case 2:
|
||||
return stream.readBits(12);
|
||||
case 3:
|
||||
return stream.readBits(32);
|
||||
}
|
||||
};
|
||||
12
src/handlers/packet/parseSounds.js
Normal file
12
src/handlers/packet/parseSounds.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = function (stream) { // 17: parseSounds
|
||||
var reliable = !!stream.readBits(1);
|
||||
var num = (reliable) ? 1 : stream.readBits(8);
|
||||
var length = (reliable) ? stream.readBits(8) : stream.readBits(16);
|
||||
stream._index += length;
|
||||
return {
|
||||
packetType: 'parseSounds',
|
||||
reliable : reliable,
|
||||
num : num,
|
||||
length : length
|
||||
}
|
||||
};
|
||||
11
src/handlers/packet/setConVar.js
Normal file
11
src/handlers/packet/setConVar.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module.exports= function (stream) { // 5: setconvar
|
||||
var count = stream.readBits(8);
|
||||
var vars = {};
|
||||
for (var i = 0; i < count; i++) {
|
||||
vars[stream.readUTF8String()] = stream.readUTF8String();
|
||||
}
|
||||
return {
|
||||
packetType: 'setConVar',
|
||||
vars : vars
|
||||
}
|
||||
};
|
||||
10
src/handlers/packet/updateStringTable.js
Normal file
10
src/handlers/packet/updateStringTable.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
var PacketStringTable = require('../../packetstringtable');
|
||||
|
||||
module.exports = function (stream) { // 12: updateStringTable
|
||||
var stringTable = new PacketStringTable(stream);
|
||||
var tables = stringTable.parse();
|
||||
return {
|
||||
packetType: 'updateStringTable',
|
||||
table : tables
|
||||
};
|
||||
};
|
||||
83
src/handlers/packet/userMessage.js
Normal file
83
src/handlers/packet/userMessage.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
var ParserGenerator = require('../../parsergenerator');
|
||||
|
||||
var userMessageParsers = {
|
||||
4: require('../userMessage/SayText2'),
|
||||
5: ParserGenerator.make('textMsg', 'destType{8}text{s}')
|
||||
};
|
||||
|
||||
module.exports = function (stream) { // 23: user message
|
||||
var type = stream.readBits(8);
|
||||
var length = stream.readBits(11);
|
||||
var pos = stream._index;
|
||||
if (userMessageParsers[type]) {
|
||||
var result = userMessageParsers[type](stream);
|
||||
} else {
|
||||
result = {
|
||||
packetType: 'unknownUserMessage',
|
||||
type : type
|
||||
}
|
||||
}
|
||||
stream._index = pos + length;
|
||||
return result;
|
||||
};
|
||||
|
||||
var UserMessageType = {
|
||||
Geiger : 0,
|
||||
Train : 1,
|
||||
HudText : 2,
|
||||
SayText : 3,
|
||||
SayText2 : 4,
|
||||
TextMsg : 5,
|
||||
ResetHUD : 6,
|
||||
GameTitle : 7,
|
||||
ItemPickup : 8,
|
||||
ShowMenu : 9,
|
||||
Shake : 10,
|
||||
Fade : 11,
|
||||
VGUIMenu : 12,
|
||||
Rumble : 13,
|
||||
CloseCaption : 14,
|
||||
SendAudio : 15,
|
||||
VoiceMask : 16,
|
||||
RequestState : 17,
|
||||
Damage : 18,
|
||||
HintText : 19,
|
||||
KeyHintText : 20,
|
||||
HudMsg : 21,
|
||||
AmmoDenied : 22,
|
||||
AchievementEvent : 23,
|
||||
UpdateRadar : 24,
|
||||
VoiceSubtitle : 25,
|
||||
HudNotify : 26,
|
||||
HudNotifyCustom : 27,
|
||||
PlayerStatsUpdate : 28,
|
||||
PlayerIgnited : 29,
|
||||
PlayerIgnitedInv : 30,
|
||||
HudArenaNotify : 31,
|
||||
UpdateAchievement : 32,
|
||||
TrainingMsg : 33,
|
||||
TrainingObjective : 34,
|
||||
DamageDodged : 35,
|
||||
PlayerJarated : 36,
|
||||
PlayerExtinguished : 37,
|
||||
PlayerJaratedFade : 38,
|
||||
PlayerShieldBlocked: 39,
|
||||
BreakModel : 40,
|
||||
CheapBreakModel : 41,
|
||||
BreakModel_Pumpkin : 42,
|
||||
BreakModelRocketDud: 43,
|
||||
CallVoteFailed : 44,
|
||||
VoteStart : 45,
|
||||
VotePass : 46,
|
||||
VoteFailed : 47,
|
||||
VoteSetup : 48,
|
||||
PlayerBonusPoints : 49,
|
||||
SpawnFlyingBird : 50,
|
||||
PlayerGodRayEffect : 51,
|
||||
SPHapWeapEvent : 52,
|
||||
HapDmg : 53,
|
||||
HapPunch : 54,
|
||||
HapSetDrag : 55,
|
||||
HapSet : 56,
|
||||
HapMeleeContact : 57
|
||||
};
|
||||
44
src/handlers/userMessage/SayText2.js
Normal file
44
src/handlers/userMessage/SayText2.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
module.exports = function (stream) { // 4: SayText2
|
||||
var client = stream.readBits(8);
|
||||
var raw = stream.readBits(8);
|
||||
var pos = stream._index;
|
||||
var from, text, kind, arg1, arg2;
|
||||
if (stream.readBits(8) === 1) {
|
||||
var first = stream.readBits(8);
|
||||
if (first === 7) {
|
||||
var color = stream.readUTF8String(6);
|
||||
} else {
|
||||
stream._index = pos + 8;
|
||||
}
|
||||
text = stream.readUTF8String();
|
||||
if (text.substr(0, 6) === '*DEAD*') {
|
||||
// grave talk is in the format '*DEAD* \u0003$from\u0001: $text'
|
||||
var start = text.indexOf('\u0003');
|
||||
var end = text.indexOf('\u0001');
|
||||
from = text.substr(start + 1, end - start - 1);
|
||||
text = text.substr(end + 5);
|
||||
kind = 'TF_Chat_AllDead';
|
||||
}
|
||||
} else {
|
||||
stream._index = pos;
|
||||
kind = stream.readUTF8String();
|
||||
from = stream.readUTF8String();
|
||||
text = stream.readUTF8String();
|
||||
stream.readASCIIString();
|
||||
stream.readASCIIString();
|
||||
}
|
||||
// cleanup color codes
|
||||
text = text.replace(/\u0001/g, '');
|
||||
text = text.replace(/\u0003/g, '');
|
||||
while ((pos = text.indexOf('\u0007')) !== -1) {
|
||||
text = text.slice(0, pos) + text.slice(pos + 7);
|
||||
}
|
||||
return {
|
||||
packetType: 'sayText2',
|
||||
client : client,
|
||||
raw : raw,
|
||||
kind : kind,
|
||||
from : from,
|
||||
text : text
|
||||
}
|
||||
};
|
||||
152
src/match.js
Normal file
152
src/match.js
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
var Match = function () {
|
||||
this.tick = 0;
|
||||
this.chat = [];
|
||||
this.users = {};
|
||||
this.deaths = [];
|
||||
this.rounds = [];
|
||||
this.startTick = 0;
|
||||
this.intervalPerTick = 0;
|
||||
this.entities = [];
|
||||
this.stringTables = [];
|
||||
this.sendTables = [];
|
||||
this.serverClasses = [];
|
||||
this.entities = [];
|
||||
this.instanceBaselines = [[],[]];
|
||||
this.staticBaseLines = [];
|
||||
};
|
||||
|
||||
Match.prototype.getSendTable = function (name) {
|
||||
for (var i = 0; i < this.sendTables.length; i++) {
|
||||
if (this.sendTables[i].name === name) {
|
||||
return this.sendTables[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
Match.prototype.getStringTable = function (name) {
|
||||
for (var i = 0; i < this.stringTables.length; i++) {
|
||||
if (this.stringTables[i].name === name) {
|
||||
return this.stringTables[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
Match.prototype.getState = function () {
|
||||
return {
|
||||
'chat' : this.chat,
|
||||
'users' : this.users,
|
||||
'deaths' : this.deaths,
|
||||
'rounds' : this.rounds,
|
||||
'startTick' : this.startTick,
|
||||
'intervalPerTick': this.intervalPerTick
|
||||
};
|
||||
};
|
||||
|
||||
Match.prototype.handlePacket = function (packet) {
|
||||
var userState;
|
||||
switch (packet.packetType) {
|
||||
case 'netTick':
|
||||
if (this.startTick === 0) {
|
||||
this.startTick = packet.tick;
|
||||
}
|
||||
this.tick = packet.tick;
|
||||
break;
|
||||
case 'serverInfo':
|
||||
this.intervalPerTick = packet.intervalPerTick;
|
||||
break;
|
||||
case 'sayText2':
|
||||
this.chat.push({
|
||||
kind: packet.kind,
|
||||
from: packet.from,
|
||||
text: packet.text,
|
||||
tick: this.tick
|
||||
});
|
||||
break;
|
||||
case 'stringTable':
|
||||
if (packet.tables.userinfo) {
|
||||
for (var j = 0; j < packet.tables.userinfo.length; j++) {
|
||||
if (packet.tables.userinfo[j].extraData) {
|
||||
var name = packet.tables.userinfo[j].extraData[0];
|
||||
var steamId = packet.tables.userinfo[j].extraData[2];
|
||||
var userId = packet.tables.userinfo[j].extraData[1].charCodeAt(0);
|
||||
userState = this.getUserState(userId);
|
||||
userState.name = name;
|
||||
userState.steamId = steamId;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'gameEvent':
|
||||
switch (packet.event.name) {
|
||||
case 'player_death':
|
||||
while (packet.event.values.assister > 256 && packet.event.values.assister < (1024 * 16)) {
|
||||
packet.event.values.assister -= 256;
|
||||
}
|
||||
var assister = packet.event.values.assister < 256 ? packet.event.values.assister : null;
|
||||
// todo get player names, not same id as the name string table
|
||||
while (packet.event.values.attacker > 256) {
|
||||
packet.event.values.attacker -= 256;
|
||||
}
|
||||
while (packet.event.values.userid > 256) {
|
||||
packet.event.values.userid -= 256;
|
||||
}
|
||||
this.deaths.push({
|
||||
killer : packet.event.values.attacker,
|
||||
assister: assister,
|
||||
victim : packet.event.values.userid,
|
||||
weapon : packet.event.values.weapon,
|
||||
tick : this.tick
|
||||
});
|
||||
break;
|
||||
case 'teamplay_round_win':
|
||||
if (packet.event.values.winreason !== 6) {// 6 = timelimit
|
||||
this.rounds.push({
|
||||
winner : packet.event.values.team === 2 ? 'red' : 'blue',
|
||||
length : packet.event.values.round_time,
|
||||
end_tick: this.tick
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'player_spawn':
|
||||
userId = packet.event.values.userid;
|
||||
userState = this.getUserState(userId);
|
||||
if (!userState.team) { //only register first spawn
|
||||
userState.team = packet.event.values.team === 2 ? 'red' : 'blue'
|
||||
}
|
||||
var classId = packet.event.values.class;
|
||||
if (!userState.classes[classId]) {
|
||||
userState.classes[classId] = 0;
|
||||
}
|
||||
userState.classes[classId]++;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Match.prototype.getUserState = function (userId) {
|
||||
// no clue why it does this
|
||||
// only seems to be the case with per user ready
|
||||
while (userId > 256) {
|
||||
userId -= 256;
|
||||
}
|
||||
if (!this.users[userId]) {
|
||||
this.users[userId] = {
|
||||
name : null,
|
||||
userId : userId,
|
||||
steamId: null,
|
||||
classes: {}
|
||||
}
|
||||
}
|
||||
return this.users[userId];
|
||||
};
|
||||
|
||||
Object.defineProperty(Match.prototype, 'classBits', {
|
||||
get: function () {
|
||||
return Math.ceil(Math.log(this.serverClasses.length) * Math.LOG2E)
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Match;
|
||||
19
src/package.json
Normal file
19
src/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "tf2-demo",
|
||||
"description": "A parser for TF2 demo files",
|
||||
"version": "0.2.5",
|
||||
"bin": {
|
||||
"demo-analyse": "./bin/analyse.js"
|
||||
},
|
||||
"main": "demo.js",
|
||||
"dependencies": {
|
||||
"bit-buffer": "icewind1991/bit-buffer#readBitStream",
|
||||
"clone": "^2.1.0",
|
||||
"minimist": "1.1.x",
|
||||
"smart-buffer": "^1.0.1",
|
||||
"typedarray-to-buffer": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-es2015-node6": "^0.4.0"
|
||||
}
|
||||
}
|
||||
83
src/packet.js
Normal file
83
src/packet.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
var ParserGenerator = require('./parsergenerator');
|
||||
|
||||
// https://code.google.com/p/coldemoplayer/source/browse/branches/2.0/compLexity+Demo+Player/CDP.Source/Messages/?r=219
|
||||
// https://github.com/TimePath/hl2-toolkit/tree/master/src/main/java/com/timepath/hl2/io/demo
|
||||
// https://github.com/stgn/netdecode/blob/master/Packet.cs
|
||||
// https://github.com/LestaD/SourceEngine2007/blob/master/src_main/common/netmessages.cpp
|
||||
|
||||
var Packet = function (type, tick, stream, length, viewOrigin, match) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
this.viewOrigin = viewOrigin;
|
||||
this.match = match;
|
||||
};
|
||||
|
||||
Packet.gameEventMap = {};
|
||||
|
||||
Object.defineProperty(Packet.prototype, 'bitsLeft', {
|
||||
get: function () {
|
||||
return (this.length * 8) - this.stream._index;
|
||||
}
|
||||
});
|
||||
|
||||
Packet.prototype.parse = function () {
|
||||
//var table = new PacketStringTable(this.stream);
|
||||
//table.searchIds();
|
||||
//return [];
|
||||
|
||||
var packets = [];
|
||||
var entities = [];
|
||||
while (this.bitsLeft > 6) { // last 6 bits for NOOP
|
||||
var type = this.stream.readBits(6);
|
||||
if (Packet.parsers[type]) {
|
||||
var packet = Packet.parsers[type].call(this, this.stream, Packet.gameEventMap, entities, this.match);
|
||||
if (packet) {
|
||||
packet.viewOrigin = this.viewOrigin;
|
||||
}
|
||||
//console.log(packet);
|
||||
packets.push(packet);
|
||||
} else {
|
||||
throw 'Unknown packet type ' + type;
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
};
|
||||
|
||||
Packet.parsers = {
|
||||
0 : function () {//NOOP
|
||||
},
|
||||
2 : ParserGenerator.make('file', 'transferId{32}fileName{s}requested{b}'),
|
||||
3 : ParserGenerator.make('netTick', 'tick{32}frameTime{16}stdDev{16}'),
|
||||
4 : ParserGenerator.make('stringCmd', 'command{s}'),
|
||||
5 : require('./handlers/packet/setConVar'),
|
||||
6 : ParserGenerator.make('sigOnState', 'state{8}count{32}'),
|
||||
7 : ParserGenerator.make('print', 'value{s}'),
|
||||
8 : ParserGenerator.make('serverInfo',
|
||||
'version{16}serverCount{32}stv{b}dedicated{b}maxCrc{32}maxClasses{16}' +
|
||||
'mapHash{128}playerCount{8}maxPlayerCount{8}intervalPerTick{f32}platform{s1}' +
|
||||
'game{s}map{s}skybox{s}serverName{s}replay{b}'),
|
||||
10: require('./handlers/packet/classInfo'),
|
||||
11: ParserGenerator.make('setPause', 'paused{b}'),
|
||||
12: require('./handlers/packet/createStringTable'),
|
||||
13: require('./handlers/packet/updateStringTable'),
|
||||
14: ParserGenerator.make('voiceInit', 'codec{s}quality{8}'),
|
||||
15: ParserGenerator.make('voiceData', 'client{8}proximity{8}length{16}_{$length}'),
|
||||
17: require('./handlers/packet/parseSounds'),
|
||||
18: ParserGenerator.make('setView', 'index{11}'),
|
||||
19: ParserGenerator.make('fixAngle', 'relative{b}x{16}y{16}z{16}'),
|
||||
21: require('./handlers/packet/bspDecal'),
|
||||
23: require('./handlers/packet/userMessage'),
|
||||
24: require('./handlers/packet/entityMessage'),
|
||||
25: require('./handlers/packet/gameEvent'),
|
||||
26: require('./handlers/packet/packetEntities'),
|
||||
27: ParserGenerator.make('tempEntities', 'count{8}length{17}_{$length}'),
|
||||
28: ParserGenerator.make('preFetch', 'index{14}'),
|
||||
29: ParserGenerator.make('menu', 'type{16}length{16}_{$length}_{$length}_{$length}_{$length}_{$length}_{$length}_{$length}'),//length*8
|
||||
30: require('./handlers/packet/gameEventList'),
|
||||
31: ParserGenerator.make('getCvarValue', 'cookie{32}value{s}'),
|
||||
32: ParserGenerator.make('cmdKeyValues', 'length{32}data{$length}')
|
||||
};
|
||||
|
||||
module.exports = Packet;
|
||||
78
src/packetstringtable.js
Normal file
78
src/packetstringtable.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
function logBase2(num) {
|
||||
var result = 0;
|
||||
while ((num >>= 1) != 0) {
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var PacketStringTable = function (stream) {
|
||||
this.stream = stream;
|
||||
this.id = PacketStringTable.tables.length;
|
||||
this.strings = [];
|
||||
this.numEntries = 0;
|
||||
this.name = '';
|
||||
PacketStringTable.tables.push(this);
|
||||
};
|
||||
|
||||
PacketStringTable.prototype.parse = function () {
|
||||
//todo
|
||||
// https://coldemoplayer.googlecode.com/svn/branches/2.0/code/plugins/CDP.Source/Messages/SvcCreateStringTable.cs
|
||||
this.stream._index = this.stream._view._view.length * 8;
|
||||
return {
|
||||
packetType: 'stringTableTODO'
|
||||
};
|
||||
//return this.searchIds();
|
||||
};
|
||||
|
||||
PacketStringTable.prototype.parsePlayerInfo = function () {
|
||||
console.log('name: ' + this.stream.readUTF8String());
|
||||
};
|
||||
|
||||
// "fuckit" parsing, look for anything that looks like a steam id, user id is the 32 bit before that
|
||||
PacketStringTable.prototype.searchIds = function () {
|
||||
var validChar = function (charCode) {
|
||||
return charCode === 91 || charCode === 93 || charCode === 58 || (charCode > 47 && charCode < 58); // [ ] : 0-9
|
||||
};
|
||||
var users = {};
|
||||
var numFound = 0;
|
||||
while (true) {
|
||||
var found = false;
|
||||
while (this.stream._index < ((this.stream._view._view.length - 1) * 8)) {
|
||||
var startPos = this.stream._index;
|
||||
try {
|
||||
if (this.stream.readASCIIString(3) === '[U:') {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
this.stream._index = startPos + 1;
|
||||
} catch (e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
if (numFound) {
|
||||
//console.log(users);
|
||||
}
|
||||
this.stream._index = this.stream._view._view.length * 8;
|
||||
return users;
|
||||
}
|
||||
while (validChar(this.stream.readBits(8))) {
|
||||
// seek
|
||||
}
|
||||
var endPos = this.stream._index - 8;
|
||||
var length = (endPos / 8) - (startPos / 8);
|
||||
this.stream._index = startPos - 32;
|
||||
var userId = this.stream.readBits(32);
|
||||
var steamId = this.stream.readASCIIString(length);
|
||||
if (steamId[steamId.length - 1] !== ']') {
|
||||
steamId += ']';
|
||||
}
|
||||
users[userId] = steamId;
|
||||
numFound++;
|
||||
}
|
||||
};
|
||||
|
||||
PacketStringTable.tables = [];
|
||||
|
||||
module.exports = PacketStringTable;
|
||||
143
src/parser.js
Normal file
143
src/parser.js
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
var toBuffer = require('typedarray-to-buffer')
|
||||
var util = require('util');
|
||||
var Packet = require('./packet');
|
||||
var ConsoleCmd = require('./consolecmd');
|
||||
var StringTable = require('./stringtable');
|
||||
var DataTable = require('./datatable');
|
||||
var UserCmd = require('./usercmd');
|
||||
var BitStream = require('bit-buffer').BitStream;
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Match = require('./match');
|
||||
|
||||
var Parser = function (stream) {
|
||||
this.stream = stream;
|
||||
this.packets = [];
|
||||
this.match = new Match();
|
||||
this.on('packet', this.match.handlePacket.bind(this.match));
|
||||
this.on('packet', function (packet) {
|
||||
this.packets.push(packet);
|
||||
});
|
||||
};
|
||||
|
||||
util.inherits(Parser, EventEmitter);
|
||||
|
||||
Parser.MessageType = {
|
||||
Sigon : 1,
|
||||
Packet : 2,
|
||||
SyncTick : 3,
|
||||
ConsoleCmd : 4,
|
||||
UserCmd : 5,
|
||||
DataTables : 6,
|
||||
Stop : 7,
|
||||
StringTables: 8
|
||||
};
|
||||
|
||||
Parser.prototype.readHeader = function () {
|
||||
return this.parseHeader(this.stream);
|
||||
};
|
||||
|
||||
Parser.prototype.parseHeader = function (stream) {
|
||||
return {
|
||||
'type' : stream.readASCIIString(8),
|
||||
'version' : stream.readInt32(),
|
||||
'protocol': stream.readInt32(),
|
||||
'server' : stream.readASCIIString(260),
|
||||
'nick' : stream.readASCIIString(260),
|
||||
'map' : stream.readASCIIString(260),
|
||||
'game' : stream.readASCIIString(260),
|
||||
'duration': stream.readFloat32(),
|
||||
'ticks' : stream.readInt32(),
|
||||
'frames' : stream.readInt32(),
|
||||
'sigon' : stream.readInt32()
|
||||
}
|
||||
};
|
||||
|
||||
Parser.prototype.parseBody = function () {
|
||||
var message;
|
||||
while (message = this.readMessage(this.stream, this.match)) {
|
||||
this.handleMessage(message);
|
||||
}
|
||||
this.emit('done', this.match);
|
||||
return this.match;
|
||||
};
|
||||
|
||||
Parser.prototype.parseMessage = function (buffer, type, tick, length, viewOrigin, match) {
|
||||
var data = new BitStream(buffer);
|
||||
|
||||
switch (type) {
|
||||
case Parser.MessageType.Sigon:
|
||||
case Parser.MessageType.Packet:
|
||||
return new Packet(type, tick, data, length, viewOrigin, match);
|
||||
case Parser.MessageType.ConsoleCmd:
|
||||
return new ConsoleCmd(type, tick, data, length, match);
|
||||
case Parser.MessageType.UserCmd:
|
||||
return new UserCmd(type, tick, data, length, match);
|
||||
case Parser.MessageType.DataTables:
|
||||
return new DataTable(type, tick, data, length, match);
|
||||
case Parser.MessageType.StringTables:
|
||||
return new StringTable(type, tick, data, length, match);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
Parser.prototype.handleMessage = function (message) {
|
||||
if (message.parse) {
|
||||
var packets = message.parse();
|
||||
for (var i = 0; i < packets.length; i++) {
|
||||
var packet = packets[i];
|
||||
if (packet) {
|
||||
this.emit('packet', packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Parser.prototype.readMessage = function (stream, match) {
|
||||
var type = stream.readBits(8);
|
||||
if (type === Parser.MessageType.Stop) {
|
||||
return null;
|
||||
}
|
||||
var tick = stream.readInt32();
|
||||
var start, length, buffer;
|
||||
|
||||
var viewOrigin = [];
|
||||
var viewAngles = [];
|
||||
|
||||
switch (type) {
|
||||
case Parser.MessageType.Sigon:
|
||||
case Parser.MessageType.Packet:
|
||||
this.stream.readInt32(); // flags
|
||||
for (var j = 0; j < 2; j++) {
|
||||
viewOrigin[j] = [];
|
||||
viewAngles[j] = [];
|
||||
for (var i = 0; i < 3; i++) {
|
||||
viewOrigin[j][i] = this.stream.readInt32();
|
||||
}
|
||||
for (i = 0; i < 3; i++) {
|
||||
viewAngles[j][i] = this.stream.readInt32();
|
||||
}
|
||||
for (i = 0; i < 3; i++) {
|
||||
this.stream.readInt32(); // local viewAngles
|
||||
}
|
||||
}
|
||||
this.stream.readInt32(); // sequence in
|
||||
this.stream.readInt32(); // sequence out
|
||||
break;
|
||||
case Parser.MessageType.UserCmd:
|
||||
stream.byteIndex += 0x04; // unknown / outgoing sequence
|
||||
break;
|
||||
case Parser.MessageType.Stop:
|
||||
return false;
|
||||
case Parser.MessageType.SyncTick:
|
||||
return true;
|
||||
}
|
||||
|
||||
length = stream.readInt32();
|
||||
start = stream.byteIndex;
|
||||
buffer = toBuffer(stream._view._view.slice(start, start + length));
|
||||
stream.byteIndex += length;
|
||||
return this.parseMessage(buffer, type, tick, length, viewOrigin, match);
|
||||
};
|
||||
|
||||
module.exports = Parser;
|
||||
50
src/parsergenerator.js
Normal file
50
src/parsergenerator.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
var Generator = {};
|
||||
|
||||
Generator.make = function (name, string) {
|
||||
var parts = string.substr(0, string.length - 1).split('}');//remove leading } to prevent empty part
|
||||
var items = parts.map(function (part) {
|
||||
return part.split('{');
|
||||
});
|
||||
return function (stream) {
|
||||
var result = {
|
||||
'packetType': name
|
||||
};
|
||||
try {
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var value = Generator.readItem(stream, items[i][1], result);
|
||||
if (items[i][0] !== '_') {
|
||||
result[items[i][0]] = value;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
throw 'Failed reading pattern ' + string + '. ' + e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
Generator.readItem = function (stream, description, data) {
|
||||
var length;
|
||||
if (description[0] === 'b') {
|
||||
return !!stream.readBits(1);
|
||||
} else if (description[0] === 's') {
|
||||
if (description.length === 1) {
|
||||
return stream.readUTF8String();
|
||||
} else {
|
||||
length = parseInt(description.substr(1), 10);
|
||||
return stream.readASCIIString(length);
|
||||
}
|
||||
} else if (description === 'f32') {
|
||||
return stream.readFloat32();
|
||||
} else if (description[0] === 'u') {
|
||||
length = parseInt(description.substr(1), 10);
|
||||
return stream.readBits(length);
|
||||
} else if (description[0] === '$') {
|
||||
var variable = description.substr(1);
|
||||
return stream.readBits(variable);
|
||||
} else {
|
||||
return stream.readBits(parseInt(description, 10), true);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Generator;
|
||||
81
src/stringtable.js
Normal file
81
src/stringtable.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
var BitStream = require('bit-buffer').BitStream;
|
||||
|
||||
var StringTable = function (type, tick, stream, length, match) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
this.match = match;
|
||||
};
|
||||
|
||||
StringTable.prototype.parse = function () {
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/ST/StringTableParser.cs
|
||||
var tableCount = this.stream.readUint8();
|
||||
var tables = {};
|
||||
var extraDataLength;
|
||||
for (var i = 0; i < tableCount; i++) {
|
||||
var entries = [];
|
||||
var tableName = this.stream.readASCIIString();
|
||||
var entryCount = this.stream.readUint16();
|
||||
for (var j = 0; j < entryCount; j++) {
|
||||
try {
|
||||
var entry = {
|
||||
text: this.stream.readUTF8String()
|
||||
};
|
||||
} catch (e){
|
||||
return [{
|
||||
packetType: 'stringTable',
|
||||
tables : tables
|
||||
}];
|
||||
}
|
||||
if (this.stream.readBits(1)) {
|
||||
extraDataLength = this.stream.readUint16();
|
||||
if (tableName === 'instancebaseline') {
|
||||
this.match.staticBaseLines[parseInt(entry.text, 10)] = this.stream.readBitStream(8 * extraDataLength);
|
||||
} else {
|
||||
entry.extraData = this.readExtraData(extraDataLength);
|
||||
}
|
||||
}
|
||||
entries.push(entry);
|
||||
}
|
||||
tables[tableName] = entries;
|
||||
this.match.stringTables.push({
|
||||
name : tableName,
|
||||
entries: entries
|
||||
});
|
||||
if (this.stream.readBits(1)) {
|
||||
this.stream.readASCIIString();
|
||||
if (this.stream.readBits(1)) {
|
||||
//throw 'more extra data not implemented';
|
||||
extraDataLength = this.stream.readBits(16);
|
||||
this.stream.readBits(extraDataLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
//console.log(tables);
|
||||
return [{
|
||||
packetType: 'stringTable',
|
||||
tables : tables
|
||||
}];
|
||||
};
|
||||
|
||||
StringTable.prototype.readExtraData = function (length) {
|
||||
var end = this.stream._index + (length * 8);
|
||||
var data = [];
|
||||
//console.log(this.stream.readUTF8String());
|
||||
data.push(this.stream.readUTF8String());
|
||||
while (this.stream._index < end) {
|
||||
try {
|
||||
var string = this.stream.readUTF8String();
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
if (string) {
|
||||
data.push(string);
|
||||
}
|
||||
}
|
||||
this.stream._index = end;
|
||||
return data;
|
||||
};
|
||||
|
||||
module.exports = StringTable;
|
||||
13
src/usercmd.js
Normal file
13
src/usercmd.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
var UserCMD = function (type, tick, stream, length, match) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
this.match = match;
|
||||
};
|
||||
|
||||
UserCMD.prototype.parse = function () {
|
||||
return [];
|
||||
};
|
||||
|
||||
module.exports = UserCMD;
|
||||
Loading…
Add table
Add a link
Reference in a new issue