mirror of
https://github.com/demostf/demo.js
synced 2026-06-04 09:04:13 +02:00
more typescript conversions
This commit is contained in:
parent
06860cc3fe
commit
94383f447f
48 changed files with 1204 additions and 1051 deletions
7
src/Parser/Message/ConsoleCmd.ts
Normal file
7
src/Parser/Message/ConsoleCmd.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import {Parser} from './Parser';
|
||||
|
||||
export class ConsoleCmd extends Parser {
|
||||
parse() {
|
||||
return this.stream.readUTF8String();
|
||||
}
|
||||
}
|
||||
101
src/Parser/Message/DataTable.ts
Normal file
101
src/Parser/Message/DataTable.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import {SendTable} from '../../Data/SendTable';
|
||||
import {SendPropDefinition, SendPropFlag, SendPropType} from '../../Data/SendPropDefinition';
|
||||
import {ServerClass} from '../../Data/ServerClass';
|
||||
import {Parser} from './Parser';
|
||||
|
||||
export class DataTable extends Parser {
|
||||
parse() {
|
||||
// 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:SendTable[] = [];
|
||||
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 === SendPropType.DPT_DataTable) {
|
||||
prop.excludeDTName = this.stream.readASCIIString();
|
||||
} else {
|
||||
if (prop.isExcludeProp()) {
|
||||
prop.excludeDTName = this.stream.readASCIIString();
|
||||
} else if (prop.type === SendPropType.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(SendPropFlag.SPROP_NOSCALE)) {
|
||||
if (prop.type === SendPropType.DPT_Float) {
|
||||
prop.bitCount = 32;
|
||||
} else if (prop.type === SendPropType.DPT_Vector) {
|
||||
if (!prop.hasFlag(SendPropFlag.SPROP_NORMAL)) {
|
||||
prop.bitCount = 32 * 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (arrayElementProp) {
|
||||
if (prop.type !== SendPropType.DPT_Array) {
|
||||
throw "expected prop of type array";
|
||||
}
|
||||
prop.arrayProperty = arrayElementProp;
|
||||
arrayElementProp = null;
|
||||
}
|
||||
|
||||
if (prop.hasFlag(SendPropFlag.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 === SendPropType.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;
|
||||
}
|
||||
}
|
||||
87
src/Parser/Message/Packet.ts
Normal file
87
src/Parser/Message/Packet.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import * as ParserGenerator from '../Packet/ParserGenerator';
|
||||
|
||||
import {Parser} from './Parser';
|
||||
import {BSPDecal} from '../Packet/BSPDecal';
|
||||
import {ClassInfo} from '../Packet/ClassInfo';
|
||||
import {CreateStringTable} from '../Packet/CreateStringTable';
|
||||
import {EntityMessage} from '../Packet/EntityMessage';
|
||||
import {GameEvent} from '../Packet/GameEvent';
|
||||
import {GameEventList} from '../Packet/GameEventList';
|
||||
import {PacketEntities} from '../Packet/PacketEntities';
|
||||
import {ParseSounds} from '../Packet/ParseSounds';
|
||||
import {SetConVar} from '../Packet/SetConVar';
|
||||
import {UpdateStringTable} from '../Packet/UpdateStringTable';
|
||||
import {UserMessage} from '../Packet/UserMessage';
|
||||
import {PacketParserMap} from '../Packet/Parser'
|
||||
import {GameEventDefinitionMap} from "../../Data/GameEvent";
|
||||
|
||||
import {Packet as IPacket} from '../../Data/Packet';
|
||||
|
||||
// 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
|
||||
|
||||
export class Packet extends Parser {
|
||||
viewOrigin: any;
|
||||
|
||||
parse() {
|
||||
//var table = new PacketStringTable(this.stream);
|
||||
//table.searchIds();
|
||||
//return [];
|
||||
|
||||
let packets: IPacket[] = [];
|
||||
let entities = [];
|
||||
while (this.bitsLeft > 6) { // last 6 bits for NOOP
|
||||
const type = this.stream.readBits(6);
|
||||
if (type !== 0) {
|
||||
if (Packet.parsers[type]) {
|
||||
let packet = Packet.parsers[type].call(this, this.stream, Packet.gameEventMap, entities, this.match);
|
||||
packets.push(packet);
|
||||
} else {
|
||||
throw 'Unknown packet type ' + type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
get bitsLeft() {
|
||||
return (this.length * 8) - this.stream._index;
|
||||
}
|
||||
|
||||
static parsers: PacketParserMap = {
|
||||
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: 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: ClassInfo,
|
||||
11: ParserGenerator.make('setPause', 'paused{b}'),
|
||||
12: CreateStringTable,
|
||||
13: UpdateStringTable,
|
||||
14: ParserGenerator.make('voiceInit', 'codec{s}quality{8}'),
|
||||
15: ParserGenerator.make('voiceData', 'client{8}proximity{8}length{16}_{$length}'),
|
||||
17: ParseSounds,
|
||||
18: ParserGenerator.make('setView', 'index{11}'),
|
||||
19: ParserGenerator.make('fixAngle', 'relative{b}x{16}y{16}z{16}'),
|
||||
21: BSPDecal,
|
||||
23: UserMessage,
|
||||
24: EntityMessage,
|
||||
25: GameEvent,
|
||||
26: 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: GameEventList,
|
||||
31: ParserGenerator.make('getCvarValue', 'cookie{32}value{s}'),
|
||||
32: ParserGenerator.make('cmdKeyValues', 'length{32}data{$length}')
|
||||
};
|
||||
|
||||
static gameEventMap: GameEventDefinitionMap = {};
|
||||
}
|
||||
20
src/Parser/Message/Parser.ts
Normal file
20
src/Parser/Message/Parser.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Match} from '../../Data/Match';
|
||||
|
||||
export abstract class Parser {
|
||||
type: any;
|
||||
tick: number;
|
||||
stream: BitStream;
|
||||
length: number;
|
||||
match: Match;
|
||||
|
||||
constructor(type, tick, stream, length, match) {
|
||||
this.type = type;
|
||||
this.tick = tick;
|
||||
this.stream = stream;
|
||||
this.length = length;//length in bytes
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
abstract parse();
|
||||
}
|
||||
75
src/Parser/Message/StringTable.ts
Normal file
75
src/Parser/Message/StringTable.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import {Parser} from './Parser';
|
||||
import {StringTableEntry} from "../../Data/StringTable";
|
||||
|
||||
export class StringTable extends Parser {
|
||||
parse() {
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/ST/StringTableParser.cs
|
||||
const tableCount = this.stream.readUint8();
|
||||
let tables = {};
|
||||
let extraDataLength;
|
||||
for (let i = 0; i < tableCount; i++) {
|
||||
let entries:StringTableEntry[] = [];
|
||||
const tableName = this.stream.readASCIIString();
|
||||
const entryCount = this.stream.readUint16();
|
||||
for (let j = 0; j < entryCount; j++) {
|
||||
let entry;
|
||||
try {
|
||||
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
|
||||
}];
|
||||
}
|
||||
|
||||
readExtraData(length):string[] {
|
||||
const end = this.stream._index + (length * 8);
|
||||
let data:string[] = [];
|
||||
//console.log(this.stream.readUTF8String());
|
||||
data.push(this.stream.readUTF8String());
|
||||
while (this.stream._index < end) {
|
||||
try {
|
||||
let string = this.stream.readUTF8String();
|
||||
if (string) {
|
||||
data.push(string);
|
||||
}
|
||||
} catch (e) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
this.stream._index = end;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
7
src/Parser/Message/UserCmd.ts
Normal file
7
src/Parser/Message/UserCmd.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import {Parser} from './Parser';
|
||||
|
||||
export class UserCmd extends Parser {
|
||||
parse() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
51
src/Parser/Packet/BSPDecal.ts
Normal file
51
src/Parser/Packet/BSPDecal.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
const getCoord = function (stream) {
|
||||
const hasInt = !!stream.readBits(1);
|
||||
const hasFract = !!stream.readBits(1);
|
||||
let value = 0;
|
||||
if (hasInt || hasFract) {
|
||||
const 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;
|
||||
};
|
||||
|
||||
const getVecCoord = function (stream) {
|
||||
const hasX = !!stream.readBits(1);
|
||||
const hasY = !!stream.readBits(1);
|
||||
const hasZ = !!stream.readBits(1);
|
||||
return {
|
||||
x: hasX ? getCoord(stream) : 0,
|
||||
y: hasY ? getCoord(stream) : 0,
|
||||
z: hasZ ? getCoord(stream) : 0
|
||||
}
|
||||
};
|
||||
|
||||
export function BSPDecal(stream: BitStream): Packet { // 21: BSPDecal
|
||||
let modelIndex, entIndex;
|
||||
const position = getVecCoord(stream);
|
||||
const textureIndex = stream.readBits(9);
|
||||
if (stream.readBits(1)) {
|
||||
entIndex = stream.readBits(11);
|
||||
modelIndex = stream.readBits(12);
|
||||
}
|
||||
const lowPriority = !!stream.readBits(1);
|
||||
return {
|
||||
packetType: 'BSPDecal',
|
||||
position: position,
|
||||
textureIndex: textureIndex,
|
||||
entIndex: entIndex,
|
||||
modelIndex: modelIndex,
|
||||
lowPriority: lowPriority
|
||||
}
|
||||
}
|
||||
33
src/Parser/Packet/ClassInfo.ts
Normal file
33
src/Parser/Packet/ClassInfo.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
function logBase2(num: number): number {
|
||||
let result = 0;
|
||||
while ((num >>= 1) != 0) {
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function ClassInfo(stream: BitStream): Packet { // 10: classInfo
|
||||
const number = stream.readBits(16);
|
||||
const create = !!stream.readBits(1);
|
||||
let entries: any[] = [];
|
||||
if (!create) {
|
||||
const bits = logBase2(number) + 1;
|
||||
for (let i = 0; i < number; i++) {
|
||||
const entry = {
|
||||
'classId': stream.readBits(bits),
|
||||
'className': stream.readASCIIString(),
|
||||
'dataTableName': stream.readASCIIString()
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
}
|
||||
return {
|
||||
'packetType': 'classInfo',
|
||||
number: number,
|
||||
create: create,
|
||||
entries: entries
|
||||
}
|
||||
}
|
||||
13
src/Parser/Packet/CreateStringTable.ts
Normal file
13
src/Parser/Packet/CreateStringTable.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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();
|
||||
return {
|
||||
packetType: 'createStringTable',
|
||||
table: tables
|
||||
};
|
||||
}
|
||||
10
src/Parser/Packet/EntityMessage.ts
Normal file
10
src/Parser/Packet/EntityMessage.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import {make} from './ParserGenerator';
|
||||
|
||||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
const baseParser = make('entityMessage', 'index{11}classId{9}length{11}data{$length}');
|
||||
|
||||
export function EntityMessage(stream:BitStream):Packet { // 24: entityMessage
|
||||
return baseParser(stream); //todo parse data further?
|
||||
};
|
||||
59
src/Parser/Packet/GameEvent.ts
Normal file
59
src/Parser/Packet/GameEvent.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {
|
||||
GameEventType, GameEventValue, GameEventEntry, GameEventDefinition, GameEvent as IGameEvent,
|
||||
GameEventValueMap, GameEventDefinitionMap
|
||||
} from "../../Data/GameEvent";
|
||||
|
||||
const parseGameEvent = function (eventId: number, stream: BitStream, events: GameEventDefinitionMap): IGameEvent|null {
|
||||
if (!events[eventId]) {
|
||||
return null;
|
||||
}
|
||||
const eventDescription: GameEventDefinition = events[eventId];
|
||||
const values: GameEventValueMap = {};
|
||||
for (let i = 0; i < eventDescription.entries.length; i++) {
|
||||
const entry: GameEventEntry = eventDescription.entries[i];
|
||||
const value = getGameEventValue(stream, entry);
|
||||
if (value) {
|
||||
values[entry.name] = value;
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: eventDescription.name,
|
||||
values: values
|
||||
};
|
||||
};
|
||||
|
||||
const getGameEventValue = function (stream: BitStream, entry: GameEventEntry): GameEventValue|null {
|
||||
switch (entry.type) {
|
||||
case GameEventType.STRING:
|
||||
return stream.readUTF8String();
|
||||
case GameEventType.FLOAT:
|
||||
return stream.readFloat32();
|
||||
case GameEventType.LONG:
|
||||
return stream.readUint32();
|
||||
case GameEventType.SHORT:
|
||||
return stream.readUint16();
|
||||
case GameEventType.BYTE:
|
||||
return stream.readUint8();
|
||||
case GameEventType.BOOLEAN:
|
||||
return stream.readBoolean();
|
||||
case GameEventType.LOCAL:
|
||||
return null;
|
||||
default:
|
||||
throw new Error('invalid game event type');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export function GameEvent(stream: BitStream, events: GameEventDefinitionMap): Packet { // 25: game event
|
||||
const length = stream.readBits(11);
|
||||
const end = stream._index + length;
|
||||
const eventId = stream.readBits(9);
|
||||
const event = parseGameEvent(eventId, stream, events);
|
||||
stream._index = end;
|
||||
return {
|
||||
packetType: 'gameEvent',
|
||||
event: event
|
||||
}
|
||||
}
|
||||
31
src/Parser/Packet/GameEventList.ts
Normal file
31
src/Parser/Packet/GameEventList.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {GameEventEntry, GameEventDefinitionMap} from "../../Data/GameEvent";
|
||||
|
||||
export function GameEventList(stream: BitStream, events: GameEventDefinitionMap): Packet { // 30: gameEventList
|
||||
// list of game events and parameters
|
||||
const numEvents = stream.readBits(9);
|
||||
const length = stream.readBits(20);
|
||||
for (let i = 0; i < numEvents; i++) {
|
||||
const id = stream.readBits(9);
|
||||
const name = stream.readASCIIString();
|
||||
let type = stream.readBits(3);
|
||||
const entries: GameEventEntry[] = [];
|
||||
while (type !== 0) {
|
||||
entries.push({
|
||||
type: type,
|
||||
name: stream.readASCIIString()
|
||||
});
|
||||
type = stream.readBits(3);
|
||||
}
|
||||
events[id] = {
|
||||
id: id,
|
||||
name: name,
|
||||
entries: entries
|
||||
};
|
||||
}
|
||||
return {
|
||||
packetType: 'gameEventList',
|
||||
events: events
|
||||
}
|
||||
}
|
||||
192
src/Parser/Packet/PacketEntities.ts
Normal file
192
src/Parser/Packet/PacketEntities.ts
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
import {SendPropParser} from '../../Parser/SendPropParser';
|
||||
import {Entity} from '../../Data/Entity';
|
||||
import {SendProp} from '../../Data/SendProp';
|
||||
import {Packet} from "../../Data/Packet";
|
||||
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
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export function PacketEntities(stream: BitStream, events: GameEventDefinition[], entities: Entity[], match: Match): Packet { //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;
|
||||
|
||||
stream._index = end;
|
||||
return {
|
||||
packetType: 'packetEntities',
|
||||
entities: entities
|
||||
};
|
||||
|
||||
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;
|
||||
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 SendProp(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);
|
||||
}
|
||||
};
|
||||
15
src/Parser/Packet/ParseSounds.ts
Normal file
15
src/Parser/Packet/ParseSounds.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
export function ParseSounds(stream: BitStream): Packet { // 17: parseSounds
|
||||
const reliable = stream.readBoolean();
|
||||
const num = (reliable) ? 1 : stream.readUint8();
|
||||
const length = (reliable) ? stream.readUint8() : stream.readUint16();
|
||||
stream._index += length;
|
||||
return {
|
||||
packetType: 'parseSounds',
|
||||
reliable: reliable,
|
||||
num: num,
|
||||
length: length
|
||||
}
|
||||
}
|
||||
8
src/Parser/Packet/Parser.ts
Normal file
8
src/Parser/Packet/Parser.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {GameEventDefinitionMap} from "../../Data/GameEvent";
|
||||
import {Match} from "../../Data/Match";
|
||||
import {Entity} from "../../Data/Entity";
|
||||
|
||||
export type Parser = (stream: BitStream, gameEventMap?: GameEventDefinitionMap, entities?: Entity[], match?: Match) => Packet;
|
||||
export type PacketParserMap = {[id: number]: Parser};
|
||||
48
src/Parser/Packet/ParserGenerator.ts
Normal file
48
src/Parser/Packet/ParserGenerator.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import {Parser} from './Parser';
|
||||
|
||||
export function make(name: string, definition: string): Parser {
|
||||
var parts = definition.substr(0, definition.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 = readItem(stream, items[i][1], result);
|
||||
if (items[i][0] !== '_') {
|
||||
result[items[i][0]] = value;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
throw 'Failed reading pattern ' + definition + '. ' + e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const 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);
|
||||
}
|
||||
};
|
||||
14
src/Parser/Packet/SetConVar.ts
Normal file
14
src/Parser/Packet/SetConVar.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
export function SetConVar(stream: BitStream): Packet { // 5: setconvar
|
||||
const count = stream.readBits(8);
|
||||
let vars = {};
|
||||
for (let i = 0; i < count; i++) {
|
||||
vars[stream.readUTF8String()] = stream.readUTF8String();
|
||||
}
|
||||
return {
|
||||
packetType: 'setConVar',
|
||||
vars: vars
|
||||
}
|
||||
}
|
||||
12
src/Parser/Packet/UpdateStringTable.ts
Normal file
12
src/Parser/Packet/UpdateStringTable.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
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();
|
||||
return {
|
||||
packetType: 'updateStringTable',
|
||||
table: tables
|
||||
};
|
||||
}
|
||||
86
src/Parser/Packet/UserMessage.ts
Normal file
86
src/Parser/Packet/UserMessage.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import {Packet} from "../../Data/Packet";
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {make} from './ParserGenerator';
|
||||
|
||||
const userMessageParsers = {
|
||||
4: require('../../handlers/userMessage/SayText2'),
|
||||
5: make('textMsg', 'destType{8}text{s}')
|
||||
};
|
||||
|
||||
export function UserMessage(stream: BitStream): Packet { // 23: user message
|
||||
const type = stream.readBits(8);
|
||||
const length = stream.readBits(11);
|
||||
const pos = stream._index;
|
||||
let result;
|
||||
if (userMessageParsers[type]) {
|
||||
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
|
||||
};
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import {SendPropDefinition} from '../Data/SendPropDefinition';
|
||||
import {SendPropDefinition, SendPropType, SendPropFlag} from '../Data/SendPropDefinition';
|
||||
import {Vector} from "../Data/Vector";
|
||||
import {BitStream} from "bit-buffer";
|
||||
import {SendPropValue, SendPropArrayValue} from "../Data/SendProp";
|
||||
|
||||
|
||||
const readBitVar = function (stream, signed) {
|
||||
const readBitVar = function (stream: BitStream, signed: boolean): number {
|
||||
switch (stream.readBits(2)) {
|
||||
case 0:
|
||||
return stream.readBits(4, signed);
|
||||
|
|
@ -13,78 +15,86 @@ const readBitVar = function (stream, signed) {
|
|||
case 3:
|
||||
return stream.readBits(32, signed);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export class SendPropParser {
|
||||
static decode(propDefinition, stream) {
|
||||
static decode(propDefinition: SendPropDefinition, stream: BitStream): SendPropValue {
|
||||
switch (propDefinition.type) {
|
||||
case SendPropDefinition.types.DPT_Int:
|
||||
case SendPropType.DPT_Int:
|
||||
return SendPropParser.readInt(propDefinition, stream);
|
||||
case SendPropDefinition.types.DPT_Vector:
|
||||
case SendPropType.DPT_Vector:
|
||||
return SendPropParser.readVector(propDefinition, stream);
|
||||
case SendPropDefinition.types.DPT_VectorXY:
|
||||
case SendPropType.DPT_VectorXY:
|
||||
return SendPropParser.readVectorXY(propDefinition, stream);
|
||||
case SendPropDefinition.types.DPT_Float:
|
||||
case SendPropType.DPT_Float:
|
||||
return SendPropParser.readFloat(propDefinition, stream);
|
||||
case SendPropDefinition.types.DPT_String:
|
||||
case SendPropType.DPT_String:
|
||||
return SendPropParser.readString(stream);
|
||||
case SendPropDefinition.types.DPT_Array:
|
||||
case SendPropType.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));
|
||||
static readInt(propDefinition: SendPropDefinition, stream: BitStream) {
|
||||
if (propDefinition.hasFlag(SendPropFlag.SPROP_VARINT)) {
|
||||
return readBitVar(stream, !propDefinition.hasFlag(SendPropFlag.SPROP_UNSIGNED));
|
||||
} else {
|
||||
return stream.readBits(propDefinition.bitCount, !propDefinition.hasFlag(SendPropDefinition.flags.SPROP_UNSIGNED));
|
||||
return stream.readBits(propDefinition.bitCount, !propDefinition.hasFlag(SendPropFlag.SPROP_UNSIGNED));
|
||||
}
|
||||
}
|
||||
|
||||
static readArray(propDefinition, stream) {
|
||||
static readArray(propDefinition: SendPropDefinition, stream: BitStream): SendPropArrayValue[] {
|
||||
let maxElements = propDefinition.numElements;
|
||||
let numBits = 1;
|
||||
while ((maxElements >>= 1) != 0)
|
||||
numBits++;
|
||||
|
||||
const count = stream.readBits(numBits);
|
||||
const values = [];
|
||||
const values: SendPropArrayValue[] = [];
|
||||
if (!propDefinition.arrayProperty) {
|
||||
throw new Error('Array of undefniend type');
|
||||
}
|
||||
for (let i = 0; i < count; i++) {
|
||||
values.push(SendPropParser.decode(propDefinition.arrayProperty, stream));
|
||||
const value = SendPropParser.decode(propDefinition.arrayProperty, stream);
|
||||
if (value instanceof Array) {
|
||||
throw new Error('Nested arrays not supported');
|
||||
}
|
||||
values.push();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
static readString(stream) {
|
||||
static readString(stream: BitStream): string {
|
||||
const length = stream.readBits(9);
|
||||
return stream.readASCIIString(length);
|
||||
}
|
||||
|
||||
static readVector(propDefinition, stream) {
|
||||
static readVector(propDefinition: SendPropDefinition, stream: BitStream): Vector {
|
||||
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;
|
||||
const z = (propDefinition.hasFlag(SendPropFlag.SPROP_NORMAL)) ? SendPropParser.readFloat(propDefinition, stream) : 0;
|
||||
return new Vector(x, y, z);
|
||||
}
|
||||
|
||||
static readVectorXY(propDefinition, stream) {
|
||||
static readVectorXY(propDefinition: SendPropDefinition, stream: BitStream): Vector {
|
||||
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)) {
|
||||
static readFloat(propDefinition: SendPropDefinition, stream: BitStream): number {
|
||||
if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD)) {
|
||||
throw new Error("not implemented");
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_COORD_MP)) {
|
||||
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP)) {
|
||||
return SendPropParser.readBitCoord(stream, false, false);
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_COORD_MP_LOWPRECISION)) {
|
||||
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP_LOWPRECISION)) {
|
||||
return SendPropParser.readBitCoord(stream, false, true);
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_COORD_MP_INTEGRAL)) {
|
||||
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_COORD_MP_INTEGRAL)) {
|
||||
return SendPropParser.readBitCoord(stream, true, false);
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_NOSCALE)) {
|
||||
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_NOSCALE)) {
|
||||
return stream.readFloat32();
|
||||
} else if (propDefinition.hasFlag(SendPropDefinition.flags.SPROP_NORMAL)) {
|
||||
} else if (propDefinition.hasFlag(SendPropFlag.SPROP_NORMAL)) {
|
||||
throw new Error("not implemented");
|
||||
} else {
|
||||
const raw = stream.readBits(propDefinition.bitCount);
|
||||
|
|
@ -93,7 +103,7 @@ export class SendPropParser {
|
|||
}
|
||||
}
|
||||
|
||||
static readBitCoord(stream, isIntegral, isLowPrecision) {
|
||||
static readBitCoord(stream: BitStream, isIntegral: boolean, isLowPrecision: boolean): number {
|
||||
let value = 0;
|
||||
let isNegative = false;
|
||||
const inBounds = stream.readBoolean();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue