1
0
Fork 0
mirror of https://github.com/demostf/demo.js synced 2026-06-04 00:54:14 +02:00

stricter packet handler types

This commit is contained in:
Robin Appelman 2017-09-02 01:49:24 +02:00
commit df1aac6575
9 changed files with 262 additions and 145 deletions

View file

@ -6,34 +6,37 @@ import {ServerClass} from './ServerClass';
import {StringTable, StringTableEntry} from './StringTable';
import {Vector} from './Vector';
export interface StringTablePacket {
export interface BasePacket {
}
export interface StringTablePacket extends BasePacket {
packetType: 'stringTable';
tables: StringTable[];
}
export interface CreateStringTablePacket {
export interface CreateStringTablePacket extends BasePacket {
packetType: 'createStringTable';
table: StringTable;
}
export interface UpdateStringTablePacket {
export interface UpdateStringTablePacket extends BasePacket {
packetType: 'updateStringTable';
entries: StringTableEntry[];
tableId: number;
}
export interface ConsoleCmdPacket {
export interface ConsoleCmdPacket extends BasePacket {
packetType: 'consoleCmd';
command: string;
}
export interface DataTablePacket {
export interface DataTablePacket extends BasePacket {
packetType: 'dataTable';
tables: SendTable[];
serverClasses: ServerClass[];
}
export interface BSPDecalPacket {
export interface BSPDecalPacket extends BasePacket {
packetType: 'bspDecal';
position: Vector;
textureIndex: number;
@ -42,7 +45,7 @@ export interface BSPDecalPacket {
lowPriority: boolean;
}
export interface ClassInfoPacket {
export interface ClassInfoPacket extends BasePacket {
packetType: 'classInfo';
number: number;
create: boolean;
@ -53,24 +56,24 @@ export interface ClassInfoPacket {
}[];
}
export interface EntityMessagePacket {
export interface EntityMessagePacket extends BasePacket {
packetType: 'entityMessage';
classId: number;
length: number;
data: string;
}
export interface GameEventPacket {
export interface GameEventPacket extends BasePacket {
packetType: 'gameEvent';
event: GameEvent;
}
export interface GameEventListPacket {
export interface GameEventListPacket extends BasePacket {
packetType: 'gameEventList';
eventList: GameEventDefinitionMap;
}
export interface PacketEntitiesPacket {
export interface PacketEntitiesPacket extends BasePacket {
packetType: 'packetEntities';
entities: PacketEntity[];
removedEntities: number[];
@ -83,7 +86,7 @@ export interface PacketEntitiesPacket {
updatedBaseLine: boolean;
}
export interface ParseSoundsPacket {
export interface ParseSoundsPacket extends BasePacket {
packetType: 'parseSounds';
reliable: boolean;
num: number;
@ -91,17 +94,17 @@ export interface ParseSoundsPacket {
data: BitStream;
}
export interface SetConVarPacket {
export interface SetConVarPacket extends BasePacket {
packetType: 'setConVar';
vars: { [key: string]: string };
vars: {[key: string]: string};
}
export interface TempEntitiesPacket {
export interface TempEntitiesPacket extends BasePacket {
packetType: 'tempEntities';
entities: PacketEntity[];
}
export interface SayText2Packet {
export interface SayText2Packet extends BasePacket {
packetType: 'sayText2';
client: number;
raw: number;
@ -110,26 +113,26 @@ export interface SayText2Packet {
text: string;
}
export interface TextMessagePacket {
export interface TextMessagePacket extends BasePacket {
packetType: 'textMsg';
destType: number;
text: string;
}
export interface UnknownUserMessagePacket {
export interface UnknownUserMessagePacket extends BasePacket {
packetType: 'unknownUserMessage';
type: number;
data: BitStream;
}
export interface VoiceInitPacket {
export interface VoiceInitPacket extends BasePacket {
packetType: 'voiceInit';
codec: string;
quality: number;
extraData: number;
}
export interface VoiceDataPacket {
export interface VoiceDataPacket extends BasePacket {
packetType: 'voiceData';
client: number;
proximity: number;
@ -137,19 +140,101 @@ export interface VoiceDataPacket {
data: BitStream;
}
export interface MenuPacket {
export interface MenuPacket extends BasePacket {
packetType: 'menu';
type: number;
length: number;
data: BitStream;
}
export interface CmdKeyValuesPacket {
export interface CmdKeyValuesPacket extends BasePacket {
packetType: 'cmdKeyValues';
length: number;
data: BitStream;
}
export interface VoidPacket extends BasePacket {
packetType: 'void';
}
export interface FilePacket extends BasePacket {
packetType: 'file';
transferId: number;
fileName: string;
requested: boolean;
}
export interface NetTickPacket extends BasePacket {
packetType: 'netTick';
tick: number;
frameTime: number;
stdDev: number;
}
export interface StringCmdPacket extends BasePacket {
packetType: 'stringCmd';
command: string;
}
export interface SigOnStatePacket extends BasePacket {
packetType: 'sigOnState';
state: number;
count: number;
}
export interface PrintPacket extends BasePacket {
packetType: 'print';
value: string;
}
export interface ServerInfoPacket extends BasePacket {
packetType: 'serverInfo';
version: number;
serverCount: number;
stv: boolean;
dedicated: boolean;
maxCrc: number;
maxClasses: number;
mapHash: number;
playerCount: number;
maxPlayerCount: number;
intervalPerTick: number;
platform: string;
game: string;
skybox: string;
serverName: string;
replay: boolean;
}
export interface SetPausePacket extends BasePacket {
packetType: 'setPause';
paused: boolean;
}
export interface SetViewPacket extends BasePacket {
packetType: 'setView';
index: number;
}
export interface FixAnglePacket extends BasePacket {
packetType: 'fixAngle';
relative: boolean;
x: number;
y: number;
z: number;
}
export interface PreFetchPacket {
packetType: 'preFetch';
index: number;
}
export interface GetCvarValuePacket {
packetType: 'getCvarValue';
cookie: number;
value: string;
}
export type UserMessagePacket = SayText2Packet | TextMessagePacket | UnknownUserMessagePacket;
export type Packet = BSPDecalPacket |
@ -170,4 +255,46 @@ export type Packet = BSPDecalPacket |
VoiceDataPacket |
MenuPacket |
ConsoleCmdPacket |
CmdKeyValuesPacket;
CmdKeyValuesPacket |
VoidPacket |
FilePacket |
NetTickPacket |
StringCmdPacket |
SigOnStatePacket |
PrintPacket |
ServerInfoPacket |
SetPausePacket |
SetViewPacket |
FixAnglePacket |
PreFetchPacket |
GetCvarValuePacket;
export enum PacketType {
file = 2,
netTick = 3,
stringCmd = 4,
setConVar = 5,
sigOnState = 6,
print = 7,
serverInfo = 8,
classInfo = 10,
setPause = 11,
createStringTable = 12,
updateStringTable = 13,
voiceInit = 14,
voiceData = 15,
parseSounds = 17,
setView = 18,
fixAngle = 19,
bspDecal = 21,
userMessage = 23,
entityMessage = 24,
gameEvent = 25,
packetEntities = 26,
tempEntities = 27,
preFetch = 28,
menu = 29,
gameEventList = 30,
getCvarValue = 31,
cmdKeyValues = 32,
}

View file

@ -1,7 +1,7 @@
import {BitStream} from 'bit-buffer';
import {Parser} from './Parser';
import {PacketType} from './Parser/Message/Packet';
import {StreamDemo} from './StreamDemo';
import {PacketType} from './Data/Packet';
export {StreamDemo} from './StreamDemo';
@ -20,6 +20,7 @@ export class Demo {
}
public stream: BitStream;
public parser: Parser | null;
constructor(arrayBuffer: ArrayBuffer) {

View file

@ -4,10 +4,11 @@ import {Header} from './Data/Header';
import {Match} from './Data/Match';
import {ConsoleCmd} from './Parser/Message/ConsoleCmd';
import {DataTable} from './Parser/Message/DataTable';
import {Packet, PacketType} from './Parser/Message/Packet';
import {Packet} from './Parser/Message/Packet';
import {Parser as MessageParser} from './Parser/Message/Parser';
import {StringTable} from './Parser/Message/StringTable';
import {UserCmd} from './Parser/Message/UserCmd';
import {PacketType} from './Data/Packet';
export class Parser extends EventEmitter {
public stream: BitStream;

View file

@ -1,12 +1,11 @@
import {make} from '../Packet/ParserGenerator';
import {EncodeBSPDecal, ParseBSPDecal} from '../Packet/BSPDecal';
import {EncodeClassInfo, ParseClassInfo} from '../Packet/ClassInfo';
import {EncodeCreateStringTable, ParseCreateStringTable} from '../Packet/CreateStringTable';
import {ParseGameEvent} from '../Packet/GameEvent';
import {EncodeGameEventList, ParseGameEventList} from '../Packet/GameEventList';
import {ParsePacketEntities} from '../Packet/PacketEntities';
import {PacketParserMap, voidEncoder} from '../Packet/Parser';
import {PacketHandler, voidEncoder} from '../Packet/Parser';
import {EncodeParseSounds, ParseParseSounds} from '../Packet/ParseSounds';
import {EncodeSetConVar, ParseSetConVar} from '../Packet/SetConVar';
import {ParseTempEntities} from '../Packet/TempEntities';
@ -16,60 +15,82 @@ import {EncodeVoiceData, ParseVoiceData} from '../Packet/VoiceData';
import {EncodeVoiceInit, ParseVoiceInit} from '../Packet/VoiceInit';
import {Parser} from './Parser';
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
import {Packet as IPacket, PacketType} from '../../Data/Packet';
export class Packet extends Parser {
private static parsers: PacketParserMap = {
2: make('file', 'transferId{32}fileName{s}requested{b}'),
3: make('netTick', 'tick{32}frameTime{16}stdDev{16}'),
4: make('stringCmd', 'command{s}'),
5: {parser: ParseSetConVar, encoder: EncodeSetConVar},
6: make('sigOnState', 'state{8}count{32}'),
7: make('print', 'value{s}'),
8: 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: {parser: ParseClassInfo, encoder: EncodeClassInfo},
11: make('setPause', 'paused{b}'),
12: {parser: ParseCreateStringTable, encoder: EncodeCreateStringTable},
13: {parser: ParseUpdateStringTable, encoder: EncodeUpdateStringTable},
14: {parser: ParseVoiceInit, encoder: EncodeVoiceInit},
15: {parser: ParseVoiceData, encoder: EncodeVoiceData},
17: {parser: ParseParseSounds, encoder: EncodeParseSounds},
18: make('setView', 'index{11}'),
19: make('fixAngle', 'relative{b}x{16}y{16}z{16}'),
21: {parser: ParseBSPDecal, encoder: EncodeBSPDecal},
23: {parser: ParseUserMessage, encoder: voidEncoder},
24: make('entityMessage', 'index{11}classId{9}length{11}data{$length}'),
25: {parser: ParseGameEvent, encoder: voidEncoder},
26: {parser: ParsePacketEntities, encoder: voidEncoder},
27: {parser: ParseTempEntities, encoder: voidEncoder},
28: make('preFetch', 'index{14}'),
29: make('menu', 'type{u16}length{u16}data{$length*8}'),
30: {parser: ParseGameEventList, encoder: EncodeGameEventList},
31: make('getCvarValue', 'cookie{32}value{s}'),
32: make('cmdKeyValues', 'length{32}data{$length}'),
};
private static parsers: Map<PacketType, PacketHandler<IPacket>> = new Map([
[PacketType.file,
make('file', 'transferId{32}fileName{s}requested{b}')],
[PacketType.netTick,
make('netTick', 'tick{32}frameTime{16}stdDev{16}')],
[PacketType.stringCmd,
make('stringCmd', 'command{s}')],
[PacketType.setConVar,
{parser: ParseSetConVar, encoder: EncodeSetConVar}],
[PacketType.sigOnState,
make('sigOnState', 'state{8}count{32}')],
[PacketType.print,
make('print', 'value{s}')],
[PacketType.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}')],
[PacketType.classInfo,
{parser: ParseClassInfo, encoder: EncodeClassInfo}],
[PacketType.setPause,
make('setPause', 'paused{b}')],
[PacketType.createStringTable,
{parser: ParseCreateStringTable, encoder: EncodeCreateStringTable}],
[PacketType.updateStringTable,
{parser: ParseUpdateStringTable, encoder: EncodeUpdateStringTable}],
[PacketType.voiceInit,
{parser: ParseVoiceInit, encoder: EncodeVoiceInit}],
[PacketType.voiceData,
{parser: ParseVoiceData, encoder: EncodeVoiceData}],
[PacketType.parseSounds,
{parser: ParseParseSounds, encoder: EncodeParseSounds}],
[PacketType.setView,
make('setView', 'index{11}')],
[PacketType.fixAngle,
make('fixAngle', 'relative{b}x{16}y{16}z{16}')],
[PacketType.bspDecal,
{parser: ParseBSPDecal, encoder: EncodeBSPDecal}],
[PacketType.userMessage,
{parser: ParseUserMessage, encoder: voidEncoder}],
[PacketType.entityMessage,
make('entityMessage', 'index{11}classId{9}length{11}data{$length}')],
[PacketType.gameEvent,
{parser: ParseGameEvent, encoder: voidEncoder}],
[PacketType.packetEntities,
{parser: ParsePacketEntities, encoder: voidEncoder}],
[PacketType.tempEntities,
{parser: ParseTempEntities, encoder: voidEncoder}],
[PacketType.preFetch,
make('preFetch', 'index{14}')],
[PacketType.menu,
make('menu', 'type{u16}length{u16}data{$length*8}')],
[PacketType.gameEventList,
{parser: ParseGameEventList, encoder: EncodeGameEventList}],
[PacketType.getCvarValue,
make('getCvarValue', 'cookie{32}value{s}')],
[PacketType.cmdKeyValues,
make('cmdKeyValues', 'length{32}data{$length}')],
]);
public parse() {
const packets: IPacket[] = [];
let lastPacketType = 0;
while (this.bitsLeft > 6) { // last 6 bits for NOOP
const type = this.stream.readBits(6);
const type = this.stream.readBits(6) as PacketType;
if (type !== 0) {
if (Packet.parsers[type]) {
const parser = Packet.parsers.get(type);
if (parser) {
const skip = this.skippedPackets.indexOf(type) !== -1;
const packet = Packet.parsers[type].parser.call(this, this.stream, this.match, skip);
const packet = parser.parser(this.stream, this.match, skip);
packets.push(packet);
} else {
throw new Error('Unknown packet type ' + type + ' just parsed a ' + PacketType[lastPacketType]);
throw new Error(`Unknown packet type ${type} just parsed a ${PacketType[lastPacketType]}`);
}
lastPacketType = type;
}
@ -81,33 +102,3 @@ export class Packet extends Parser {
return (this.length * 8) - this.stream.index;
}
}
export enum PacketType {
file = 2,
netTick = 3,
stringCmd = 4,
setConVar = 5,
sigOnState = 6,
print = 7,
serverInfo = 8,
classInfo = 10,
setPause = 11,
createStringTable = 12,
updateStringTable = 13,
voiceInit = 14,
voiceData = 15,
parseSounds = 17,
setView = 18,
fixAngle = 19,
bspDecal = 21,
userMessage = 23,
entityMessage = 24,
gameEvent = 25,
packetEntities = 26,
tempEntities = 27,
preFetch = 28,
menu = 29,
gameEventList = 30,
getCvarValue = 30,
cmdKeyValues = 32,
}

View file

@ -1,8 +1,7 @@
import {BitStream} from 'bit-buffer';
import {Match} from '../../Data/Match';
import {Packet} from '../../Data/Packet';
import {Packet, PacketType} from '../../Data/Packet';
import {MessageType} from '../../Parser';
import {PacketType} from './Packet';
export abstract class Parser {
protected type: any;
@ -13,11 +12,11 @@ export abstract class Parser {
protected skippedPackets: PacketType[];
constructor(type: MessageType, tick: number, stream: BitStream, length: number, match: Match, skippedPacket: PacketType[] = []) {
this.type = type;
this.tick = tick;
this.stream = stream;
this.length = length; // length in bytes
this.match = match;
this.type = type;
this.tick = tick;
this.stream = stream;
this.length = length; // length in bytes
this.match = match;
this.skippedPackets = skippedPacket;
}

View file

@ -1,19 +1,17 @@
import {BitStream} from 'bit-buffer';
import {Match} from '../../Data/Match';
import {Packet} from '../../Data/Packet';
import {Packet, PacketType, VoidPacket} from '../../Data/Packet';
export type Parser = (stream: BitStream, match?: Match, skip?: boolean) => Packet;
export type Encoder = (packet: Packet, stream: BitStream, match?: Match) => void;
export type Parser<P extends Packet> = (stream: BitStream, match?: Match, skip?: boolean) => P;
export type Encoder<P extends Packet> = (packet: P, stream: BitStream, match?: Match) => void;
export interface PacketHandler {
parser: Parser,
encoder: Encoder
export interface PacketHandler<P extends Packet> {
parser: Parser<P>,
encoder: Encoder<P>
}
export const voidEncoder: Encoder = () => {
export const voidEncoder: Encoder<VoidPacket> = () => {
return {
type: 'void'
};
};
export interface PacketParserMap {
[id: number]: PacketHandler;
}

View file

@ -1,35 +1,34 @@
import {Packet} from '../../Data/Packet';
import {PacketHandler} from './Parser';
import {Encoder, PacketHandler, Parser} from './Parser';
import {BitStream} from 'bit-buffer';
export function make(name: string, definition: string): PacketHandler {
export function make<P extends Packet>(name: P['packetType'], definition: string): PacketHandler<P> {
const parts = definition.split('}');
const items = parts.map((part) => {
return part.split('{');
}).filter(part => part[0]);
return {
parser: (stream) => {
const result = {
packetType: name,
};
try {
for (const group of items) {
const value = readItem(stream, group[1], result);
if (group[0] !== '_') {
result[group[0]] = value;
}
}
} catch (e) {
throw new Error('Failed reading pattern ' + definition + '. ' + e);
}
return result as Packet;
},
encoder: (packet, stream) => {
const parser: Parser<P> = (stream: BitStream) => {
const result = {
packetType: name,
};
try {
for (const group of items) {
writeItem(stream, group[1], packet, packet[group[0]]);
const value = readItem(stream, group[1], result);
if (group[0] !== '_') {
result[group[0]] = value;
}
}
} catch (e) {
throw new Error('Failed reading pattern ' + definition + '. ' + e);
}
return result as P;
};
const encoder: Encoder<P> = (packet: P, stream: BitStream) => {
for (const group of items) {
writeItem(stream, group[1], packet, packet[group[0]]);
}
};
return {parser, encoder};
}
function readItem(stream: BitStream, description: string, data) {

View file

@ -3,14 +3,14 @@ import {BitStream} from 'bit-buffer';
import {assertEncoder, assertParser, getStream} from './PacketTest';
function assertGeneratedParser(definition: string, stream: BitStream, expected: any, length: number) {
expected.packetType = 'packetName';
const {parser} = make('packetName', definition);
expected.packetType = 'void';
const {parser} = make('void', definition);
return assertParser(parser, stream, expected, length);
}
function assertGeneratedEncoder(definition: string, data: any, length: number = 0) {
data.packetType = 'packetName';
const {parser, encoder} = make('packetName', definition);
data.packetType = 'void';
const {parser, encoder} = make('void', definition);
return assertEncoder(parser, encoder, data, length);
}

View file

@ -3,7 +3,8 @@
"lib": [
"dom",
"es2015.promise",
"es5"
"es5",
"ES2017"
],
"module": "ES6",
"target": "ES5",