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:
parent
51739e3aa8
commit
e9349d178e
9 changed files with 91 additions and 70 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
10
src/Demo.ts
10
src/Demo.ts
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue