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

@ -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) {