mirror of
https://github.com/demostf/demo.js
synced 2026-06-04 00:54:14 +02:00
start splitting parserstate from analysing
This commit is contained in:
parent
d8f01428c5
commit
198fe0b1ba
18 changed files with 459 additions and 421 deletions
|
|
@ -1,5 +1,7 @@
|
|||
import {ConsoleCmdPacket} from '../../Data/Packet';
|
||||
import {Parser} from './Parser';
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {ConsoleCmdMessage, MessageHandler, MessageType} from '../../Data/Message';
|
||||
|
||||
export class ConsoleCmd extends Parser {
|
||||
public parse(): ConsoleCmdPacket[] {
|
||||
|
|
@ -9,3 +11,17 @@ export class ConsoleCmd extends Parser {
|
|||
}];
|
||||
}
|
||||
}
|
||||
|
||||
export const ConsoleCmdHandler: MessageHandler<ConsoleCmdMessage> = {
|
||||
parseMessage: (stream: BitStream, tick: number) => {
|
||||
return {
|
||||
type: MessageType.ConsoleCmd,
|
||||
tick,
|
||||
rawData: stream,
|
||||
command: stream.readUTF8String()
|
||||
};
|
||||
},
|
||||
encodeMessage: (message: ConsoleCmdMessage, stream: BitStream) => {
|
||||
stream.writeUTF8String(message.command);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
import {DataTablePacket} from '../../Data/Packet';
|
||||
import {SendPropDefinition, SendPropFlag, SendPropType} from '../../Data/SendPropDefinition';
|
||||
import {SendTable} from '../../Data/SendTable';
|
||||
import {ServerClass} from '../../Data/ServerClass';
|
||||
import {Parser} from './Parser';
|
||||
import {DataTablesMessage, MessageHandler, MessageType} from '../../Data/Message';
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
export class DataTable extends Parser {
|
||||
public parse(): DataTablePacket[] {
|
||||
export const DataTableHandler: MessageHandler<DataTablesMessage> = {
|
||||
parseMessage: (stream: BitStream, tick: number) => {
|
||||
// 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
|
||||
const tables: SendTable[] = [];
|
||||
const tables: SendTable[] = [];
|
||||
const tableMap: {[key: string]: SendTable} = {};
|
||||
while (this.stream.readBoolean()) {
|
||||
const needsDecoder = this.stream.readBoolean();
|
||||
const tableName = this.stream.readASCIIString();
|
||||
const numProps = this.stream.readBits(10);
|
||||
const table = new SendTable(tableName);
|
||||
while (stream.readBoolean()) {
|
||||
const needsDecoder = stream.readBoolean();
|
||||
const tableName = stream.readASCIIString();
|
||||
const numProps = stream.readBits(10);
|
||||
const table = new SendTable(tableName);
|
||||
|
||||
// get props metadata
|
||||
let arrayElementProp;
|
||||
for (let i = 0; i < numProps; i++) {
|
||||
const propType = this.stream.readBits(5);
|
||||
const propName = this.stream.readASCIIString();
|
||||
const propType = stream.readBits(5);
|
||||
const propName = stream.readASCIIString();
|
||||
const nFlagsBits = 16; // might be 11 (old?), 13 (new?), 16(networked) or 17(??)
|
||||
const flags = this.stream.readBits(nFlagsBits);
|
||||
const prop = new SendPropDefinition(propType, propName, flags, tableName);
|
||||
const flags = stream.readBits(nFlagsBits);
|
||||
const prop = new SendPropDefinition(propType, propName, flags, tableName);
|
||||
if (propType === SendPropType.DPT_DataTable) {
|
||||
prop.excludeDTName = this.stream.readASCIIString();
|
||||
prop.excludeDTName = stream.readASCIIString();
|
||||
} else {
|
||||
if (prop.isExcludeProp()) {
|
||||
prop.excludeDTName = this.stream.readASCIIString();
|
||||
prop.excludeDTName = stream.readASCIIString();
|
||||
} else if (prop.type === SendPropType.DPT_Array) {
|
||||
prop.numElements = this.stream.readBits(10);
|
||||
prop.numElements = stream.readBits(10);
|
||||
} else {
|
||||
prop.lowValue = this.stream.readFloat32();
|
||||
prop.highValue = this.stream.readFloat32();
|
||||
prop.bitCount = this.stream.readBits(7);
|
||||
prop.lowValue = stream.readFloat32();
|
||||
prop.highValue = stream.readFloat32();
|
||||
prop.bitCount = stream.readBits(7);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ export class DataTable extends Parser {
|
|||
throw new Error('expected prop of type array');
|
||||
}
|
||||
prop.arrayProperty = arrayElementProp;
|
||||
arrayElementProp = null;
|
||||
arrayElementProp = null;
|
||||
}
|
||||
|
||||
if (prop.hasFlag(SendPropFlag.SPROP_INSIDEARRAY)) {
|
||||
|
|
@ -78,38 +78,43 @@ export class DataTable extends Parser {
|
|||
for (const prop of table.props) {
|
||||
if (prop.type === SendPropType.DPT_DataTable) {
|
||||
if (prop.excludeDTName) {
|
||||
prop.table = tableMap[prop.excludeDTName];
|
||||
prop.table = tableMap[prop.excludeDTName];
|
||||
prop.excludeDTName = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const numServerClasses = this.stream.readUint16(); // short
|
||||
const numServerClasses = stream.readUint16(); // short
|
||||
const serverClasses: ServerClass[] = [];
|
||||
if (numServerClasses <= 0) {
|
||||
throw new Error('expected one or more serverclasses');
|
||||
}
|
||||
|
||||
for (let i = 0; i < numServerClasses; i++) {
|
||||
const classId = this.stream.readUint16();
|
||||
const classId = stream.readUint16();
|
||||
if (classId > numServerClasses) {
|
||||
throw new Error('invalid class id');
|
||||
}
|
||||
const className = this.stream.readASCIIString();
|
||||
const dataTable = this.stream.readASCIIString();
|
||||
const className = stream.readASCIIString();
|
||||
const dataTable = stream.readASCIIString();
|
||||
serverClasses.push(new ServerClass(classId, className, dataTable));
|
||||
}
|
||||
|
||||
const bitsLeft = (this.length * 8) - this.stream.index;
|
||||
const bitsLeft = (this.length * 8) - stream.index;
|
||||
if (bitsLeft > 7 || bitsLeft < 0) {
|
||||
throw new Error('unexpected remaining data in datatable (' + bitsLeft + ' bits)');
|
||||
}
|
||||
|
||||
return [{
|
||||
packetType: 'dataTable',
|
||||
return {
|
||||
type: MessageType.DataTables,
|
||||
tick,
|
||||
rawData: stream,
|
||||
tables,
|
||||
serverClasses,
|
||||
}];
|
||||
};
|
||||
},
|
||||
encodeMessage: (message, stream) => {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,83 +13,85 @@ import {EncodeUpdateStringTable, ParseUpdateStringTable} from '../Packet/UpdateS
|
|||
import {EncodeUserMessage, ParseUserMessage} from '../Packet/UserMessage';
|
||||
import {EncodeVoiceData, ParseVoiceData} from '../Packet/VoiceData';
|
||||
import {EncodeVoiceInit, ParseVoiceInit} from '../Packet/VoiceInit';
|
||||
import {Parser} from './Parser';
|
||||
|
||||
import {Packet as IPacket, PacketTypeId} from '../../Data/Packet';
|
||||
import {MessageHandler, MessageType, PacketMessage} from '../../Data/Message';
|
||||
import {BitStream} from 'bit-buffer';
|
||||
import {ParserState} from '../../Data/ParserState';
|
||||
|
||||
type PacketHandlerMap = Map<PacketTypeId, PacketHandler<IPacket>>;
|
||||
|
||||
export class Packet extends Parser {
|
||||
private static handlers: PacketHandlerMap = new Map<PacketTypeId, PacketHandler<IPacket>>([
|
||||
[PacketTypeId.file,
|
||||
make('file', 'transferId{32}fileName{s}requested{b}')],
|
||||
[PacketTypeId.netTick,
|
||||
make('netTick', 'tick{32}frameTime{16}stdDev{16}')],
|
||||
[PacketTypeId.stringCmd,
|
||||
make('stringCmd', 'command{s}')],
|
||||
[PacketTypeId.setConVar,
|
||||
{parser: ParseSetConVar, encoder: EncodeSetConVar}],
|
||||
[PacketTypeId.sigOnState,
|
||||
make('sigOnState', 'state{8}count{32}')],
|
||||
[PacketTypeId.print,
|
||||
make('print', 'value{s}')],
|
||||
[PacketTypeId.serverInfo,
|
||||
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}')],
|
||||
[PacketTypeId.classInfo,
|
||||
{parser: ParseClassInfo, encoder: EncodeClassInfo}],
|
||||
[PacketTypeId.setPause,
|
||||
make('setPause', 'paused{b}')],
|
||||
[PacketTypeId.createStringTable,
|
||||
{parser: ParseCreateStringTable, encoder: EncodeCreateStringTable}],
|
||||
[PacketTypeId.updateStringTable,
|
||||
{parser: ParseUpdateStringTable, encoder: EncodeUpdateStringTable}],
|
||||
[PacketTypeId.voiceInit,
|
||||
{parser: ParseVoiceInit, encoder: EncodeVoiceInit}],
|
||||
[PacketTypeId.voiceData,
|
||||
{parser: ParseVoiceData, encoder: EncodeVoiceData}],
|
||||
[PacketTypeId.parseSounds,
|
||||
{parser: ParseParseSounds, encoder: EncodeParseSounds}],
|
||||
[PacketTypeId.setView,
|
||||
make('setView', 'index{11}')],
|
||||
[PacketTypeId.fixAngle,
|
||||
make('fixAngle', 'relative{b}x{16}y{16}z{16}')],
|
||||
[PacketTypeId.bspDecal,
|
||||
{parser: ParseBSPDecal, encoder: EncodeBSPDecal}],
|
||||
[PacketTypeId.userMessage,
|
||||
{parser: ParseUserMessage, encoder: EncodeUserMessage}],
|
||||
[PacketTypeId.entityMessage,
|
||||
make('entityMessage', 'index{11}classId{9}length{11}data{$length}')],
|
||||
[PacketTypeId.gameEvent,
|
||||
{parser: ParseGameEvent, encoder: EncodeGameEvent}],
|
||||
[PacketTypeId.packetEntities,
|
||||
{parser: ParsePacketEntities, encoder: voidEncoder}],
|
||||
[PacketTypeId.tempEntities,
|
||||
{parser: ParseTempEntities, encoder: EncodeTempEntities}],
|
||||
[PacketTypeId.preFetch,
|
||||
make('preFetch', 'index{14}')],
|
||||
[PacketTypeId.menu,
|
||||
make('menu', 'type{u16}length{u16}data{$length*8}')],
|
||||
[PacketTypeId.gameEventList,
|
||||
{parser: ParseGameEventList, encoder: EncodeGameEventList}],
|
||||
[PacketTypeId.getCvarValue,
|
||||
make('getCvarValue', 'cookie{32}value{s}')],
|
||||
[PacketTypeId.cmdKeyValues,
|
||||
make('cmdKeyValues', 'length{32}data{$length}')],
|
||||
]);
|
||||
const handlers: PacketHandlerMap = new Map<PacketTypeId, PacketHandler<IPacket>>([
|
||||
[PacketTypeId.file,
|
||||
make('file', 'transferId{32}fileName{s}requested{b}')],
|
||||
[PacketTypeId.netTick,
|
||||
make('netTick', 'tick{32}frameTime{16}stdDev{16}')],
|
||||
[PacketTypeId.stringCmd,
|
||||
make('stringCmd', 'command{s}')],
|
||||
[PacketTypeId.setConVar,
|
||||
{parser: ParseSetConVar, encoder: EncodeSetConVar}],
|
||||
[PacketTypeId.sigOnState,
|
||||
make('sigOnState', 'state{8}count{32}')],
|
||||
[PacketTypeId.print,
|
||||
make('print', 'value{s}')],
|
||||
[PacketTypeId.serverInfo,
|
||||
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}')],
|
||||
[PacketTypeId.classInfo,
|
||||
{parser: ParseClassInfo, encoder: EncodeClassInfo}],
|
||||
[PacketTypeId.setPause,
|
||||
make('setPause', 'paused{b}')],
|
||||
[PacketTypeId.createStringTable,
|
||||
{parser: ParseCreateStringTable, encoder: EncodeCreateStringTable}],
|
||||
[PacketTypeId.updateStringTable,
|
||||
{parser: ParseUpdateStringTable, encoder: EncodeUpdateStringTable}],
|
||||
[PacketTypeId.voiceInit,
|
||||
{parser: ParseVoiceInit, encoder: EncodeVoiceInit}],
|
||||
[PacketTypeId.voiceData,
|
||||
{parser: ParseVoiceData, encoder: EncodeVoiceData}],
|
||||
[PacketTypeId.parseSounds,
|
||||
{parser: ParseParseSounds, encoder: EncodeParseSounds}],
|
||||
[PacketTypeId.setView,
|
||||
make('setView', 'index{11}')],
|
||||
[PacketTypeId.fixAngle,
|
||||
make('fixAngle', 'relative{b}x{16}y{16}z{16}')],
|
||||
[PacketTypeId.bspDecal,
|
||||
{parser: ParseBSPDecal, encoder: EncodeBSPDecal}],
|
||||
[PacketTypeId.userMessage,
|
||||
{parser: ParseUserMessage, encoder: EncodeUserMessage}],
|
||||
[PacketTypeId.entityMessage,
|
||||
make('entityMessage', 'index{11}classId{9}length{11}data{$length}')],
|
||||
[PacketTypeId.gameEvent,
|
||||
{parser: ParseGameEvent, encoder: EncodeGameEvent}],
|
||||
[PacketTypeId.packetEntities,
|
||||
{parser: ParsePacketEntities, encoder: voidEncoder}],
|
||||
[PacketTypeId.tempEntities,
|
||||
{parser: ParseTempEntities, encoder: EncodeTempEntities}],
|
||||
[PacketTypeId.preFetch,
|
||||
make('preFetch', 'index{14}')],
|
||||
[PacketTypeId.menu,
|
||||
make('menu', 'type{u16}length{u16}data{$length*8}')],
|
||||
[PacketTypeId.gameEventList,
|
||||
{parser: ParseGameEventList, encoder: EncodeGameEventList}],
|
||||
[PacketTypeId.getCvarValue,
|
||||
make('getCvarValue', 'cookie{32}value{s}')],
|
||||
[PacketTypeId.cmdKeyValues,
|
||||
make('cmdKeyValues', 'length{32}data{$length}')],
|
||||
]);
|
||||
|
||||
public parse() {
|
||||
export const PacketMessageHandler: MessageHandler<PacketMessage> = {
|
||||
parseMessage: (stream: BitStream, tick: number, state: ParserState) => {
|
||||
const packets: IPacket[] = [];
|
||||
let lastPacketType = 0;
|
||||
while (this.bitsLeft > 6) { // last 6 bits for NOOP
|
||||
const type = this.stream.readBits(6) as PacketTypeId;
|
||||
while (stream.bitsLeft > 6) { // last 6 bits for NOOP
|
||||
const type = stream.readBits(6) as PacketTypeId;
|
||||
if (type !== 0) {
|
||||
const parser = Packet.handlers.get(type);
|
||||
const parser = handlers.get(type);
|
||||
if (parser) {
|
||||
const skip = this.skippedPackets.indexOf(type) !== -1;
|
||||
const packet = parser.parser(this.stream, this.state, skip);
|
||||
const skip = state.skippedPackets.indexOf(type) !== -1;
|
||||
const packet = parser.parser(stream, state, skip);
|
||||
packets.push(packet);
|
||||
} else {
|
||||
throw new Error(`Unknown packet type ${type} just parsed a ${PacketTypeId[lastPacketType]}`);
|
||||
|
|
@ -97,10 +99,14 @@ export class Packet extends Parser {
|
|||
lastPacketType = type;
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
return {
|
||||
type: MessageType.Packet,
|
||||
tick,
|
||||
rawData: stream,
|
||||
packets
|
||||
};
|
||||
},
|
||||
encodeMessage: (message, stream) => {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
get bitsLeft() {
|
||||
return (this.length * 8) - this.stream.index;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {BitStream} from 'bit-buffer';
|
||||
import {Packet, PacketTypeId} from '../../Data/Packet';
|
||||
import {MessageType} from '../../Parser';
|
||||
import {MessageType} from '../../Data/Message';
|
||||
import {ParserState} from '../../Data/ParserState';
|
||||
|
||||
export abstract class Parser {
|
||||
|
|
|
|||
|
|
@ -1,45 +1,49 @@
|
|||
import {StringTablePacket} from '../../Data/Packet';
|
||||
import {StringTable as StringTableObject, StringTableEntry} from '../../Data/StringTable';
|
||||
import {Parser} from './Parser';
|
||||
import {MessageHandler, MessageType, StringTablesMessage} from '../../Data/Message';
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
export class StringTable extends Parser {
|
||||
public parse(): StringTablePacket[] {
|
||||
export const StringTableHandler: MessageHandler<StringTablesMessage> = {
|
||||
parseMessage: (stream: BitStream, tick: number) => {
|
||||
// we get the tables from the packets
|
||||
// return [{
|
||||
// packetType: 'stringTable',
|
||||
// tables: []
|
||||
// }];
|
||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/ST/StringTableParser.cs
|
||||
const tableCount = this.stream.readUint8();
|
||||
const tableCount = stream.readUint8();
|
||||
const tables: StringTableObject[] = [];
|
||||
let extraDataLength;
|
||||
for (let i = 0; i < tableCount; i++) {
|
||||
const entries: StringTableEntry[] = [];
|
||||
const tableName = this.stream.readASCIIString();
|
||||
const entryCount = this.stream.readUint16();
|
||||
const tableName = stream.readASCIIString();
|
||||
const entryCount = stream.readUint16();
|
||||
for (let j = 0; j < entryCount; j++) {
|
||||
let entry: StringTableEntry;
|
||||
try {
|
||||
entry = {
|
||||
text: this.stream.readUTF8String(),
|
||||
text: stream.readUTF8String(),
|
||||
};
|
||||
} catch (e) {
|
||||
return [{
|
||||
packetType: 'stringTable',
|
||||
return {
|
||||
type: MessageType.StringTables,
|
||||
tick,
|
||||
rawData: stream,
|
||||
tables,
|
||||
}];
|
||||
};
|
||||
}
|
||||
if (this.stream.readBoolean()) {
|
||||
extraDataLength = this.stream.readUint16();
|
||||
if ((extraDataLength * 8) > this.stream.bitsLeft) {
|
||||
if (stream.readBoolean()) {
|
||||
extraDataLength = stream.readUint16();
|
||||
if ((extraDataLength * 8) > stream.bitsLeft) {
|
||||
// extradata to long, can't continue parsing the tables
|
||||
// seems to happen in POV demos after the MyM update
|
||||
return [{
|
||||
packetType: 'stringTable',
|
||||
return {
|
||||
type: MessageType.StringTables,
|
||||
tick,
|
||||
rawData: stream,
|
||||
tables,
|
||||
}];
|
||||
};
|
||||
}
|
||||
entry.extraData = this.stream.readBitStream(extraDataLength * 8);
|
||||
entry.extraData = stream.readBitStream(extraDataLength * 8);
|
||||
}
|
||||
entries.push(entry);
|
||||
}
|
||||
|
|
@ -49,18 +53,23 @@ export class StringTable extends Parser {
|
|||
maxEntries: entryCount,
|
||||
};
|
||||
tables.push(table);
|
||||
if (this.stream.readBits(1)) {
|
||||
this.stream.readASCIIString();
|
||||
if (this.stream.readBits(1)) {
|
||||
if (stream.readBits(1)) {
|
||||
stream.readASCIIString();
|
||||
if (stream.readBits(1)) {
|
||||
// throw 'more extra data not implemented';
|
||||
extraDataLength = this.stream.readBits(16);
|
||||
this.stream.readBits(extraDataLength);
|
||||
extraDataLength = stream.readBits(16);
|
||||
stream.readBits(extraDataLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [{
|
||||
packetType: 'stringTable',
|
||||
return {
|
||||
type: MessageType.StringTables,
|
||||
tick,
|
||||
rawData: stream,
|
||||
tables,
|
||||
}];
|
||||
};
|
||||
},
|
||||
encodeMessage: (message, stream) => {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
import {Parser} from './Parser';
|
||||
import {MessageHandler, MessageType, UserCmdMessage} from '../../Data/Message';
|
||||
import {BitStream} from 'bit-buffer';
|
||||
|
||||
export class UserCmd extends Parser {
|
||||
public parse() {
|
||||
return [];
|
||||
export const UserCmdHandler: MessageHandler<UserCmdMessage> = {
|
||||
parseMessage: (stream: BitStream, tick: number) => {
|
||||
return {
|
||||
type: MessageType.UserCmd,
|
||||
tick,
|
||||
rawData: stream
|
||||
};
|
||||
},
|
||||
encodeMessage: (message, stream) => {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue