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 {StringTable, StringTableEntry} from './StringTable';
import {Vector} from './Vector'; import {Vector} from './Vector';
export interface StringTablePacket { export interface BasePacket {
}
export interface StringTablePacket extends BasePacket {
packetType: 'stringTable'; packetType: 'stringTable';
tables: StringTable[]; tables: StringTable[];
} }
export interface CreateStringTablePacket { export interface CreateStringTablePacket extends BasePacket {
packetType: 'createStringTable'; packetType: 'createStringTable';
table: StringTable; table: StringTable;
} }
export interface UpdateStringTablePacket { export interface UpdateStringTablePacket extends BasePacket {
packetType: 'updateStringTable'; packetType: 'updateStringTable';
entries: StringTableEntry[]; entries: StringTableEntry[];
tableId: number; tableId: number;
} }
export interface ConsoleCmdPacket { export interface ConsoleCmdPacket extends BasePacket {
packetType: 'consoleCmd'; packetType: 'consoleCmd';
command: string; command: string;
} }
export interface DataTablePacket { export interface DataTablePacket extends BasePacket {
packetType: 'dataTable'; packetType: 'dataTable';
tables: SendTable[]; tables: SendTable[];
serverClasses: ServerClass[]; serverClasses: ServerClass[];
} }
export interface BSPDecalPacket { export interface BSPDecalPacket extends BasePacket {
packetType: 'bspDecal'; packetType: 'bspDecal';
position: Vector; position: Vector;
textureIndex: number; textureIndex: number;
@ -42,7 +45,7 @@ export interface BSPDecalPacket {
lowPriority: boolean; lowPriority: boolean;
} }
export interface ClassInfoPacket { export interface ClassInfoPacket extends BasePacket {
packetType: 'classInfo'; packetType: 'classInfo';
number: number; number: number;
create: boolean; create: boolean;
@ -53,24 +56,24 @@ export interface ClassInfoPacket {
}[]; }[];
} }
export interface EntityMessagePacket { export interface EntityMessagePacket extends BasePacket {
packetType: 'entityMessage'; packetType: 'entityMessage';
classId: number; classId: number;
length: number; length: number;
data: string; data: string;
} }
export interface GameEventPacket { export interface GameEventPacket extends BasePacket {
packetType: 'gameEvent'; packetType: 'gameEvent';
event: GameEvent; event: GameEvent;
} }
export interface GameEventListPacket { export interface GameEventListPacket extends BasePacket {
packetType: 'gameEventList'; packetType: 'gameEventList';
eventList: GameEventDefinitionMap; eventList: GameEventDefinitionMap;
} }
export interface PacketEntitiesPacket { export interface PacketEntitiesPacket extends BasePacket {
packetType: 'packetEntities'; packetType: 'packetEntities';
entities: PacketEntity[]; entities: PacketEntity[];
removedEntities: number[]; removedEntities: number[];
@ -83,7 +86,7 @@ export interface PacketEntitiesPacket {
updatedBaseLine: boolean; updatedBaseLine: boolean;
} }
export interface ParseSoundsPacket { export interface ParseSoundsPacket extends BasePacket {
packetType: 'parseSounds'; packetType: 'parseSounds';
reliable: boolean; reliable: boolean;
num: number; num: number;
@ -91,17 +94,17 @@ export interface ParseSoundsPacket {
data: BitStream; data: BitStream;
} }
export interface SetConVarPacket { export interface SetConVarPacket extends BasePacket {
packetType: 'setConVar'; packetType: 'setConVar';
vars: { [key: string]: string }; vars: {[key: string]: string};
} }
export interface TempEntitiesPacket { export interface TempEntitiesPacket extends BasePacket {
packetType: 'tempEntities'; packetType: 'tempEntities';
entities: PacketEntity[]; entities: PacketEntity[];
} }
export interface SayText2Packet { export interface SayText2Packet extends BasePacket {
packetType: 'sayText2'; packetType: 'sayText2';
client: number; client: number;
raw: number; raw: number;
@ -110,26 +113,26 @@ export interface SayText2Packet {
text: string; text: string;
} }
export interface TextMessagePacket { export interface TextMessagePacket extends BasePacket {
packetType: 'textMsg'; packetType: 'textMsg';
destType: number; destType: number;
text: string; text: string;
} }
export interface UnknownUserMessagePacket { export interface UnknownUserMessagePacket extends BasePacket {
packetType: 'unknownUserMessage'; packetType: 'unknownUserMessage';
type: number; type: number;
data: BitStream; data: BitStream;
} }
export interface VoiceInitPacket { export interface VoiceInitPacket extends BasePacket {
packetType: 'voiceInit'; packetType: 'voiceInit';
codec: string; codec: string;
quality: number; quality: number;
extraData: number; extraData: number;
} }
export interface VoiceDataPacket { export interface VoiceDataPacket extends BasePacket {
packetType: 'voiceData'; packetType: 'voiceData';
client: number; client: number;
proximity: number; proximity: number;
@ -137,19 +140,101 @@ export interface VoiceDataPacket {
data: BitStream; data: BitStream;
} }
export interface MenuPacket { export interface MenuPacket extends BasePacket {
packetType: 'menu'; packetType: 'menu';
type: number; type: number;
length: number; length: number;
data: BitStream; data: BitStream;
} }
export interface CmdKeyValuesPacket { export interface CmdKeyValuesPacket extends BasePacket {
packetType: 'cmdKeyValues'; packetType: 'cmdKeyValues';
length: number; length: number;
data: BitStream; 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 UserMessagePacket = SayText2Packet | TextMessagePacket | UnknownUserMessagePacket;
export type Packet = BSPDecalPacket | export type Packet = BSPDecalPacket |
@ -170,4 +255,46 @@ export type Packet = BSPDecalPacket |
VoiceDataPacket | VoiceDataPacket |
MenuPacket | MenuPacket |
ConsoleCmdPacket | 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 {BitStream} from 'bit-buffer';
import {Parser} from './Parser'; import {Parser} from './Parser';
import {PacketType} from './Parser/Message/Packet';
import {StreamDemo} from './StreamDemo'; import {StreamDemo} from './StreamDemo';
import {PacketType} from './Data/Packet';
export {StreamDemo} from './StreamDemo'; export {StreamDemo} from './StreamDemo';
@ -20,6 +20,7 @@ export class Demo {
} }
public stream: BitStream; public stream: BitStream;
public parser: Parser | null; public parser: Parser | null;
constructor(arrayBuffer: ArrayBuffer) { constructor(arrayBuffer: ArrayBuffer) {

View file

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

View file

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

View file

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

View file

@ -1,14 +1,13 @@
import {Packet} from '../../Data/Packet'; import {Packet} from '../../Data/Packet';
import {PacketHandler} from './Parser'; import {Encoder, PacketHandler, Parser} from './Parser';
import {BitStream} from 'bit-buffer'; 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 parts = definition.split('}');
const items = parts.map((part) => { const items = parts.map((part) => {
return part.split('{'); return part.split('{');
}).filter(part => part[0]); }).filter(part => part[0]);
return { const parser: Parser<P> = (stream: BitStream) => {
parser: (stream) => {
const result = { const result = {
packetType: name, packetType: name,
}; };
@ -22,14 +21,14 @@ export function make(name: string, definition: string): PacketHandler {
} catch (e) { } catch (e) {
throw new Error('Failed reading pattern ' + definition + '. ' + e); throw new Error('Failed reading pattern ' + definition + '. ' + e);
} }
return result as Packet; return result as P;
}, };
encoder: (packet, stream) => { const encoder: Encoder<P> = (packet: P, stream: BitStream) => {
for (const group of items) { for (const group of items) {
writeItem(stream, group[1], packet, packet[group[0]]); writeItem(stream, group[1], packet, packet[group[0]]);
} }
}
}; };
return {parser, encoder};
} }
function readItem(stream: BitStream, description: string, data) { function readItem(stream: BitStream, description: string, data) {

View file

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

View file

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