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

more splitting of analyser

This commit is contained in:
Robin Appelman 2017-09-23 18:39:00 +02:00
commit b11cd7eec0
9 changed files with 240 additions and 211 deletions

View file

@ -17,29 +17,29 @@ const echo = function (data) {
fs.readFile(argv._[0], function (err, data) { fs.readFile(argv._[0], function (err, data) {
if (err) throw err; if (err) throw err;
const demo = Demo.fromNodeBuffer(data); const demo = Demo.fromNodeBuffer(data);
const parser = demo.getParser(true); const analyser = demo.getAnalyser(true);
const head = parser.readHeader(); const head = analyser.getHeader();
if (argv.head) { if (argv.head) {
echo(head); echo(head);
return; return;
} }
const match = parser.parseBody(); const match = analyser.getBody();
if (argv['create-event-definitions']) { if (argv['create-event-definitions']) {
const definitions = Array.from(parser.match.eventDefinitions.values()); const definitions = Array.from(match.eventDefinitions.values());
const definition = definitions const definition = definitions
.map(createEventDefinition) .map(createEventDefinition)
.join('\n\n') .join('\n\n')
+ '\n\n' + createEventDefinitionUnion(definitions) + '\n\n' + '\n\n' + createEventDefinitionUnion(definitions) + '\n\n'
+ 'export type GameEventType = GameEvent[\'name\'];\n\n' + 'export type GameEventType = GameEvent[\'name\'];\n\n'
+ createEventTypeMap(definitions) + '\n\n' + createEventTypeMap(definitions) + '\n\n'
+ createEventTypeIdMap(parser.match.eventDefinitions) + '\n'; + createEventTypeIdMap(match.eventDefinitions) + '\n';
console.log(definition); console.log(definition);
} else if (argv['event-list']) { } else if (argv['event-list']) {
echo(Array.from(parser.match.eventDefinitions.values())); echo(Array.from(match.eventDefinitions.values()));
} else if (argv.dump) { } else if (argv.dump) {
echo(parser.match.packets); echo(match.packets);
} else if (argv.strings) { } else if (argv.strings) {
echo(parser.match.strings); echo(match.strings);
} else { } else {
echo(match.getState()); echo(match.getState());
} }

30
src/Analyser.ts Normal file
View file

@ -0,0 +1,30 @@
import {Parser} from './Parser';
import {Match} from './Data/Match';
import {EventEmitter} from 'events';
import {Header} from './Data/Header';
export class Analyser extends EventEmitter {
private parser: Parser;
private match: Match;
constructor(parser: Parser) {
super();
this.parser = parser;
}
public getHeader(): Header {
return this.parser.getHeader();
}
public getBody(): Match {
if (!this.match) {
this.match = new Match(this.parser.parserState);
for (const packet of this.parser.getPackets()) {
this.match.handlePacket(packet);
this.emit('packet', packet);
}
this.emit('done');
}
return this.match;
}
}

View file

@ -36,7 +36,11 @@ export class Match {
public teamEntityMap: Map<EntityId, Team> = new Map(); public teamEntityMap: Map<EntityId, Team> = new Map();
public buildings: Map<EntityId, Building> = new Map(); public buildings: Map<EntityId, Building> = new Map();
public playerResources: PlayerResource[] = []; public playerResources: PlayerResource[] = [];
public readonly parserState: ParserState = new ParserState(); public readonly parserState: ParserState;
constructor(parserState: ParserState) {
this.parserState = parserState;
}
public getState() { public getState() {
const users = {}; const users = {};

View file

@ -13,6 +13,7 @@ import {
} from '../PacketHandler/StringTable'; } from '../PacketHandler/StringTable';
import {handleGameEventList} from '../PacketHandler/GameEventList'; import {handleGameEventList} from '../PacketHandler/GameEventList';
import {DataTablesMessage, Message, MessageType, StringTablesMessage} from './Message'; import {DataTablesMessage, Message, MessageType, StringTablesMessage} from './Message';
import {handlePacketEntitiesForState} from '../PacketHandler/PacketEntities';
export class ParserState { export class ParserState {
public version: number = 0; public version: number = 0;
@ -44,6 +45,9 @@ export class ParserState {
case 'gameEventList': case 'gameEventList':
handleGameEventList(packet, this); handleGameEventList(packet, this);
break; break;
case 'packetEntities':
handlePacketEntitiesForState(packet, this);
break;
} }
} }

View file

@ -1,6 +1,7 @@
import {BitStream} from 'bit-buffer'; import {BitStream} from 'bit-buffer';
import {Parser} from './Parser'; import {Parser} from './Parser';
import {PacketTypeId} from './Data/Packet'; import {PacketTypeId} from './Data/Packet';
import {Analyser} from './Analyser';
export class Demo { export class Demo {
public static fromNodeBuffer(nodeBuffer) { public static fromNodeBuffer(nodeBuffer) {
@ -31,4 +32,8 @@ export class Demo {
} }
return this.parser; return this.parser;
} }
public getAnalyser(fastMode: boolean = false) {
return new Analyser(this.getParser(fastMode));
}
} }

View file

@ -7,24 +7,30 @@ import {SendProp} from '../Data/SendProp';
import {Vector} from '../Data/Vector'; import {Vector} from '../Data/Vector';
import {CWeaponMedigun, Weapon} from '../Data/Weapon'; import {CWeaponMedigun, Weapon} from '../Data/Weapon';
import {TeamNumber} from '../Data/Team'; import {TeamNumber} from '../Data/Team';
import {ParserState} from '../Data/ParserState';
export function handlePacketEntities(packet: PacketEntitiesPacket, match: Match) { export function handlePacketEntities(packet: PacketEntitiesPacket, match: Match) {
for (const removedEntityId of packet.removedEntities) {
match.parserState.entityClasses.delete(removedEntityId);
}
for (const entity of packet.entities) { for (const entity of packet.entities) {
saveEntity(entity, match);
handleEntity(entity, match); handleEntity(entity, match);
} }
} }
function saveEntity(packetEntity: PacketEntity, match: Match) { export function handlePacketEntitiesForState(packet: PacketEntitiesPacket, state: ParserState) {
if (packetEntity.pvs === PVS.DELETE) { for (const removedEntityId of packet.removedEntities) {
match.parserState.entityClasses.delete(packetEntity.entityIndex); state.entityClasses.delete(removedEntityId);
} }
match.parserState.entityClasses.set(packetEntity.entityIndex, packetEntity.serverClass); for (const entity of packet.entities) {
saveEntity(entity, state);
}
}
function saveEntity(packetEntity: PacketEntity, state: ParserState) {
if (packetEntity.pvs === PVS.DELETE) {
state.entityClasses.delete(packetEntity.entityIndex);
}
state.entityClasses.set(packetEntity.entityIndex, packetEntity.serverClass);
} }
function handleEntity(entity: PacketEntity, match: Match) { function handleEntity(entity: PacketEntity, match: Match) {

View file

@ -1,14 +1,13 @@
import {BitStream} from 'bit-buffer'; import {BitStream} from 'bit-buffer';
import {EventEmitter} from 'events';
import {Header} from './Data/Header'; import {Header} from './Data/Header';
import {Match} from './Data/Match';
import {ConsoleCmdHandler} from './Parser/Message/ConsoleCmd'; import {ConsoleCmdHandler} from './Parser/Message/ConsoleCmd';
import {DataTableHandler} from './Parser/Message/DataTable'; import {DataTableHandler} from './Parser/Message/DataTable';
import {PacketMessageHandler} from './Parser/Message/Packet'; import {PacketMessageHandler} from './Parser/Message/Packet';
import {StringTableHandler} from './Parser/Message/StringTable'; import {StringTableHandler} from './Parser/Message/StringTable';
import {UserCmdHandler} from './Parser/Message/UserCmd'; import {UserCmdHandler} from './Parser/Message/UserCmd';
import {PacketTypeId} from './Data/Packet'; import {Packet, PacketTypeId} from './Data/Packet';
import {Message, MessageHandler, MessageType, PacketMessage} from './Data/Message'; import {Message, MessageHandler, MessageType, PacketMessage} from './Data/Message';
import {ParserState} from './Data/ParserState';
const messageHandlers: Map<MessageType, MessageHandler<Message>> = new Map<MessageType, MessageHandler<Message>>([ const messageHandlers: Map<MessageType, MessageHandler<Message>> = new Map<MessageType, MessageHandler<Message>>([
[MessageType.Sigon, PacketMessageHandler], [MessageType.Sigon, PacketMessageHandler],
@ -19,39 +18,41 @@ const messageHandlers: Map<MessageType, MessageHandler<Message>> = new Map<Messa
[MessageType.StringTables, StringTableHandler], [MessageType.StringTables, StringTableHandler],
]); ]);
export class Parser extends EventEmitter { export class Parser {
public stream: BitStream; public readonly stream: BitStream;
public match: Match; public readonly parserState: ParserState;
protected skipPackets: PacketTypeId[]; private header: Header | null = null;
protected readonly skipPackets: PacketTypeId[];
public viewOrigin: number[][] = [[], []]; public viewOrigin: number[][] = [[], []];
public viewAngles: number[][] = [[], []]; public viewAngles: number[][] = [[], []];
constructor(stream: BitStream, skipPackets: PacketTypeId[] = []) { constructor(stream: BitStream, skipPackets: PacketTypeId[] = []) {
super();
this.stream = stream; this.stream = stream;
this.match = new Match(); this.parserState = new ParserState();
this.on('packet', this.match.handlePacket.bind(this.match));
this.skipPackets = skipPackets; this.skipPackets = skipPackets;
} }
public readHeader() { public getHeader() {
return this.parseHeader(this.stream); if (!this.header) {
this.header = this.parseHeader(this.stream);
}
return this.header;
} }
public parseBody() { public * getPackets(): Iterable<Packet> {
// ensure that we are past the header
this.getHeader();
const messages = this.getMessages(); const messages = this.getMessages();
for (const message of messages) { for (const message of messages) {
this.handleMessage(message); yield* this.handleMessage(message);
} }
this.emit('done', this.match);
return this.match;
} }
private * getMessages(): Iterable<Message> { private * getMessages(): Iterable<Message> {
let hasNext: boolean = true; let hasNext: boolean = true;
while (hasNext) { while (hasNext) {
const message = this.readMessage(this.stream, this.match); const message = this.readMessage(this.stream, this.parserState);
if (!message) { if (!message) {
hasNext = false; hasNext = false;
} else { } else {
@ -60,20 +61,12 @@ export class Parser extends EventEmitter {
} }
} }
public tick() { protected parseMessage(data: BitStream, type: MessageType, tick: number, state: ParserState): Message {
const message = this.readMessage(this.stream, this.match);
if (message) {
this.handleMessage(message);
}
return !!message;
}
protected parseMessage(data: BitStream, type: MessageType, tick: number, match: Match): Message {
const handler = messageHandlers.get(type); const handler = messageHandlers.get(type);
if (!handler) { if (!handler) {
throw new Error(`No handler for message of type ${MessageType[type]}`); throw new Error(`No handler for message of type ${MessageType[type]}`);
} }
return handler.parseMessage(data, tick, match.parserState); return handler.parseMessage(data, tick, state);
} }
protected parseHeader(stream): Header { protected parseHeader(stream): Header {
@ -92,17 +85,17 @@ export class Parser extends EventEmitter {
}; };
} }
protected handleMessage(message: Message) { protected * handleMessage(message: Message): Iterable<Packet> {
this.match.parserState.handleMessage(message); this.parserState.handleMessage(message);
if (message.type === MessageType.Packet) { if (message.type === MessageType.Packet) {
for (const packet of (message as PacketMessage).packets) { for (const packet of (message as PacketMessage).packets) {
this.match.parserState.handlePacket(packet); this.parserState.handlePacket(packet);
this.emit('packet', packet); yield packet;
} }
} }
} }
protected readMessage(stream: BitStream, match: Match): Message | false { protected readMessage(stream: BitStream, state: ParserState): Message | false {
if (stream.bitsLeft < 8) { if (stream.bitsLeft < 8) {
return false; return false;
} }
@ -143,6 +136,6 @@ export class Parser extends EventEmitter {
const length = stream.readInt32(); const length = stream.readInt32();
const buffer = stream.readBitStream(length * 8); const buffer = stream.readBitStream(length * 8);
return this.parseMessage(buffer, type, tick, match); return this.parseMessage(buffer, type, tick, state);
} }
} }

View file

@ -6,37 +6,57 @@ import {BitStream} from 'bit-buffer';
import * as split2 from 'split2'; import * as split2 from 'split2';
import {createUnzip, createGunzip} from 'zlib'; import {createUnzip, createGunzip} from 'zlib';
import {PassThrough} from 'stream'; import {PassThrough} from 'stream';
import {EntityId, PVS} from '../../Data/PacketEntity';
import {SendPropValue} from '../../Data/SendProp';
interface ResultData {
tick: number,
serverClass: string,
id: EntityId,
props: {[propName: string]: SendPropValue},
pvs: PVS
}
function writeEntities(name: string) { function writeEntities(name: string) {
const targetFile = `${__dirname}/../data/${name}_entities.json`; const targetFile = `${__dirname}/../data/${name}_entities.json`;
const source = readFileSync(`${__dirname}/../data/${name}.dem`); const source = readFileSync(`${__dirname}/../data/${name}.dem`);
const demo = Demo.fromNodeBuffer(source); const demo = Demo.fromNodeBuffer(source);
const parser = demo.getParser(false); const parser = demo.getParser(false);
parser.readHeader();
const match = parser.match; const resultData = getResultData(parser.getPackets());
const writeStream = createWriteStream(targetFile, 'utf8'); const writeStream = createWriteStream(targetFile, 'utf8');
parser.on('packet', (packet: Packet) => { for (const result of resultData) {
writeStream.write(JSON.stringify(result) + '\n');
}
writeStream.end();
}
function* getResultData(packets: Iterable<Packet>): IterableIterator<ResultData> {
let tick = 0;
for (const packet of packets) {
if (packet.packetType === 'netTick') {
tick = packet.tick;
}
if (packet.packetType === 'packetEntities') { if (packet.packetType === 'packetEntities') {
for (const entity of packet.entities) { for (const entity of packet.entities) {
const entityProps = {}; const entityProps = {};
for (const prop of entity.props) { for (const prop of entity.props) {
entityProps[`${prop.definition.name}`] = prop.value; entityProps[`${prop.definition.name}`] = prop.value;
} }
writeStream.write(JSON.stringify({ yield {
tick: match.tick, tick: tick,
serverClass: entity.serverClass.name, serverClass: entity.serverClass.name,
id: entity.entityIndex, id: entity.entityIndex,
props: entityProps, props: entityProps,
pvs: entity.pvs pvs: entity.pvs
}) + '\n'); };
}
} }
} }
});
parser.parseBody();
writeStream.end();
} }
function testEntities(name: string, entityCount: number) { function testEntities(name: string, entityCount: number) {
@ -45,34 +65,8 @@ function testEntities(name: string, entityCount: number) {
const source = readFileSync(`${__dirname}/../data/${name}.dem`); const source = readFileSync(`${__dirname}/../data/${name}.dem`);
const demo = Demo.fromNodeBuffer(source); const demo = Demo.fromNodeBuffer(source);
const parser = demo.getParser(false); const parser = demo.getParser(false);
parser.readHeader();
const match = parser.match;
const resultData: any[] = []; const resultData = getResultData(parser.getPackets());
parser.on('packet', (packet: Packet) => {
if (packet.packetType === 'packetEntities') {
for (const entity of packet.entities) {
const entityProps = {};
for (const prop of entity.props) {
entityProps[`${prop.definition.name}`] = prop.value;
}
resultData.push({
tick: match.tick,
serverClass: entity.serverClass.name,
id: entity.entityIndex,
props: entityProps,
pvs: entity.pvs
});
}
}
});
function parseEntities() {
const message = parser.tick();
if (message && resultData.length === 0) {
parseEntities();
}
}
const readStream = createReadStream(targetFile); const readStream = createReadStream(targetFile);
@ -81,14 +75,10 @@ function testEntities(name: string, entityCount: number) {
readStream readStream
.pipe(createUnzip()) .pipe(createUnzip())
.pipe(split2(JSON.parse)).on('data', (data) => { .pipe(split2(JSON.parse)).on('data', (data) => {
if (resultData.length < 1) { const result = resultData.next();
parseEntities(); assert.deepEqual(data, result.value, `Failed asserting that packet ${parsed} is the same`);
}
const result = resultData.shift();
assert.deepEqual(data, result, `Failed asserting that packet ${parsed} is the same`);
parsed++; parsed++;
}).on('end', () => { }).on('end', () => {
assert.equal(resultData.length, 0, 'Entities left to be checked');
assert.equal(parsed, entityCount, 'unexpected number of entities'); assert.equal(parsed, entityCount, 'unexpected number of entities');
resolve(); resolve();

View file

@ -1,17 +1,14 @@
import * as assert from 'assert'; import * as assert from 'assert';
import {readFileSync} from 'fs'; import {readFileSync} from 'fs';
import {Demo} from '../../Demo'; import {Demo} from '../../Demo';
import {Packet} from '../../Data/Packet';
import {BitStream} from 'bit-buffer'; import {BitStream} from 'bit-buffer';
function testDemo(name: string, fastMode: boolean = false) { function testDemo(name: string, fastMode: boolean = false) {
const target = JSON.parse(readFileSync(`${__dirname}/../data/${name}.json`, 'utf8')); const target = JSON.parse(readFileSync(`${__dirname}/../data/${name}.json`, 'utf8'));
const source = readFileSync(`${__dirname}/../data/${name}.dem`); const source = readFileSync(`${__dirname}/../data/${name}.dem`);
const demo = Demo.fromNodeBuffer(source); const demo = Demo.fromNodeBuffer(source);
const parser = demo.getParser(fastMode); const analyser = demo.getAnalyser(fastMode);
parser.readHeader(); const parsed = analyser.getBody().getState();
parser.parseBody();
const parsed = parser.match.getState();
assert.deepEqual(JSON.parse(JSON.stringify(parsed)), target); assert.deepEqual(JSON.parse(JSON.stringify(parsed)), target);
} }