mirror of
https://github.com/demostf/demo.js
synced 2026-06-04 00:54:14 +02:00
encoder wip
This commit is contained in:
parent
5b2665bdc9
commit
a5a1642782
13 changed files with 876 additions and 35 deletions
|
|
@ -28,9 +28,13 @@ export class ParserState {
|
||||||
public instanceBaselines: [Map<EntityId, SendProp[]>, Map<EntityId, SendProp[]>] = [new Map(), new Map()];
|
public instanceBaselines: [Map<EntityId, SendProp[]>, Map<EntityId, SendProp[]>] = [new Map(), new Map()];
|
||||||
public skippedPackets: PacketTypeId[] = [];
|
public skippedPackets: PacketTypeId[] = [];
|
||||||
public userInfoEntries: Map<string, BitStream> = new Map();
|
public userInfoEntries: Map<string, BitStream> = new Map();
|
||||||
|
public tick: number = 0;
|
||||||
|
|
||||||
public handlePacket(packet: Packet) {
|
public handlePacket(packet: Packet) {
|
||||||
switch (packet.packetType) {
|
switch (packet.packetType) {
|
||||||
|
case 'netTick':
|
||||||
|
this.tick = packet.tick;
|
||||||
|
break;
|
||||||
case 'serverInfo':
|
case 'serverInfo':
|
||||||
this.version = packet.version;
|
this.version = packet.version;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,8 @@ import {PacketTypeId} from './Data/Packet';
|
||||||
import {Parser} from './Parser';
|
import {Parser} from './Parser';
|
||||||
|
|
||||||
export class Demo {
|
export class Demo {
|
||||||
public static fromNodeBuffer(nodeBuffer) {
|
public static fromNodeBuffer(nodeBuffer: Buffer) {
|
||||||
const arrayBuffer = new ArrayBuffer(nodeBuffer.length);
|
return new Demo(nodeBuffer.buffer as ArrayBuffer);
|
||||||
const view = new Uint8Array(arrayBuffer);
|
|
||||||
for (let i = 0; i < nodeBuffer.length; ++i) {
|
|
||||||
view[i] = nodeBuffer[i];
|
|
||||||
}
|
|
||||||
return new Demo(arrayBuffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public stream: BitStream;
|
public stream: BitStream;
|
||||||
|
|
|
||||||
48
src/Encoder.ts
Normal file
48
src/Encoder.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import {BitStream} from 'bit-buffer';
|
||||||
|
import {Header} from './Data/Header';
|
||||||
|
import {Message, MessageType} from './Data/Message';
|
||||||
|
import {ParserState} from './Data/ParserState';
|
||||||
|
import {messageHandlers} from './Parser';
|
||||||
|
|
||||||
|
export class Encoder {
|
||||||
|
public readonly stream: BitStream;
|
||||||
|
public readonly parserState: ParserState;
|
||||||
|
|
||||||
|
constructor(stream: BitStream) {
|
||||||
|
this.stream = stream;
|
||||||
|
this.parserState = new ParserState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public encodeHeader(header: Header) {
|
||||||
|
this.stream.writeASCIIString(header.type, 8);
|
||||||
|
this.stream.writeUint32(header.version);
|
||||||
|
this.stream.writeUint32(header.protocol);
|
||||||
|
this.stream.writeASCIIString(header.server, 260);
|
||||||
|
this.stream.writeASCIIString(header.nick, 260);
|
||||||
|
this.stream.writeASCIIString(header.map, 260);
|
||||||
|
this.stream.writeASCIIString(header.game, 260);
|
||||||
|
this.stream.writeFloat32(header.duration);
|
||||||
|
this.stream.writeUint32(header.ticks);
|
||||||
|
this.stream.writeUint32(header.frames);
|
||||||
|
this.stream.writeUint32(header.sigon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeMessage(message: Message) {
|
||||||
|
this.stream.writeUint8(message.type);
|
||||||
|
const handler = messageHandlers.get(message.type);
|
||||||
|
if (!handler) {
|
||||||
|
throw new Error(`No handler for message of type ${MessageType[message.type]}`);
|
||||||
|
}
|
||||||
|
handler.encodeMessage(message, this.stream, this.parserState);
|
||||||
|
this.handleMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleMessage(message: Message) {
|
||||||
|
this.parserState.handleMessage(message);
|
||||||
|
if (message.type === MessageType.Packet) {
|
||||||
|
for (const packet of message.packets) {
|
||||||
|
this.parserState.handlePacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,18 +6,20 @@ import {ParserState} from './Data/ParserState';
|
||||||
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 {StopHandler} from './Parser/Message/Stop';
|
||||||
import {StringTableHandler} from './Parser/Message/StringTable';
|
import {StringTableHandler} from './Parser/Message/StringTable';
|
||||||
import {SyncTickHandler} from './Parser/Message/SyncTick';
|
import {SyncTickHandler} from './Parser/Message/SyncTick';
|
||||||
import {UserCmdHandler} from './Parser/Message/UserCmd';
|
import {UserCmdHandler} from './Parser/Message/UserCmd';
|
||||||
|
|
||||||
const messageHandlers: Map<MessageType, MessageHandler<Message>> = new Map<MessageType, MessageHandler<Message>>([
|
export const messageHandlers: Map<MessageType, MessageHandler<Message>> = new Map<MessageType, MessageHandler<Message>>([
|
||||||
[MessageType.Sigon, PacketMessageHandler],
|
[MessageType.Sigon, PacketMessageHandler],
|
||||||
[MessageType.Packet, PacketMessageHandler],
|
[MessageType.Packet, PacketMessageHandler],
|
||||||
[MessageType.ConsoleCmd, ConsoleCmdHandler],
|
[MessageType.ConsoleCmd, ConsoleCmdHandler],
|
||||||
[MessageType.UserCmd, UserCmdHandler],
|
[MessageType.UserCmd, UserCmdHandler],
|
||||||
[MessageType.DataTables, DataTableHandler],
|
[MessageType.DataTables, DataTableHandler],
|
||||||
[MessageType.StringTables, StringTableHandler],
|
[MessageType.StringTables, StringTableHandler],
|
||||||
[MessageType.SyncTick, SyncTickHandler]
|
[MessageType.SyncTick, SyncTickHandler],
|
||||||
|
[MessageType.Stop, StopHandler]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export class Parser {
|
export class Parser {
|
||||||
|
|
@ -50,22 +52,13 @@ export class Parser {
|
||||||
protected * getMessages(): Iterable<Message> {
|
protected * getMessages(): Iterable<Message> {
|
||||||
while (true) {
|
while (true) {
|
||||||
const message = this.readMessage(this.stream, this.parserState);
|
const message = this.readMessage(this.stream, this.parserState);
|
||||||
if (message) {
|
|
||||||
yield message;
|
yield message;
|
||||||
} else {
|
if (message.type === MessageType.Stop) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected parseMessage(data: BitStream, type: MessageType, state: ParserState): Message {
|
|
||||||
const handler = messageHandlers.get(type);
|
|
||||||
if (!handler) {
|
|
||||||
throw new Error(`No handler for message of type ${MessageType[type]}`);
|
|
||||||
}
|
|
||||||
return handler.parseMessage(data, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected parseHeader(stream): Header {
|
protected parseHeader(stream): Header {
|
||||||
return {
|
return {
|
||||||
type: stream.readASCIIString(8),
|
type: stream.readASCIIString(8),
|
||||||
|
|
@ -85,22 +78,28 @@ export class Parser {
|
||||||
protected * handleMessage(message: Message): Iterable<Packet> {
|
protected * handleMessage(message: Message): Iterable<Packet> {
|
||||||
this.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.packets) {
|
||||||
this.parserState.handlePacket(packet);
|
this.parserState.handlePacket(packet);
|
||||||
yield packet;
|
yield packet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readMessage(stream: BitStream, state: ParserState): Message | false {
|
protected readMessage(stream: BitStream, state: ParserState): Message {
|
||||||
if (stream.bitsLeft < 8) {
|
if (stream.bitsLeft < 8) {
|
||||||
return false;
|
throw new Error('Stream ended without stop packet');
|
||||||
}
|
}
|
||||||
const type: MessageType = stream.readUint8();
|
const type: MessageType = stream.readUint8();
|
||||||
if (type === MessageType.Stop) {
|
if (type === 0) {
|
||||||
return false;
|
return {
|
||||||
|
type: MessageType.Stop,
|
||||||
|
rawData: stream.readBitStream(0)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
const handler = messageHandlers.get(type);
|
||||||
return this.parseMessage(stream, type, state);
|
if (!handler) {
|
||||||
|
throw new Error(`No handler for message of type ${MessageType[type]}(${type})`);
|
||||||
|
}
|
||||||
|
return handler.parseMessage(this.stream, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,13 @@ export function getEntityUpdate(sendTable: SendTable, stream: BitStream): SendPr
|
||||||
let index = -1;
|
let index = -1;
|
||||||
const allProps = sendTable.flattenedProps;
|
const allProps = sendTable.flattenedProps;
|
||||||
const props: Map<string, SendProp> = new Map();
|
const props: Map<string, SendProp> = new Map();
|
||||||
|
let lastIndex = -1;
|
||||||
while (stream.readBoolean()) {
|
while (stream.readBoolean()) {
|
||||||
|
lastIndex = index;
|
||||||
index = readFieldIndex(stream, index);
|
index = readFieldIndex(stream, index);
|
||||||
if (index >= 4096 || index > allProps.length) {
|
if (index >= 4096 || index > allProps.length) {
|
||||||
throw new Error('prop index out of bounds while applying update for ' + sendTable.name + ' got ' + index
|
throw new Error(`prop index out of bounds while applying update for ${sendTable.name}
|
||||||
+ ' property only has ' + allProps.length + ' properties');
|
got ${index} property only has ${allProps.length} properties (lastProp: ${lastIndex})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const propDefinition = allProps[index];
|
const propDefinition = allProps[index];
|
||||||
|
|
@ -42,8 +44,8 @@ export function encodeEntityUpdate(props: SendProp[], sendTable: SendTable, stre
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index < lastIndex) {
|
if (index < lastIndex) {
|
||||||
throw new Error(`Property index not incremental while encoding` +
|
throw new Error(`Property index not incremental while encoding ` +
|
||||||
`${prop.definition.fullName} after ${allProps[lastIndex].fullName}` +
|
`${prop.definition.fullName} after ${allProps[lastIndex].fullName} ` +
|
||||||
`in ${sendTable.name} (current: ${index}, last: ${lastIndex})`);
|
`in ${sendTable.name} (current: ${index}, last: ${lastIndex})`);
|
||||||
}
|
}
|
||||||
writeFieldIndex(index, stream, lastIndex);
|
writeFieldIndex(index, stream, lastIndex);
|
||||||
|
|
|
||||||
14
src/Parser/Message/Stop.ts
Normal file
14
src/Parser/Message/Stop.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import {BitStream} from 'bit-buffer';
|
||||||
|
import {MessageHandler, MessageType, StopMessage, SyncTickMessage} from '../../Data/Message';
|
||||||
|
|
||||||
|
export const StopHandler: MessageHandler<StopMessage> = {
|
||||||
|
parseMessage: (stream: BitStream) => {
|
||||||
|
return {
|
||||||
|
type: MessageType.Stop,
|
||||||
|
rawData: stream.readBitStream(0)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
encodeMessage: (message, stream) => {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -4,8 +4,6 @@ import {GameEvent, GameEventType} from '../../Data/GameEventTypes';
|
||||||
import {GameEventListPacket} from '../../Data/Packet';
|
import {GameEventListPacket} from '../../Data/Packet';
|
||||||
|
|
||||||
export function ParseGameEventList(stream: BitStream): GameEventListPacket { // 30: gameEventList
|
export function ParseGameEventList(stream: BitStream): GameEventListPacket { // 30: gameEventList
|
||||||
const s = stream.index;
|
|
||||||
|
|
||||||
// list of game events and parameters
|
// list of game events and parameters
|
||||||
const numEvents = stream.readBits(9);
|
const numEvents = stream.readBits(9);
|
||||||
const length = stream.readBits(20);
|
const length = stream.readBits(20);
|
||||||
|
|
@ -28,6 +26,7 @@ export function ParseGameEventList(stream: BitStream): GameEventListPacket { //
|
||||||
entries
|
entries
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
packetType: 'gameEventList',
|
packetType: 'gameEventList',
|
||||||
eventList
|
eventList
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@ export function ParsePacketEntities(
|
||||||
const updatedEntries = stream.readBits(11);
|
const updatedEntries = stream.readBits(11);
|
||||||
const length = stream.readBits(20);
|
const length = stream.readBits(20);
|
||||||
const updatedBaseLine = stream.readBoolean();
|
const updatedBaseLine = stream.readBoolean();
|
||||||
|
const start = stream.index;
|
||||||
const end = stream.index + length;
|
const end = stream.index + length;
|
||||||
let entityId = -1;
|
let entityId = -1;
|
||||||
|
|
||||||
|
|
@ -166,6 +167,10 @@ export function ParsePacketEntities(
|
||||||
if (!sendTable) {
|
if (!sendTable) {
|
||||||
throw new Error(`Unknown sendTable ${packetEntity.serverClass.dataTable}`);
|
throw new Error(`Unknown sendTable ${packetEntity.serverClass.dataTable}`);
|
||||||
}
|
}
|
||||||
|
if (entityId === 55) {
|
||||||
|
console.log(`decode preserve: ${entityId} = ${sendTable.name}, ${receivedEntities.length}/${i} ${stream.index} ${end} tick ${state.tick}`);
|
||||||
|
console.log(receivedEntities[receivedEntities.length - 1], start, entityId, diff);
|
||||||
|
}
|
||||||
const updatedProps = getEntityUpdate(sendTable, stream);
|
const updatedProps = getEntityUpdate(sendTable, stream);
|
||||||
packetEntity.applyPropUpdate(updatedProps);
|
packetEntity.applyPropUpdate(updatedProps);
|
||||||
receivedEntities.push(packetEntity);
|
receivedEntities.push(packetEntity);
|
||||||
|
|
@ -173,7 +178,7 @@ export function ParsePacketEntities(
|
||||||
const packetEntity = getPacketEntityForExisting(entityId, state, pvs);
|
const packetEntity = getPacketEntityForExisting(entityId, state, pvs);
|
||||||
receivedEntities.push(packetEntity);
|
receivedEntities.push(packetEntity);
|
||||||
} else {
|
} else {
|
||||||
// throw new Error(`No existing entity to update with id ${entityId}`);
|
throw new Error(`No existing entity to update with id ${entityId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,6 +238,9 @@ export function EncodePacketEntities(packet: PacketEntitiesPacket, stream: BitSt
|
||||||
writeEnterPVS(entity, stream, state, packet.baseLine);
|
writeEnterPVS(entity, stream, state, packet.baseLine);
|
||||||
} else if (entity.pvs === PVS.PRESERVE) {
|
} else if (entity.pvs === PVS.PRESERVE) {
|
||||||
const sendTable = getSendTable(state, entity.serverClass.dataTable);
|
const sendTable = getSendTable(state, entity.serverClass.dataTable);
|
||||||
|
if (entity.entityIndex === 55) {
|
||||||
|
console.log(`encode preserve: ${entity.entityIndex} = ${entity.serverClass.dataTable}`);
|
||||||
|
}
|
||||||
encodeEntityUpdate(entity.props, sendTable, stream);
|
encodeEntityUpdate(entity.props, sendTable, stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export const readUBitVar = readBitVar;
|
||||||
export function readVarInt(stream: BitStream, signed: boolean = false) {
|
export function readVarInt(stream: BitStream, signed: boolean = false) {
|
||||||
let result = 0;
|
let result = 0;
|
||||||
for (let i = 0; i < 35; i += 7) {
|
for (let i = 0; i < 35; i += 7) {
|
||||||
const byte = stream.readBits(8);
|
const byte = stream.readUint8();
|
||||||
result |= ((byte & 0x7F) << i);
|
result |= ((byte & 0x7F) << i);
|
||||||
|
|
||||||
if ((byte >> 7) === 0) {
|
if ((byte >> 7) === 0) {
|
||||||
|
|
|
||||||
38
src/Transformer.ts
Normal file
38
src/Transformer.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import {BitStream} from 'bit-buffer';
|
||||||
|
import {Parser} from './Parser';
|
||||||
|
import {Encoder} from './Encoder';
|
||||||
|
import {Packet} from './Data/Packet';
|
||||||
|
import {Message, MessageType} from './Data/Message';
|
||||||
|
|
||||||
|
export type PacketTransform = (packet: Packet) => Packet;
|
||||||
|
|
||||||
|
export type MessageTransform = (message: Message) => Message;
|
||||||
|
|
||||||
|
export function nullTransform<T extends Packet | Message>(input: T): T {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Transformer extends Parser {
|
||||||
|
private readonly encoder: Encoder;
|
||||||
|
|
||||||
|
constructor(sourceStream: BitStream, targetStream: BitStream) {
|
||||||
|
super(sourceStream);
|
||||||
|
this.encoder = new Encoder(targetStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public transform(packetTransform: PacketTransform, messageTransform: MessageTransform) {
|
||||||
|
this.encoder.encodeHeader(this.getHeader());
|
||||||
|
|
||||||
|
for (const message of this.getMessages()) {
|
||||||
|
this.parserState.handleMessage(message);
|
||||||
|
if (message.type === MessageType.Packet) {
|
||||||
|
for (const packet of message.packets) {
|
||||||
|
this.parserState.handlePacket(packet);
|
||||||
|
}
|
||||||
|
message.packets = message.packets.map(packetTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.encoder.writeMessage(messageTransform(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/tests/data/short.dem
Normal file
BIN
src/tests/data/short.dem
Normal file
Binary file not shown.
695
src/tests/data/short.json
Normal file
695
src/tests/data/short.json
Normal file
|
|
@ -0,0 +1,695 @@
|
||||||
|
{
|
||||||
|
"chat": [
|
||||||
|
{
|
||||||
|
"kind": "TF_Chat_All",
|
||||||
|
"from": "-[MG]- Linc",
|
||||||
|
"text": "hf",
|
||||||
|
"tick": 41683
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "TF_Chat_All",
|
||||||
|
"from": "-[MG]- Linc",
|
||||||
|
"text": "gg",
|
||||||
|
"tick": 74952
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "TF_Chat_All",
|
||||||
|
"from": "sientalprime",
|
||||||
|
"text": "gr",
|
||||||
|
"tick": 75279
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"users": {
|
||||||
|
"2": {
|
||||||
|
"classes": {},
|
||||||
|
"name": "SourceTV",
|
||||||
|
"steamId": "BOT",
|
||||||
|
"userId": 2
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"classes": {
|
||||||
|
"3": 12
|
||||||
|
},
|
||||||
|
"name": "Oneflower",
|
||||||
|
"steamId": "[U:1:72084]",
|
||||||
|
"userId": 3,
|
||||||
|
"team": "blue"
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"classes": {
|
||||||
|
"4": 10,
|
||||||
|
"5": 2
|
||||||
|
},
|
||||||
|
"name": "THG | The Bearded MIG",
|
||||||
|
"steamId": "[U:1:79943218]",
|
||||||
|
"userId": 4,
|
||||||
|
"team": "blue"
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"classes": {
|
||||||
|
"4": 3,
|
||||||
|
"5": 7,
|
||||||
|
"6": 1
|
||||||
|
},
|
||||||
|
"name": "sientalprime",
|
||||||
|
"steamId": "[U:1:315192136]",
|
||||||
|
"userId": 5,
|
||||||
|
"team": "blue"
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"classes": {
|
||||||
|
"1": 11,
|
||||||
|
"2": 2,
|
||||||
|
"6": 1,
|
||||||
|
"7": 4
|
||||||
|
},
|
||||||
|
"name": "Colonel Miggy-Bears",
|
||||||
|
"steamId": "[U:1:4783268]",
|
||||||
|
"userId": 6,
|
||||||
|
"team": "blue"
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
"classes": {
|
||||||
|
"1": 11,
|
||||||
|
"2": 1
|
||||||
|
},
|
||||||
|
"name": "-[MG]- Linc",
|
||||||
|
"steamId": "[U:1:25900190]",
|
||||||
|
"userId": 7,
|
||||||
|
"team": "blue"
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"classes": {
|
||||||
|
"3": 11
|
||||||
|
},
|
||||||
|
"name": "J1ll ✧ stipuha zavisimyy",
|
||||||
|
"steamId": "[U:1:70354528]",
|
||||||
|
"userId": 8,
|
||||||
|
"team": "red"
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
"classes": {
|
||||||
|
"3": 10
|
||||||
|
},
|
||||||
|
"name": "dzapis",
|
||||||
|
"steamId": "[U:1:134795814]",
|
||||||
|
"userId": 9,
|
||||||
|
"team": "red"
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
"classes": {
|
||||||
|
"1": 1,
|
||||||
|
"3": 10,
|
||||||
|
"8": 2
|
||||||
|
},
|
||||||
|
"name": "SAVE_THE_SPYCRABS",
|
||||||
|
"steamId": "[U:1:125755589]",
|
||||||
|
"userId": 10,
|
||||||
|
"team": "blue"
|
||||||
|
},
|
||||||
|
"11": {
|
||||||
|
"classes": {
|
||||||
|
"1": 9
|
||||||
|
},
|
||||||
|
"name": "skq",
|
||||||
|
"steamId": "[U:1:113575586]",
|
||||||
|
"userId": 11,
|
||||||
|
"team": "red"
|
||||||
|
},
|
||||||
|
"12": {
|
||||||
|
"classes": {
|
||||||
|
"4": 7
|
||||||
|
},
|
||||||
|
"name": "Dunkelheit",
|
||||||
|
"steamId": "[U:1:52631210]",
|
||||||
|
"userId": 12,
|
||||||
|
"team": "red"
|
||||||
|
},
|
||||||
|
"13": {
|
||||||
|
"classes": {
|
||||||
|
"1": 7
|
||||||
|
},
|
||||||
|
"name": "Alexander",
|
||||||
|
"steamId": "[U:1:101341390]",
|
||||||
|
"userId": 13,
|
||||||
|
"team": "red"
|
||||||
|
},
|
||||||
|
"14": {
|
||||||
|
"classes": {
|
||||||
|
"5": 6
|
||||||
|
},
|
||||||
|
"name": "Dr.Oetker",
|
||||||
|
"steamId": "[U:1:76315857]",
|
||||||
|
"userId": 14,
|
||||||
|
"team": "red"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deaths": [
|
||||||
|
{
|
||||||
|
"killer": 3,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 11,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 41352
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 43136
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 12,
|
||||||
|
"victim": 7,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 43190
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": 12,
|
||||||
|
"victim": 10,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 43515
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 43817
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 8,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 4,
|
||||||
|
"weapon": "shotgun_soldier",
|
||||||
|
"tick": 44729
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 7,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 45866
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 3,
|
||||||
|
"assister": 7,
|
||||||
|
"victim": 8,
|
||||||
|
"weapon": "world",
|
||||||
|
"tick": 45900
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 45954
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 46185
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 4,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 13,
|
||||||
|
"weapon": "tf_projectile_pipe",
|
||||||
|
"tick": 46445
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": 12,
|
||||||
|
"victim": 10,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 46456
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 4,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 46491
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 5,
|
||||||
|
"weapon": "tf_projectile_pipe",
|
||||||
|
"tick": 46607
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": 9,
|
||||||
|
"victim": 7,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 48554
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 8,
|
||||||
|
"assister": 13,
|
||||||
|
"victim": 4,
|
||||||
|
"weapon": "cow_mangler",
|
||||||
|
"tick": 48693
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": 12,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 48763
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 8,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 48835
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 13,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 10,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 48959
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 5,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 49312
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 8,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 4,
|
||||||
|
"weapon": "shotgun_soldier",
|
||||||
|
"tick": 51581
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 10,
|
||||||
|
"assister": 3,
|
||||||
|
"victim": 8,
|
||||||
|
"weapon": "tf_projectile_rocket",
|
||||||
|
"tick": 51658
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 51866
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 7,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 51907
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 6,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 11,
|
||||||
|
"weapon": "degreaser",
|
||||||
|
"tick": 52054
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": 13,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "tf_projectile_pipe",
|
||||||
|
"tick": 52087
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 10,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 12,
|
||||||
|
"weapon": "tf_projectile_rocket",
|
||||||
|
"tick": 52117
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 10,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 52431
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 5,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 9,
|
||||||
|
"weapon": "ubersaw",
|
||||||
|
"tick": 52628
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 14,
|
||||||
|
"assister": 13,
|
||||||
|
"victim": 5,
|
||||||
|
"weapon": "ubersaw",
|
||||||
|
"tick": 52721
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 11,
|
||||||
|
"victim": 4,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 55162
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 3,
|
||||||
|
"assister": 4,
|
||||||
|
"victim": 9,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 55369
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 13,
|
||||||
|
"assister": 11,
|
||||||
|
"victim": 7,
|
||||||
|
"weapon": "pistol_scout",
|
||||||
|
"tick": 55645
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "tf_projectile_pipe",
|
||||||
|
"tick": 56902
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 10,
|
||||||
|
"assister": 5,
|
||||||
|
"victim": 8,
|
||||||
|
"weapon": "tf_projectile_rocket",
|
||||||
|
"tick": 57415
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 5,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 57785
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 10,
|
||||||
|
"weapon": "tf_projectile_pipe",
|
||||||
|
"tick": 57963
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 7,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 13,
|
||||||
|
"weapon": "force_a_nature",
|
||||||
|
"tick": 58048
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 4,
|
||||||
|
"assister": 7,
|
||||||
|
"victim": 11,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 58164
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 12,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 58395
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 7,
|
||||||
|
"assister": 3,
|
||||||
|
"victim": 12,
|
||||||
|
"weapon": "force_a_nature",
|
||||||
|
"tick": 58756
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 7,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 58758
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 59038
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 14,
|
||||||
|
"assister": 9,
|
||||||
|
"victim": 4,
|
||||||
|
"weapon": "ubersaw",
|
||||||
|
"tick": 59157
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 11,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 61550
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": 8,
|
||||||
|
"victim": 4,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 61819
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 8,
|
||||||
|
"victim": 10,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 62325
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 13,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 7,
|
||||||
|
"weapon": "pistol_scout",
|
||||||
|
"tick": 62772
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 6,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 8,
|
||||||
|
"weapon": "sniperrifle",
|
||||||
|
"tick": 63063
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 5,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 9,
|
||||||
|
"weapon": "ubersaw",
|
||||||
|
"tick": 63337
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 63853
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": 13,
|
||||||
|
"victim": 7,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 64916
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 13,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 10,
|
||||||
|
"weapon": "world",
|
||||||
|
"tick": 65203
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 65305
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 11,
|
||||||
|
"assister": 8,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 66212
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": 13,
|
||||||
|
"victim": 5,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 66256
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 4,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 66343
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 10,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 11,
|
||||||
|
"weapon": "tf_projectile_rocket",
|
||||||
|
"tick": 68654
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 13,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 10,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 68695
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 13,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 68952
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 9,
|
||||||
|
"assister": 8,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 69063
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 5,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 12,
|
||||||
|
"weapon": "iron_bomber",
|
||||||
|
"tick": 69227
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 5,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 9,
|
||||||
|
"weapon": "iron_bomber",
|
||||||
|
"tick": 71107
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": 14,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "tf_projectile_pipe",
|
||||||
|
"tick": 72276
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 7,
|
||||||
|
"assister": 3,
|
||||||
|
"victim": 8,
|
||||||
|
"weapon": "force_a_nature",
|
||||||
|
"tick": 73216
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 7,
|
||||||
|
"weapon": "tf_projectile_pipe",
|
||||||
|
"tick": 73641
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 3,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 11,
|
||||||
|
"weapon": "quake_rl",
|
||||||
|
"tick": 73797
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 5,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 9,
|
||||||
|
"weapon": "tf_projectile_pipe_remote",
|
||||||
|
"tick": 74070
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 13,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 3,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 74284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 14,
|
||||||
|
"assister": 12,
|
||||||
|
"victim": 4,
|
||||||
|
"weapon": "crusaders_crossbow",
|
||||||
|
"tick": 74355
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 5,
|
||||||
|
"weapon": "tf_projectile_pipe",
|
||||||
|
"tick": 74639
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 12,
|
||||||
|
"assister": null,
|
||||||
|
"victim": 10,
|
||||||
|
"weapon": "tf_projectile_pipe",
|
||||||
|
"tick": 74639
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"killer": 13,
|
||||||
|
"assister": 12,
|
||||||
|
"victim": 6,
|
||||||
|
"weapon": "scattergun",
|
||||||
|
"tick": 74679
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"assister": null,
|
||||||
|
"victim": 8,
|
||||||
|
"weapon": "worldspawn",
|
||||||
|
"tick": 75044
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rounds": [
|
||||||
|
{
|
||||||
|
"winner": "red",
|
||||||
|
"length": 73.81500244140625,
|
||||||
|
"end_tick": 46460
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"winner": "red",
|
||||||
|
"length": 86.14501953125,
|
||||||
|
"end_tick": 52870
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"winner": "red",
|
||||||
|
"length": 85.76995849609375,
|
||||||
|
"end_tick": 59255
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"winner": "red",
|
||||||
|
"length": 97.02001953125,
|
||||||
|
"end_tick": 66390
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"winner": "red",
|
||||||
|
"length": 117.405029296875,
|
||||||
|
"end_tick": 74884
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"startTick": 41206,
|
||||||
|
"intervalPerTick": 0.014999999664723873
|
||||||
|
}
|
||||||
39
src/tests/snapshot/transcode.ts
Normal file
39
src/tests/snapshot/transcode.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import {BitStream} from 'bit-buffer';
|
||||||
|
import {readFileSync} from 'fs';
|
||||||
|
import {DynamicBitStream} from '../../DynamicBitStream';
|
||||||
|
import {nullTransform, Transformer} from '../../Transformer';
|
||||||
|
import {Parser} from '../../Parser';
|
||||||
|
import {Analyser} from '../../Analyser';
|
||||||
|
|
||||||
|
function testDemo(name: string) {
|
||||||
|
const target = JSON.parse(readFileSync(`${__dirname}/../data/${name}.json`, 'utf8'));
|
||||||
|
const decodeStream = new BitStream(
|
||||||
|
readFileSync(`${__dirname}/../data/${name}.dem`).buffer as ArrayBuffer
|
||||||
|
);
|
||||||
|
const encodeStream = new DynamicBitStream(32 * 1024 * 1024);
|
||||||
|
|
||||||
|
const transformer = new Transformer(decodeStream, encodeStream);
|
||||||
|
transformer.transform(nullTransform, nullTransform);
|
||||||
|
|
||||||
|
const encodedLength = encodeStream.index;
|
||||||
|
encodeStream.index = 0;
|
||||||
|
|
||||||
|
console.log('start reparse');
|
||||||
|
|
||||||
|
const reParser = new Parser(encodeStream);
|
||||||
|
const analyser = new Analyser(reParser);
|
||||||
|
const parsed = analyser.getBody().getState();
|
||||||
|
|
||||||
|
const reParsedLength = encodeStream.index;
|
||||||
|
|
||||||
|
assert.equal(reParsedLength, encodedLength, 'Unexpected number of bits used when parsing encoding stream');
|
||||||
|
|
||||||
|
assert.deepEqual(JSON.parse(JSON.stringify(parsed)), target);
|
||||||
|
}
|
||||||
|
|
||||||
|
suite('Transcode demo', () => {
|
||||||
|
test('Noop transcode', () => {
|
||||||
|
testDemo('short');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue