mirror of
https://github.com/demostf/demo.js
synced 2026-06-04 09:04:13 +02:00
more splitting of analyser
This commit is contained in:
parent
198fe0b1ba
commit
b11cd7eec0
9 changed files with 240 additions and 211 deletions
246
bin/analyse.js
246
bin/analyse.js
|
|
@ -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
30
src/Analyser.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 = {};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue