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

add fast mode

This commit is contained in:
Robin Appelman 2017-03-07 18:16:55 +01:00
commit e9349d178e
9 changed files with 91 additions and 70 deletions

View file

@ -26,7 +26,7 @@ var 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;
var demo = Demo.fromNodeBuffer(data); var demo = Demo.fromNodeBuffer(data);
var parser = demo.getParser(); var parser = demo.getParser(true);
var head = parser.readHeader(); var head = parser.readHeader();
if (argv.head) { if (argv.head) {
echo(head); echo(head);

View file

@ -2,6 +2,7 @@ import {Stream} from "stream";
import {BitStream} from 'bit-buffer'; import {BitStream} from 'bit-buffer';
import {Parser} from './Parser'; import {Parser} from './Parser';
import {StreamParser} from './StreamParser'; import {StreamParser} from './StreamParser';
import {PacketType} from "./Parser/Message/Packet";
export class Demo { export class Demo {
stream: BitStream; stream: BitStream;
@ -11,9 +12,14 @@ export class Demo {
this.stream = new BitStream(arrayBuffer); this.stream = new BitStream(arrayBuffer);
} }
getParser() { getParser(fastMode: boolean = false) {
if (!this.parser) { if (!this.parser) {
this.parser = new Parser(this.stream); const skippedPackets = fastMode ? [
PacketType.packetEntities,
PacketType.tempEntities,
PacketType.entityMessage,
] : [];
this.parser = new Parser(this.stream, skippedPackets);
} }
return this.parser; return this.parser;
} }

View file

@ -1,4 +1,4 @@
import {Packet} from './Parser/Message/Packet'; import {Packet, PacketType} from './Parser/Message/Packet';
import {ConsoleCmd} from './Parser/Message/ConsoleCmd'; import {ConsoleCmd} from './Parser/Message/ConsoleCmd';
import {StringTable} from './Parser/Message/StringTable'; import {StringTable} from './Parser/Message/StringTable';
import {DataTable} from './Parser/Message/DataTable'; import {DataTable} from './Parser/Message/DataTable';
@ -12,12 +12,14 @@ import {Header} from "./Data/Header";
export class Parser extends EventEmitter { export class Parser extends EventEmitter {
stream: BitStream; stream: BitStream;
match: Match; match: Match;
skipPackets: PacketType[];
constructor(stream: BitStream) { constructor(stream: BitStream, skipPackets: PacketType[] = []) {
super(); super();
this.stream = stream; this.stream = stream;
this.match = new Match(); this.match = new Match();
this.on('packet', this.match.handlePacket.bind(this.match)); this.on('packet', this.match.handlePacket.bind(this.match));
this.skipPackets = skipPackets;
} }
readHeader() { readHeader() {
@ -62,15 +64,15 @@ export class Parser extends EventEmitter {
switch (type) { switch (type) {
case MessageType.Sigon: case MessageType.Sigon:
case MessageType.Packet: case MessageType.Packet:
return new Packet(type, tick, data, length, match); return new Packet(type, tick, data, length, match, this.skipPackets);
case MessageType.ConsoleCmd: case MessageType.ConsoleCmd:
return new ConsoleCmd(type, tick, data, length, match); return new ConsoleCmd(type, tick, data, length, match, this.skipPackets);
case MessageType.UserCmd: case MessageType.UserCmd:
return new UserCmd(type, tick, data, length, match); return new UserCmd(type, tick, data, length, match, this.skipPackets);
case MessageType.DataTables: case MessageType.DataTables:
return new DataTable(type, tick, data, length, match); return new DataTable(type, tick, data, length, match, this.skipPackets);
case MessageType.StringTables: case MessageType.StringTables:
return new StringTable(type, tick, data, length, match); return new StringTable(type, tick, data, length, match, this.skipPackets);
default: default:
throw new Error("unknown message type"); throw new Error("unknown message type");
} }

View file

@ -37,7 +37,8 @@ export class Packet extends Parser {
const type = this.stream.readBits(6); const type = this.stream.readBits(6);
if (type !== 0) { if (type !== 0) {
if (Packet.parsers[type]) { if (Packet.parsers[type]) {
let packet = Packet.parsers[type].call(this, this.stream, this.match); const skip = this.skippedPackets.indexOf(type) !== -1;
const packet = Packet.parsers[type].call(this, 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]);
@ -86,7 +87,7 @@ export class Packet extends Parser {
}; };
} }
enum PacketType { export enum PacketType {
file = 2, file = 2,
netTick = 3, netTick = 3,
stringCmd = 4, stringCmd = 4,

View file

@ -1,7 +1,8 @@
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} from "../../Data/Packet";
import {SendTable} from "../../Data/SendTable"; import {MessageType} from "../../Parser";
import {PacketType} from "./Packet";
export abstract class Parser { export abstract class Parser {
type: any; type: any;
@ -9,14 +10,16 @@ export abstract class Parser {
stream: BitStream; stream: BitStream;
length: number; length: number;
match: Match; match: Match;
skippedPackets: PacketType[];
constructor(type, tick, stream, length, match) { constructor(type: MessageType, tick: number, stream: BitStream, length: number, match: Match, skippedPacket: PacketType[] = []) {
this.type = type; this.type = type;
this.tick = tick; this.tick = tick;
this.stream = stream; this.stream = stream;
this.length = length;//length in bytes this.length = length;//length in bytes
this.match = match; this.match = match;
this.skippedPackets = skippedPacket;
} }
abstract parse():Packet[]|string; abstract parse(): Packet[]|string;
} }

View file

@ -58,7 +58,7 @@ function getPacketEntityForExisting(entityId: number, match: Match, pvs: PVS) {
return new PacketEntity(serverClass, entityId, pvs); return new PacketEntity(serverClass, entityId, pvs);
} }
export function PacketEntities(stream: BitStream, match: Match): PacketEntitiesPacket { //26: packetEntities export function PacketEntities(stream: BitStream, match: Match, skip: boolean = false): PacketEntitiesPacket { //26: packetEntities
// https://github.com/skadistats/smoke/blob/master/smoke/replay/handler/svc_packetentities.pyx // https://github.com/skadistats/smoke/blob/master/smoke/replay/handler/svc_packetentities.pyx
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Handler/PacketEntitesHandler.cs // https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Handler/PacketEntitesHandler.cs
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Entity.cs // https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/DP/Entity.cs
@ -74,38 +74,41 @@ export function PacketEntities(stream: BitStream, match: Match): PacketEntitiesP
let entityId = -1; let entityId = -1;
const receivedEntities: PacketEntity[] = []; const receivedEntities: PacketEntity[] = [];
for (let i = 0; i < updatedEntries; i++) { const removedEntityIds: number[] = [];
const diff = readUBitVar(stream);
entityId += 1 + diff;
const pvs = readPVSType(stream);
if (pvs === PVS.ENTER) {
const packetEntity = readEnterPVS(stream, entityId, match);
applyEntityUpdate(packetEntity, match.getSendTable(packetEntity.serverClass.dataTable), stream);
if (updatedBaseLine) { if (!skip) {
const newBaseLine: SendProp[] = []; for (let i = 0; i < updatedEntries; i++) {
newBaseLine.concat(packetEntity.props); const diff = readUBitVar(stream);
match.baseLineCache[packetEntity.serverClass.id] = packetEntity.clone(); entityId += 1 + diff;
} const pvs = readPVSType(stream);
packetEntity.inPVS = true; if (pvs === PVS.ENTER) {
receivedEntities.push(packetEntity); const packetEntity = readEnterPVS(stream, entityId, match);
} else if (pvs === PVS.PRESERVE) { applyEntityUpdate(packetEntity, match.getSendTable(packetEntity.serverClass.dataTable), stream);
const packetEntity = getPacketEntityForExisting(entityId, match, pvs);
applyEntityUpdate(packetEntity, match.getSendTable(packetEntity.serverClass.dataTable), stream); if (updatedBaseLine) {
receivedEntities.push(packetEntity); const newBaseLine: SendProp[] = [];
} else { newBaseLine.concat(packetEntity.props);
if(match.entityClasses[entityId]) { match.baseLineCache[packetEntity.serverClass.id] = packetEntity.clone();
const packetEntity = getPacketEntityForExisting(entityId, match, pvs); }
packetEntity.inPVS = true;
receivedEntities.push(packetEntity); receivedEntities.push(packetEntity);
} else if (pvs === PVS.PRESERVE) {
const packetEntity = getPacketEntityForExisting(entityId, match, pvs);
applyEntityUpdate(packetEntity, match.getSendTable(packetEntity.serverClass.dataTable), stream);
receivedEntities.push(packetEntity);
} else {
if (match.entityClasses[entityId]) {
const packetEntity = getPacketEntityForExisting(entityId, match, pvs);
receivedEntities.push(packetEntity);
}
} }
} }
}
const removedEntityIds: number[] = []; if (isDelta) {
if (isDelta) { while (stream.readBoolean()) {
while (stream.readBoolean()) { const entityId = stream.readBits(11);
const entityId = stream.readBits(11); removedEntityIds.push(entityId);
removedEntityIds.push(entityId); }
} }
} }

View file

@ -2,5 +2,5 @@ import {Packet} from "../../Data/Packet";
import {BitStream} from 'bit-buffer'; import {BitStream} from 'bit-buffer';
import {Match} from "../../Data/Match"; import {Match} from "../../Data/Match";
export type Parser = (stream: BitStream, match?: Match) => Packet; export type Parser = (stream: BitStream, match?: Match, skip: boolean = false) => Packet;
export type PacketParserMap = {[id: number]: Parser}; export type PacketParserMap = {[id: number]: Parser};

View file

@ -4,35 +4,37 @@ import {Match} from "../../Data/Match";
import {PacketEntity, PVS} from "../../Data/PacketEntity"; import {PacketEntity, PVS} from "../../Data/PacketEntity";
import {applyEntityUpdate} from "../EntityDecoder"; import {applyEntityUpdate} from "../EntityDecoder";
export function TempEntities(stream: BitStream, match: Match): TempEntitiesPacket { // 10: classInfo export function TempEntities(stream: BitStream, match: Match, skip: boolean = false): TempEntitiesPacket { // 10: classInfo
const entityCount = stream.readBits(8); const entityCount = stream.readBits(8);
const length = readVarInt(stream); const length = readVarInt(stream);
const end = stream.index + length; const end = stream.index + length;
let entity: PacketEntity|null = null; let entity: PacketEntity|null = null;
let entities: PacketEntity[] = []; let entities: PacketEntity[] = [];
for (let i = 0; i < entityCount; i++) { if (!skip) {
const delay = (stream.readBoolean()) ? stream.readUint8() / 100 : 0; //unused it seems for (let i = 0; i < entityCount; i++) {
if (stream.readBoolean()) { const delay = (stream.readBoolean()) ? stream.readUint8() / 100 : 0; //unused it seems
const classId = stream.readBits(match.classBits); if (stream.readBoolean()) {
const serverClass = match.serverClasses[classId - 1]; const classId = stream.readBits(match.classBits);
// no clue why the -1 but it works const serverClass = match.serverClasses[classId - 1];
// maybe because world (id=0) can never be temp // no clue why the -1 but it works
// but it's not like the -1 saves any space // maybe because world (id=0) can never be temp
const sendTable = match.getSendTable(serverClass.dataTable); // but it's not like the -1 saves any space
entity = new PacketEntity(serverClass, 0, PVS.ENTER); const sendTable = match.getSendTable(serverClass.dataTable);
applyEntityUpdate(entity, sendTable, stream); entity = new PacketEntity(serverClass, 0, PVS.ENTER);
entities.push(entity); applyEntityUpdate(entity, sendTable, stream);
} else { entities.push(entity);
if (entity) {
applyEntityUpdate(entity, match.getSendTable(entity.serverClass.dataTable), stream);
} else { } else {
throw new Error("no entity set to update"); if (entity) {
applyEntityUpdate(entity, match.getSendTable(entity.serverClass.dataTable), stream);
} else {
throw new Error("no entity set to update");
}
} }
} }
} if (end - stream.index > 8) {
if (end - stream.index > 8) { throw new Error("unexpected content after TempEntities");
throw new Error("unexpected content after TempEntities"); }
} }
stream.index = end; stream.index = end;

View file

@ -2,11 +2,11 @@ import {readFileSync} from 'fs';
import {Demo} from "../../Demo"; import {Demo} from "../../Demo";
import * as assert from 'assert'; import * as assert from 'assert';
function testDemo(name: string) { 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(); const parser = demo.getParser(fastMode);
parser.readHeader(); parser.readHeader();
parser.parseBody(); parser.parseBody();
const parsed = parser.match.getState(); const parsed = parser.match.getState();
@ -14,6 +14,10 @@ function testDemo(name: string) {
} }
suite('Parse basic demo info', () => { suite('Parse basic demo info', () => {
test('Fast mode', () => {
testDemo('snakewater', true);
});
test('Parse snakewater.dem', () => { test('Parse snakewater.dem', () => {
testDemo('snakewater'); testDemo('snakewater');
}); });