1
0
Fork 0
mirror of https://github.com/demostf/demo.js synced 2026-06-03 16:44:12 +02:00

final typescript conversion

This commit is contained in:
Robin Appelman 2016-12-18 20:15:18 +01:00
commit 3b805be013
19 changed files with 440 additions and 518 deletions

View file

@ -1 +1 @@
module.exports = require('./build/demo');
module.exports = require('./build/Demo').Demo;

View file

@ -2,6 +2,7 @@ import {Entity} from "./Entity";
import {ServerClass} from "./ServerClass";
import {SendTable} from "./SendTable";
import {StringTable} from "./StringTable";
import {SendProp} from "./SendProp";
export class Match {
tick: number;
chat: any[];
@ -10,11 +11,11 @@ export class Match {
rounds: any[];
startTick: number;
intervalPerTick: number;
entities: Entity[];
entities: (Entity|null)[];
stringTables: StringTable[];
serverClasses: ServerClass[];
sendTables: SendTable[];
instanceBaselines: any[][];
instanceBaselines: SendProp[][][];
staticBaseLines: any[];
constructor() {

View file

@ -11,7 +11,7 @@ export class SendProp {
this.value = null;
}
clone() {
clone():SendProp {
const prop = new SendProp(this.definition);
prop.value = clone(this.value);
return prop;

41
src/Demo.ts Normal file
View file

@ -0,0 +1,41 @@
import {Stream} from "stream";
import {BitStream} from 'bit-buffer';
import {Parser} from './Parser';
import {StreamParser} from './StreamParser';
export class Demo {
stream: BitStream;
constructor(arrayBuffer: ArrayBuffer) {
this.stream = new BitStream(arrayBuffer);
}
getParser() {
return new Parser(this.stream);
}
static fromNodeBuffer(nodeBuffer) {
const arrayBuffer = new ArrayBuffer(nodeBuffer.length);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < nodeBuffer.length; ++i) {
view[i] = nodeBuffer[i];
}
return new Demo(arrayBuffer);
}
static fromNodeStream(nodeStream) {
return new StreamDemo(nodeStream);
}
}
class StreamDemo {
stream: Stream;
constructor(nodeStream: Stream) {
this.stream = nodeStream;
}
getParser() {
return new StreamParser(this.stream);
}
}

147
src/Parser.ts Normal file
View file

@ -0,0 +1,147 @@
import toBuffer = require('typedarray-to-buffer');
import {Packet} from './Parser/Message/Packet';
import {ConsoleCmd} from './Parser/Message/ConsoleCmd';
import {StringTable} from './Parser/Message/StringTable';
import {DataTable} from './Parser/Message/DataTable';
import {UserCmd} from './Parser/Message/UserCmd';
import {BitStream} from 'bit-buffer';
import {EventEmitter} from 'events';
import {Match} from './Data/Match';
import {Packet as IPacket} from "./Data/Packet";
import {Parser as MessageParser} from './Parser/Message/Parser';
export class Parser extends EventEmitter {
stream: BitStream;
packets: IPacket[];
match: Match;
constructor(stream: BitStream) {
super();
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);
});
}
readHeader() {
return this.parseHeader(this.stream);
}
parseHeader(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()
}
}
parseBody() {
let message;
while (message = this.readMessage(this.stream, this.match)) {
if (message instanceof MessageParser) {
this.handleMessage(message);
}
}
this.emit('done', this.match);
return this.match;
}
parseMessage(buffer: ArrayBuffer, type: MessageType, tick: number, length: number, match: Match): MessageParser {
const data = new BitStream(buffer);
switch (type) {
case MessageType.Sigon:
case MessageType.Packet:
return new Packet(type, tick, data, length, match);
case MessageType.ConsoleCmd:
return new ConsoleCmd(type, tick, data, length, match);
case MessageType.UserCmd:
return new UserCmd(type, tick, data, length, match);
case MessageType.DataTables:
return new DataTable(type, tick, data, length, match);
case MessageType.StringTables:
return new StringTable(type, tick, data, length, match);
default:
throw new Error("unknown message type");
}
}
handleMessage(message: MessageParser) {
if (message.parse) {
const packets = message.parse();
for (let i = 0; i < packets.length; i++) {
const packet = packets[i];
if (packet) {
this.emit('packet', packet);
}
}
}
}
readMessage(stream, match): MessageParser|boolean {
const type: MessageType = stream.readBits(8);
if (type === MessageType.Stop) {
return false;
}
const tick = stream.readInt32();
let start, length, buffer;
let viewOrigin: number[][] = [];
let viewAngles: number[][] = [];
switch (type) {
case MessageType.Sigon:
case MessageType.Packet:
this.stream.readInt32(); // flags
for (let j = 0; j < 2; j++) {
viewOrigin[j] = [];
viewAngles[j] = [];
for (let i = 0; i < 3; i++) {
viewOrigin[j][i] = this.stream.readInt32();
}
for (let i = 0; i < 3; i++) {
viewAngles[j][i] = this.stream.readInt32();
}
for (let i = 0; i < 3; i++) {
this.stream.readInt32(); // local viewAngles
}
}
this.stream.readInt32(); // sequence in
this.stream.readInt32(); // sequence out
break;
case MessageType.UserCmd:
stream.byteIndex += 0x04; // unknown / outgoing sequence
break;
case 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, match);
}
}
export enum MessageType {
Sigon = 1,
Packet = 2,
SyncTick = 3,
ConsoleCmd = 4,
UserCmd = 5,
DataTables = 6,
Stop = 7,
StringTables = 8
}

View file

@ -1,5 +1,7 @@
import {BitStream} from 'bit-buffer';
import {Match} from '../../Data/Match';
import {Packet} from "../../Data/Packet";
import {SendTable} from "../../Data/SendTable";
export abstract class Parser {
type: any;
@ -16,5 +18,5 @@ export abstract class Parser {
this.match = match;
}
abstract parse();
abstract parse():Packet[]|string|SendTable[];
}

View file

@ -1,11 +1,10 @@
import {PacketStringTable} from '../../packetstringtable';
import {PacketStringTable} from './PacketStringTable';
import {Packet} from "../../Data/Packet";
import {BitStream} from 'bit-buffer';
export function CreateStringTable(stream: BitStream): Packet { // 12: createStringTable
const stringTable = new PacketStringTable(stream);
const tables = stringTable.parse();
const tables = PacketStringTable(stream);
return {
packetType: 'createStringTable',
table: tables

View file

@ -6,18 +6,18 @@ import {BitStream} from 'bit-buffer';
import {GameEventDefinition} from "../../Data/GameEvent";
import {Match} from "../../Data/Match";
var PVS = {
PRESERVE: 0,
ENTER: 1,
LEAVE: 2,
DELETE: 4
};
enum PVS {
PRESERVE = 0,
ENTER = 1,
LEAVE = 2,
DELETE = 4
}
function readPVSType(stream) {
function readPVSType(stream: BitStream): number {
// https://github.com/skadistats/smoke/blob/a2954fbe2fa3936d64aee5b5567be294fef228e6/smoke/io/stream/entity.pyx#L24
var pvs;
var hi = stream.readBoolean();
var low = stream.readBoolean();
let pvs;
const hi = stream.readBoolean();
const low = stream.readBoolean();
if (low && !hi) {
pvs = PVS.ENTER;
} else if (!(hi || low)) {
@ -30,27 +30,33 @@ function readPVSType(stream) {
return pvs;
}
function readEnterPVS(stream, entityId, match, baseLine) {
function readEnterPVS(stream: BitStream, entityId: number, match: Match, baseLine: number):Entity {
// https://github.com/PazerOP/DemoLib/blob/5f9467650f942a4a70f9ec689eadcd3e0a051956/TF2Net/NetMessages/NetPacketEntitiesMessage.cs#L198
var serverClass = match.serverClasses[stream.readBits(match.classBits)];
const serverClass = match.serverClasses[stream.readBits(match.classBits)];
console.log(serverClass);
var sendTable = match.getSendTable(serverClass.dataTable);
var serialNumber = stream.readBits(10);
const sendTable = match.getSendTable(serverClass.dataTable);
const serialNumber = stream.readBits(10);
if (!sendTable) {
throw new Error('Unknown SendTable for serverclass');
}
var entity = (match.entities[entityId]) ? match.entities[entityId] : new Entity(serverClass, sendTable, entityId, serialNumber);
let entity = match.entities[entityId];
if (!entity) {
entity = new Entity(serverClass, sendTable, entityId, serialNumber);
}
var decodedBaseLine = match.instanceBaselines[baseLine][entityId];
const decodedBaseLine = match.instanceBaselines[baseLine][entityId];
if (decodedBaseLine) {
for (var i = 0; i < decodedBaseLine.length; i++) {
var newProp = decodedBaseLine[i];
for (let i = 0; i < decodedBaseLine.length; i++) {
const newProp = decodedBaseLine[i];
if (!entity.getPropByDefinition(newProp.definition)) {
entity.props.push(newProp.clone(entity));
entity.props.push(newProp.clone());
}
}
} else {
var staticBaseLine = match.staticBaseLines[serverClass.id];
const staticBaseLine = match.staticBaseLines[serverClass.id];
if (staticBaseLine) {
var streamStart = staticBaseLine._index;
const streamStart = staticBaseLine._index;
applyEntityUpdate(entity, staticBaseLine);
staticBaseLine._index = streamStart;
}
@ -70,24 +76,20 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
// 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;
const maxEntries = stream.readBits(11);
const isDelta = !!stream.readBits(1);
const delta = (isDelta) ? stream.readInt32() : null;
const baseLine = stream.readBits(1);
const updatedEntries = stream.readBits(11);
const length = stream.readBits(20);
const updatedBaseLine = stream.readBoolean();
const end = stream._index + length;
let entityId = -1
stream._index = end;
return {
packetType: 'packetEntities',
entities: entities
entities: entities
};
if (updatedBaseLine) {
@ -100,29 +102,31 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
}
}
for (var i = 0; i < updatedEntries; i++) {
var diff = readUBitVar(stream);
console.log(diff);
for (let i = 0; i < updatedEntries; i++) {
const diff = readUBitVar(stream);
entityId += 1 + diff;
var pvs = readPVSType(stream);
const pvs = readPVSType(stream);
if (pvs === PVS.ENTER) {
var entity = readEnterPVS(stream, entityId, match, baseLine);
const entity = readEnterPVS(stream, entityId, match, baseLine);
applyEntityUpdate(entity, stream);
match.entities[entityId] = entity;
if (updatedBaseLine) {
match.instanceBaselines[baseLine][entityId] = [].concat(entity.props);
const newBaseLine:SendProp[] = [];
newBaseLine.concat(entity.props);
match.instanceBaselines[baseLine][entityId] = newBaseLine;
}
entity.inPVS = true;
} else if (pvs === PVS.PRESERVE) {
entity = match.entities[entityId];
if (!entity) {
const entity = match.entities[entityId];
if (entity) {
applyEntityUpdate(entity, stream);
} else {
console.log(entityId, match.entities.length);
throw new Error("unknown entity");
}
applyEntityUpdate(entity, stream);
} else {
entity = match.entities[entityId];
const entity = match.entities[entityId];
if (entity) {
entity.inPVS = false;
}
@ -132,7 +136,7 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
if (isDelta) {
while (stream.readBoolean()) {
var ent = stream.readBits(11);
const ent = stream.readBits(11);
match.entities[ent] = null;
}
}
@ -140,29 +144,29 @@ export function PacketEntities(stream: BitStream, events: GameEventDefinition[],
stream._index = end;
return {
packetType: 'packetEntities',
entities: entities
entities: entities
};
};
}
var readFieldIndex = function (stream, lastIndex) {
const readFieldIndex = function (stream: BitStream, lastIndex: number): number {
if (!stream.readBoolean()) {
return -1;
}
var diff = readUBitVar(stream);
const diff = readUBitVar(stream);
return lastIndex + diff + 1;
};
var applyEntityUpdate = function (entity, stream) {
var index = -1;
var allProps = entity.sendTable.flattenedProps;
const applyEntityUpdate = function (entity: Entity, stream: BitStream): Entity {
let index = -1;
const 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;
const propDefinition = allProps[index];
const existingProp = entity.getPropByDefinition(propDefinition);
let prop;
if (existingProp) {
prop = existingProp;
} else {
@ -178,7 +182,7 @@ var applyEntityUpdate = function (entity, stream) {
return entity;
};
var readUBitVar = function (stream) {
const readUBitVar = function (stream: BitStream): number {
switch (stream.readBits(2)) {
case 0:
return stream.readBits(4);
@ -189,4 +193,5 @@ var readUBitVar = function (stream) {
case 3:
return stream.readBits(32);
}
throw new Error('Invalid var bit');
};

View file

@ -0,0 +1,11 @@
import {BitStream} from "bit-buffer";
import {Packet} from "../../Data/Packet";
export function PacketStringTable(stream: BitStream):Packet {
//todo
// https://coldemoplayer.googlecode.com/svn/branches/2.0/code/plugins/CDP.Source/Messages/SvcCreateStringTable.cs
stream._index = stream._view._view.byteLength * 8;
return {
packetType: 'stringTableTODO'
};
}

View file

@ -1,10 +1,9 @@
import {PacketStringTable} from '../../packetstringtable';
import {PacketStringTable} from './PacketStringTable';
import {Packet} from "../../Data/Packet";
import {BitStream} from 'bit-buffer';
export function UpdateStringTable(stream: BitStream): Packet { // 12: updateStringTable
const stringTable = new PacketStringTable(stream);
const tables = stringTable.parse();
const tables = PacketStringTable(stream);
return {
packetType: 'updateStringTable',
table: tables

View file

@ -1,86 +1,87 @@
import {Packet} from "../../Data/Packet";
import {BitStream} from 'bit-buffer';
import {make} from './ParserGenerator';
import {SayText2} from '../UserMessage/SayText2';
enum 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
}
const userMessageParsers = {
4: require('../../handlers/userMessage/SayText2'),
4: SayText2,
5: make('textMsg', 'destType{8}text{s}')
};
export function UserMessage(stream: BitStream): Packet { // 23: user message
const type = stream.readBits(8);
const type = stream.readBits(8);
const length = stream.readBits(11);
const pos = stream._index;
const pos = stream._index;
let result;
if (userMessageParsers[type]) {
result = userMessageParsers[type](stream);
} else {
result = {
packetType: 'unknownUserMessage',
type: type
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
};

View file

@ -34,6 +34,7 @@ export class SendPropParser {
case SendPropType.DPT_Array:
return SendPropParser.readArray(propDefinition, stream);
}
throw new Error('Unknown property type');
}
static readInt(propDefinition: SendPropDefinition, stream: BitStream) {

View file

@ -1,7 +1,10 @@
module.exports = function (stream) { // 4: SayText2
import {Packet} from "../../Data/Packet";
import {BitStream} from 'bit-buffer';
export function SayText2(stream: BitStream): Packet { // 4: SayText2
var client = stream.readBits(8);
var raw = stream.readBits(8);
var pos = stream._index;
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);
@ -14,16 +17,16 @@ module.exports = function (stream) { // 4: SayText2
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';
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();
kind = stream.readUTF8String();
from = stream.readUTF8String();
text = stream.readUTF8String();
stream.readASCIIString();
stream.readASCIIString();
}
@ -35,10 +38,10 @@ module.exports = function (stream) { // 4: SayText2
}
return {
packetType: 'sayText2',
client : client,
raw : raw,
kind : kind,
from : from,
text : text
client: client,
raw: raw,
kind: kind,
from: from,
text: text
}
};

View file

@ -1,16 +1,88 @@
var util = require('util');
var Parser = require('./parser');
var BitStream = require('bit-buffer').BitStream;
import {Match} from "./Data/Match";
import {BitStream} from 'bit-buffer';
import {Parser, MessageType} from './Parser';
import {Stream} from "stream";
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);
};
export class StreamParser extends Parser {
buffer: Buffer;
header: any;
sourceStream: Stream;
util.inherits(StreamParser, Parser);
constructor(stream) {
super(new BitStream(new ArrayBuffer(0)));
this.sourceStream = stream;
this.on('packet', this.match.handlePacket.bind(this.match));
this.header = null;
this.buffer = new Buffer(0);
}
eatBuffer(length) {
this.buffer = shrinkBuffer(this.buffer, length);
}
start() {
this.sourceStream.on('data', this.handleData.bind(this));
this.sourceStream.on('end', function () {
this.emit('done', this.match);
}.bind(this));
}
handleData(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.readStreamMessage();
}
}
readStreamMessage() {
if (this.buffer.length < 9) { // 9 byte minimum message header (type, tick, length)
return;
}
const stream = new BitStream(this.buffer);
const type = stream.readBits(8);
if (type === MessageType.Stop) {
console.log('stop');
return;
}
const tick = stream.readInt32();
let headerSize = 5;
let extraHeader = 0;
switch (type) {
case MessageType.Sigon:
case MessageType.Packet:
extraHeader += 0x54; // command/sequence info
break;
case MessageType.UserCmd:
extraHeader += 0x04; // unknown / outgoing sequence
break;
case MessageType.Stop:
case MessageType.SyncTick:
this.eatBuffer(headerSize);
return;
}
stream.byteIndex += extraHeader;
const length = stream.readInt32();
headerSize += extraHeader + 4;
if (this.buffer.length < (headerSize + length)) {
console.log('wants ' + length);
return;
}
console.log('got message ' + tick);
const messageBuffer = this.buffer.slice(headerSize, headerSize + length);
this.eatBuffer(headerSize + length);
const message = this.parseMessage(messageBuffer, type, tick, length, this.match);
this.handleMessage(message);
}
}
function shrinkBuffer(buffer, length) {
if (length < 0) {
@ -18,72 +90,3 @@ function shrinkBuffer(buffer, 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;

View file

@ -1,43 +0,0 @@
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;

View file

@ -1,70 +0,0 @@
export class PacketStringTable {
constructor(stream) {
this.stream = stream;
this.id = PacketStringTable.tables.length;
this.strings = [];
this.numEntries = 0;
this.name = '';
PacketStringTable.tables.push(this);
}
parse() {
//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();
}
parsePlayerInfo() {
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
searchIds() {
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 = [];

View file

@ -1,143 +0,0 @@
import * as toBuffer from 'typedarray-to-buffer'
import {Packet} from './Parser/Message/Packet';
import {ConsoleCmd} from './Parser/Message/ConsoleCmd';
import {StringTable} from './Parser/Message/StringTable';
import {DataTable} from './Parser/Message/DataTable';
import {UserCmd} from './Parser/Message/UserCmd';
import {BitStream} from 'bit-buffer';
import {EventEmitter} from 'events';
import {Match} from './Data/Match';
class Parser extends EventEmitter {
constructor(stream) {
super();
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);
});
}
readHeader() {
return this.parseHeader(this.stream);
}
parseHeader(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()
}
}
parseBody() {
var message;
while (message = this.readMessage(this.stream, this.match)) {
this.handleMessage(message);
}
this.emit('done', this.match);
return this.match;
}
parseMessage(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;
}
}
handleMessage(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);
}
}
}
}
readMessage(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);
}
}
Parser.MessageType = {
Sigon : 1,
Packet : 2,
SyncTick : 3,
ConsoleCmd : 4,
UserCmd : 5,
DataTables : 6,
Stop : 7,
StringTables: 8
};
module.exports = Parser;

View file

@ -1,40 +0,0 @@
export interface BitView {
length: number;
}
export interface BitStream {
byteIndex: number;
buffer: Buffer;
_view: BitView;
_index: number;
readBits(bits: number, signed?: boolean): number;
readWrite(value: number, bits: number);
readBoolean(): boolean;
readInt8(): number;
readUint8(): number;
readInt16(): number;
readUint16(): number;
readInt32(): number;
readUint32(): number;
readFloat32(): number;
readFloat64(): number;
writeBoolean(value: number);
writeInt8(value: number);
writeUint8(value: number);
writeInt16(value: number);
writeUint16(value: number);
writeInt32(value: number);
writeUint32(value: number);
writeFloat32(value: number);
writeFloat64(value: number);
readASCIIString(length?: number): string;
readUTF8String(length?: number): string;
writeASCIIString(data: string, length?: number);
writeUTF8String(data: string, length?: number);
readBitStream(length: number): BitStream;
}

View file

@ -0,0 +1,5 @@
declare module 'typedarray-to-buffer' {
function toBuffer(typedArray:Uint8Array):ArrayBuffer;
export = toBuffer
}