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

@ -5,96 +5,96 @@ const fs = require('fs');
const argv = require('minimist')(process.argv.slice(2), {boolean: true}); const argv = require('minimist')(process.argv.slice(2), {boolean: true});
if (argv._.length !== 1) { if (argv._.length !== 1) {
console.log('Usage: "node analyse [--strings] [--dump] [--head] [--event-list] [--create-event-definitions] FILE"'); console.log('Usage: "node analyse [--strings] [--dump] [--head] [--event-list] [--create-event-definitions] FILE"');
process.exit(1); process.exit(1);
} }
const echo = function (data) { const echo = function (data) {
const string = JSON.stringify(data, null, 2); const string = JSON.stringify(data, null, 2);
console.log(string); console.log(string);
}; };
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());
} }
}); });
function getEventTypeName(s) { function getEventTypeName(s) {
const name = s.replace(/(\_\w)/g, function (m) { const name = s.replace(/(\_\w)/g, function (m) {
return m[1].toUpperCase(); return m[1].toUpperCase();
}).replace(/\b[a-z]/g, function (letter) { }).replace(/\b[a-z]/g, function (letter) {
return letter.toUpperCase(); return letter.toUpperCase();
}); });
if (EventNameReplace.has(name)) { if (EventNameReplace.has(name)) {
return EventNameReplace.get(name); return EventNameReplace.get(name);
} else { } else {
return name return name
.replace('Teamplay', 'TeamPlay') .replace('Teamplay', 'TeamPlay')
.replace('death', 'Death') .replace('death', 'Death')
.replace('panel', 'Panel') .replace('panel', 'Panel')
.replace('object', 'Object') .replace('object', 'Object')
.replace('update', 'Update') .replace('update', 'Update')
.replace('ready', 'Ready') .replace('ready', 'Ready')
.replace('Gameui', 'GameUI') .replace('Gameui', 'GameUI')
.replace('onhit', 'OnHit') .replace('onhit', 'OnHit')
.replace('bymedic', 'ByMedic') .replace('bymedic', 'ByMedic')
.replace('Controlpoint', 'ControlPoint') .replace('Controlpoint', 'ControlPoint')
.replace('Pipebomb', 'PipeBomb') .replace('Pipebomb', 'PipeBomb')
.replace('Scorestats', 'ScoreStats') .replace('Scorestats', 'ScoreStats')
.replace('Creditbonus', 'CreditBonus') .replace('Creditbonus', 'CreditBonus')
.replace('Sentrybuster', 'SentryBuster') .replace('Sentrybuster', 'SentryBuster')
.replace('Questlog', 'QuestLog') .replace('Questlog', 'QuestLog')
.replace('Localplayer', 'LocalPlayer') .replace('Localplayer', 'LocalPlayer')
.replace('Minigame', 'MiniGame') .replace('Minigame', 'MiniGame')
.replace('Winlimit', 'WinLimit') .replace('Winlimit', 'WinLimit')
.replace('Hltv', 'HLTV'); .replace('Hltv', 'HLTV');
} }
} }
function getEntryTypeDefinition(typeId) { function getEntryTypeDefinition(typeId) {
switch (typeId) { switch (typeId) {
case 1: case 1:
return 'string'; return 'string';
case 2: case 2:
case 3: case 3:
case 4: case 4:
case 5: case 5:
return 'number'; return 'number';
case 6: case 6:
return 'boolean'; return 'boolean';
case 7: case 7:
return 'null'; return 'null';
} }
} }
function createEventDefinition(definition) { function createEventDefinition(definition) {
return ` return `
export interface ${getEventTypeName(definition.name)}Event { export interface ${getEventTypeName(definition.name)}Event {
name: '${definition.name}'; name: '${definition.name}';
values: { values: {
@ -104,21 +104,21 @@ ${definition.entries.map(entry => ` ${entry.name}: ${getEntryTypeDefinition(ent
} }
function createEventDefinitionUnion(definitions) { function createEventDefinitionUnion(definitions) {
return `export type GameEvent = ` + return `export type GameEvent = ` +
definitions.map(definition => '\t' + getEventTypeName(definition.name) + 'Event') definitions.map(definition => '\t' + getEventTypeName(definition.name) + 'Event')
.join(' |\n').trim() .join(' |\n').trim()
+ ';'; + ';';
} }
function createEventTypeMap(definitions) { function createEventTypeMap(definitions) {
return `export type GameEventTypeMap = { return `export type GameEventTypeMap = {
${definitions.map(definition => ` ${definition.name}: ${getEventTypeName(definition.name)}Event;`).join('\n')} ${definitions.map(definition => ` ${definition.name}: ${getEventTypeName(definition.name)}Event;`).join('\n')}
};`; };`;
} }
function createEventTypeIdMap(definitionMap) { function createEventTypeIdMap(definitionMap) {
const definitionEntries = Array.from(definitionMap.entries()); const definitionEntries = Array.from(definitionMap.entries());
return `export type GameEventTypeId = number; return `export type GameEventTypeId = number;
export const GameEventTypeIdMap: Map<GameEventType, GameEventTypeId> = new Map<GameEventType, GameEventTypeId>([ export const GameEventTypeIdMap: Map<GameEventType, GameEventTypeId> = new Map<GameEventType, GameEventTypeId>([
${definitionEntries.map(([typeId, definition]) => ` ['${definition.name}', ${typeId}],`).join('\n')} ${definitionEntries.map(([typeId, definition]) => ` ['${definition.name}', ${typeId}],`).join('\n')}
@ -126,45 +126,45 @@ ${definitionEntries.map(([typeId, definition]) => ` ['${definition.name}', ${typ
} }
const EventNameReplace = new Map([ const EventNameReplace = new Map([
['ReplayReplaysavailable', 'ReplayReplaysAvailable'], ['ReplayReplaysavailable', 'ReplayReplaysAvailable'],
['ServerAddban', 'ServerAddBan'], ['ServerAddban', 'ServerAddBan'],
['ServerRemoveban', 'ServerRemoveBan'], ['ServerRemoveban', 'ServerRemoveBan'],
['ClientBeginconnect', 'ClientBeginConnect'], ['ClientBeginconnect', 'ClientBeginConnect'],
['ClientFullconnect', 'ClientFullConnect'], ['ClientFullconnect', 'ClientFullConnect'],
['PlayerChangename', 'PlayerChangeName'], ['PlayerChangename', 'PlayerChangeName'],
['PlayerHintmessage', 'PlayerHintMessage'], ['PlayerHintmessage', 'PlayerHintMessage'],
['GameNewmap', 'GameNewMap'], ['GameNewmap', 'GameNewMap'],
['IntroNextcamera', 'IntroNextCamera'], ['IntroNextcamera', 'IntroNextCamera'],
['PlayerChangeclass', 'PlayerChangeClass'], ['PlayerChangeclass', 'PlayerChangeClass'],
['ControlpointInitialized', 'ControlPointInitialized'], ['ControlpointInitialized', 'ControlPointInitialized'],
['ControlpointUpdateimages', 'ControlPointUpdateImages'], ['ControlpointUpdateimages', 'ControlPointUpdateImages'],
['ControlpointUpdatelayout', 'ControlPointUpdateLayout'], ['ControlpointUpdatelayout', 'ControlPointUpdateLayout'],
['ControlpointUpdatecapping', 'ControlPointUpdateCapping'], ['ControlpointUpdatecapping', 'ControlPointUpdateCapping'],
['ControlpointUpdateowner', 'ControlPointUpdateOwner'], ['ControlpointUpdateowner', 'ControlPointUpdateOwner'],
['ControlpointStarttouch', 'ControlPointStartTouch'], ['ControlpointStarttouch', 'ControlPointStartTouch'],
['ControlpointEndtouch', 'ControlPointEndTouch'], ['ControlpointEndtouch', 'ControlPointEndTouch'],
['ControlpointPulseElement', 'ControlPointPulseElement'], ['ControlpointPulseElement', 'ControlPointPulseElement'],
['ControlpointFakeCapture', 'ControlPointFakeCapture'], ['ControlpointFakeCapture', 'ControlPointFakeCapture'],
['ControlpointFakeCaptureMult', 'ControlPointFakeCaptureMult'], ['ControlpointFakeCaptureMult', 'ControlPointFakeCaptureMult'],
['TeamplayWaitingAbouttoend', 'TeamPlayWaitingAboutToEnd'], ['TeamplayWaitingAbouttoend', 'TeamPlayWaitingAboutToEnd'],
['TeamplayPointStartcapture', 'TeamPlayPointStartCapture'], ['TeamplayPointStartcapture', 'TeamPlayPointStartCapture'],
['FreezecamStarted', 'FreezeCamStarted'], ['FreezecamStarted', 'FreezeCamStarted'],
['LocalplayerChangeteam', 'LocalPlayerChangeTeam'], ['LocalplayerChangeteam', 'LocalPlayerChangeTeam'],
['LocalplayerChangeclass', 'LocalPlayerChangeClass'], ['LocalplayerChangeclass', 'LocalPlayerChangeClass'],
['LocalplayerChangedisguise', 'LocalPlayerChangeDisguise'], ['LocalplayerChangedisguise', 'LocalPlayerChangeDisguise'],
['FlagstatusUpdate', 'FlagStatusUpdate'], ['FlagstatusUpdate', 'FlagStatusUpdate'],
['TournamentEnablecountdown', 'TournamentEnableCountdown'], ['TournamentEnablecountdown', 'TournamentEnableCountdown'],
['PlayerCalledformedic', 'PlayerCalledForMedic'], ['PlayerCalledformedic', 'PlayerCalledForMedic'],
['PlayerAskedforball', 'PlayerAskedForBall'], ['PlayerAskedforball', 'PlayerAskedForBall'],
['LocalplayerBecameobserver', 'LocalPlayerBecameObserver'], ['LocalplayerBecameobserver', 'LocalPlayerBecameObserver'],
['PlayerHealedmediccall', 'PlayerHealedMedicCall'], ['PlayerHealedmediccall', 'PlayerHealedMedicCall'],
['ArenaMatchMaxstreak', 'ArenaMatchMaxStreak'], ['ArenaMatchMaxstreak', 'ArenaMatchMaxStreak'],
['StatsResetround', 'StatsResetRound'], ['StatsResetround', 'StatsResetRound'],
['FishNotice_arm', 'FishNoticeArm'], ['FishNotice_arm', 'FishNoticeArm'],
['PlayerBonuspoints', 'PlayerBonusPoints'], ['PlayerBonuspoints', 'PlayerBonusPoints'],
['PlayerUsedPowerupBottle', 'PlayerUsedPowerUpBottle'], ['PlayerUsedPowerupBottle', 'PlayerUsedPowerUpBottle'],
['ReplayStartrecord', 'ReplayStartRecord'], ['ReplayStartrecord', 'ReplayStartRecord'],
['ReplaySessioninfo', 'ReplaySessionInfo'], ['ReplaySessioninfo', 'ReplaySessionInfo'],
['ReplayEndrecord', 'ReplayEndRecord'], ['ReplayEndrecord', 'ReplayEndRecord'],
['ReplayServererror', 'ReplayServerError'] ['ReplayServererror', 'ReplayServerError']
]); ]);

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);
} }