mirror of
https://github.com/demostf/demo.js
synced 2026-06-04 00:54:14 +02:00
add encoder for stringtable message
This commit is contained in:
parent
b94a79ab6d
commit
9820e0fc57
6 changed files with 164 additions and 30 deletions
|
|
@ -1,10 +1,12 @@
|
||||||
import {BitStream} from 'bit-buffer';
|
import {BitStream} from 'bit-buffer';
|
||||||
|
|
||||||
export interface StringTable {
|
export interface StringTable {
|
||||||
name: string;
|
name: string;
|
||||||
entries: StringTableEntry[];
|
entries: StringTableEntry[];
|
||||||
maxEntries: number;
|
maxEntries: number;
|
||||||
fixedUserDataSize?: number;
|
fixedUserDataSize?: number;
|
||||||
fixedUserDataSizeBits?: number;
|
fixedUserDataSizeBits?: number;
|
||||||
|
tableEntry?: StringTableEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StringTableEntry {
|
export interface StringTableEntry {
|
||||||
|
|
|
||||||
|
|
@ -12,36 +12,26 @@ export const StringTableHandler: MessageHandler<StringTablesMessage> = {
|
||||||
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/ST/StringTableParser.cs
|
// https://github.com/StatsHelix/demoinfo/blob/3d28ea917c3d44d987b98bb8f976f1a3fcc19821/DemoInfo/ST/StringTableParser.cs
|
||||||
const tableCount = messageStream.readUint8();
|
const tableCount = messageStream.readUint8();
|
||||||
const tables: StringTableObject[] = [];
|
const tables: StringTableObject[] = [];
|
||||||
let extraDataLength;
|
|
||||||
for (let i = 0; i < tableCount; i++) {
|
for (let i = 0; i < tableCount; i++) {
|
||||||
const entries: StringTableEntry[] = [];
|
const entries: StringTableEntry[] = [];
|
||||||
const tableName = messageStream.readASCIIString();
|
const tableName = messageStream.readASCIIString();
|
||||||
const entryCount = messageStream.readUint16();
|
const entryCount = messageStream.readUint16();
|
||||||
for (let j = 0; j < entryCount; j++) {
|
for (let j = 0; j < entryCount; j++) {
|
||||||
let entry: StringTableEntry;
|
const entry: StringTableEntry = {
|
||||||
try {
|
|
||||||
entry = {
|
|
||||||
text: messageStream.readUTF8String(),
|
text: messageStream.readUTF8String(),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
|
||||||
return {
|
|
||||||
type: MessageType.StringTables,
|
|
||||||
tick,
|
|
||||||
rawData: messageStream,
|
|
||||||
tables,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (messageStream.readBoolean()) {
|
if (messageStream.readBoolean()) {
|
||||||
extraDataLength = messageStream.readUint16();
|
const extraDataLength = messageStream.readUint16();
|
||||||
if ((extraDataLength * 8) > messageStream.bitsLeft) {
|
if ((extraDataLength * 8) > messageStream.bitsLeft) {
|
||||||
// extradata to long, can't continue parsing the tables
|
// extradata to long, can't continue parsing the tables
|
||||||
// seems to happen in POV demos after the MyM update
|
// seems to happen in POV demos after the MyM update
|
||||||
return {
|
throw new Error(`to long extraData ${extraDataLength} from ${messageStream.bitsLeft}`);
|
||||||
type: MessageType.StringTables,
|
// return {
|
||||||
tick,
|
// type: MessageType.StringTables,
|
||||||
rawData: messageStream,
|
// tick,
|
||||||
tables,
|
// rawData: messageStream,
|
||||||
};
|
// tables,
|
||||||
|
// };
|
||||||
}
|
}
|
||||||
entry.extraData = messageStream.readBitStream(extraDataLength * 8);
|
entry.extraData = messageStream.readBitStream(extraDataLength * 8);
|
||||||
}
|
}
|
||||||
|
|
@ -52,15 +42,16 @@ export const StringTableHandler: MessageHandler<StringTablesMessage> = {
|
||||||
name: tableName,
|
name: tableName,
|
||||||
maxEntries: entryCount,
|
maxEntries: entryCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (messageStream.readBoolean()) {
|
||||||
|
table.tableEntry = {text: messageStream.readASCIIString()};
|
||||||
|
if (messageStream.readBoolean()) {
|
||||||
|
const extraDataLength = messageStream.readBits(16);
|
||||||
|
table.tableEntry.extraData = messageStream.readBitStream(extraDataLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tables.push(table);
|
tables.push(table);
|
||||||
if (messageStream.readBits(1)) {
|
|
||||||
messageStream.readASCIIString();
|
|
||||||
if (messageStream.readBits(1)) {
|
|
||||||
// throw 'more extra data not implemented';
|
|
||||||
extraDataLength = messageStream.readBits(16);
|
|
||||||
messageStream.readBits(extraDataLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
type: MessageType.StringTables,
|
type: MessageType.StringTables,
|
||||||
|
|
@ -70,6 +61,56 @@ export const StringTableHandler: MessageHandler<StringTablesMessage> = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
encodeMessage: (message, stream) => {
|
encodeMessage: (message, stream) => {
|
||||||
throw new Error('Not implemented');
|
stream.writeUint32(message.tick);
|
||||||
|
|
||||||
|
const lengthStart = stream.index;
|
||||||
|
stream.index += 32;
|
||||||
|
const dataStart = stream.index;
|
||||||
|
|
||||||
|
stream.writeUint8(message.tables.length);
|
||||||
|
|
||||||
|
for (const table of message.tables) {
|
||||||
|
stream.writeASCIIString(table.name);
|
||||||
|
stream.writeUint16(table.entries.length);
|
||||||
|
|
||||||
|
for (const entry of table.entries) {
|
||||||
|
stream.writeUTF8String(entry.text);
|
||||||
|
if (entry.extraData) {
|
||||||
|
stream.writeBoolean(true);
|
||||||
|
|
||||||
|
stream.writeUint16(Math.ceil(entry.extraData.length / 8));
|
||||||
|
entry.extraData.index = 0;
|
||||||
|
stream.writeBitStream(entry.extraData, entry.extraData.length);
|
||||||
|
} else {
|
||||||
|
stream.writeBoolean(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table.tableEntry) {
|
||||||
|
stream.writeBoolean(true);
|
||||||
|
stream.writeASCIIString(table.tableEntry.text);
|
||||||
|
if (table.tableEntry.extraData) {
|
||||||
|
stream.writeBoolean(true);
|
||||||
|
stream.writeUint16(table.tableEntry.extraData.length);
|
||||||
|
table.tableEntry.extraData.index = 0;
|
||||||
|
stream.writeBitStream(table.tableEntry.extraData, table.tableEntry.extraData.length);
|
||||||
|
} else {
|
||||||
|
stream.writeBoolean(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stream.writeBoolean(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataEnd = stream.index;
|
||||||
|
|
||||||
|
stream.index = lengthStart;
|
||||||
|
|
||||||
|
const byteLength = Math.ceil((dataEnd - dataStart) / 8);
|
||||||
|
stream.writeUint32(byteLength);
|
||||||
|
|
||||||
|
// align to byte;
|
||||||
|
stream.index = dataStart + byteLength * 8;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
BIN
src/tests/data/stringTableData.bin
Normal file
BIN
src/tests/data/stringTableData.bin
Normal file
Binary file not shown.
1
src/tests/data/stringTableResult.json
Normal file
1
src/tests/data/stringTableResult.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -8,7 +8,6 @@ import {DataTablesMessage} from '../../../../Data/Message';
|
||||||
import {hydrateTable} from '../Packet/hydrate';
|
import {hydrateTable} from '../Packet/hydrate';
|
||||||
import {ServerClass} from '../../../../Data/ServerClass';
|
import {ServerClass} from '../../../../Data/ServerClass';
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import {deepEqual} from 'assert';
|
|
||||||
|
|
||||||
const data = Array.from(readFileSync(__dirname + '/../../../data/dataTableData.bin').values());
|
const data = Array.from(readFileSync(__dirname + '/../../../data/dataTableData.bin').values());
|
||||||
const expectedRaw = JSON.parse(gunzipSync(readFileSync(__dirname + '/../../../data/dataTableResult.json.gz')).toString('utf8')) as DataTablesMessage;
|
const expectedRaw = JSON.parse(gunzipSync(readFileSync(__dirname + '/../../../data/dataTableResult.json.gz')).toString('utf8')) as DataTablesMessage;
|
||||||
|
|
|
||||||
91
src/tests/unit/Parser/Message/StringTableTest.ts
Normal file
91
src/tests/unit/Parser/Message/StringTableTest.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import {StringTable, StringTableEntry} from '../../../../Data/StringTable';
|
||||||
|
import {readFileSync} from 'fs';
|
||||||
|
import {ParserState} from '../../../../Data/ParserState';
|
||||||
|
import {StringTableHandler} from '../../../../Parser/Message/StringTable';
|
||||||
|
import {assertEncoder, assertParser, getStream} from '../Packet/PacketTest';
|
||||||
|
import {BitStream} from 'bit-buffer';
|
||||||
|
|
||||||
|
const encodeEntry = (entry: StringTableEntry) => {
|
||||||
|
const encodeEntry: any = {
|
||||||
|
text: entry.text,
|
||||||
|
};
|
||||||
|
if (entry.extraData) {
|
||||||
|
encodeEntry.extraData = Array.from(entry.extraData.readArrayBuffer(Math.ceil(entry.extraData.length / 8)).values());
|
||||||
|
}
|
||||||
|
return encodeEntry;
|
||||||
|
};
|
||||||
|
const encodeTables = (tables) => {
|
||||||
|
return tables.map((table) => {
|
||||||
|
const encodeTable: any = {
|
||||||
|
name: table.name,
|
||||||
|
entries: table.entries.map(encodeEntry)
|
||||||
|
};
|
||||||
|
if (table.tableEntry) {
|
||||||
|
encodeTable.tableEntry = encodeEntry(table.tableEntry);
|
||||||
|
}
|
||||||
|
return encodeTable;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const decodeEntry = (entry) => {
|
||||||
|
const decodeEntry: any = {
|
||||||
|
text: entry.text,
|
||||||
|
};
|
||||||
|
if (entry.extraData) {
|
||||||
|
decodeEntry.extraData = getStream(entry.extraData);
|
||||||
|
}
|
||||||
|
return decodeEntry;
|
||||||
|
};
|
||||||
|
const decodeTables = (tables) => {
|
||||||
|
return tables.map((table) => {
|
||||||
|
const decodeTable: any = {
|
||||||
|
name: table.name,
|
||||||
|
entries: table.entries.map(decodeEntry)
|
||||||
|
};
|
||||||
|
if (table.tableEntry) {
|
||||||
|
decodeTable.tableEntry = decodeEntry(table.tableEntry);
|
||||||
|
}
|
||||||
|
return decodeTable;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = Array.from(readFileSync(__dirname + '/../../../data/stringTableData.bin').values());
|
||||||
|
const expectedRaw = {
|
||||||
|
type: 8,
|
||||||
|
tick: 21766,
|
||||||
|
tables: JSON.parse(readFileSync(__dirname + '/../../../data/stringTableResult.json', 'utf8'))
|
||||||
|
};
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
type: 8,
|
||||||
|
tick: 21766,
|
||||||
|
tables: decodeTables(expectedRaw.tables),
|
||||||
|
rawData: getStream('')
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getParserState = () => {
|
||||||
|
return new ParserState();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = StringTableHandler;
|
||||||
|
|
||||||
|
function parser(stream) {
|
||||||
|
const result: any = handler.parseMessage(stream, getParserState());
|
||||||
|
delete result.rawData;
|
||||||
|
result.tables = encodeTables(result.tables);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encoder(message, stream) {
|
||||||
|
handler.encodeMessage(expected, stream, getParserState());
|
||||||
|
}
|
||||||
|
|
||||||
|
suite('StringTable', () => {
|
||||||
|
test('Parse StringTable message', () => {
|
||||||
|
assertParser(parser, getStream(data), expectedRaw, 3690024);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Encode StringTable message', () => {
|
||||||
|
assertEncoder(parser, encoder, expectedRaw, 3690024);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue