mirror of
https://github.com/demostf/demo.js
synced 2026-06-04 00:54:14 +02:00
encoder for updateStringTable
This commit is contained in:
parent
89b9c3b25c
commit
ae79b6a0d4
11 changed files with 280 additions and 57 deletions
|
|
@ -4,7 +4,7 @@ import {handleGameEvent} from '../PacketHandler/GameEvent';
|
||||||
import {handleGameEventList} from '../PacketHandler/GameEventList';
|
import {handleGameEventList} from '../PacketHandler/GameEventList';
|
||||||
import {handlePacketEntities} from '../PacketHandler/PacketEntities';
|
import {handlePacketEntities} from '../PacketHandler/PacketEntities';
|
||||||
import {handleSayText2} from '../PacketHandler/SayText2';
|
import {handleSayText2} from '../PacketHandler/SayText2';
|
||||||
import {handleStringTable, handleStringTables} from '../PacketHandler/StringTable';
|
import {handleStringTable, handleStringTables, handleStringTableUpdate} from '../PacketHandler/StringTable';
|
||||||
import {Building} from './Building';
|
import {Building} from './Building';
|
||||||
import {Death} from './Death';
|
import {Death} from './Death';
|
||||||
import {GameEventDefinitionMap} from './GameEvent';
|
import {GameEventDefinitionMap} from './GameEvent';
|
||||||
|
|
@ -149,9 +149,11 @@ export class Match {
|
||||||
handleStringTables(packet, this);
|
handleStringTables(packet, this);
|
||||||
break;
|
break;
|
||||||
case 'createStringTable':
|
case 'createStringTable':
|
||||||
case 'updateStringTable':
|
|
||||||
handleStringTable(packet, this);
|
handleStringTable(packet, this);
|
||||||
break;
|
break;
|
||||||
|
case 'updateStringTable':
|
||||||
|
handleStringTableUpdate(packet, this);
|
||||||
|
break;
|
||||||
case 'gameEventList':
|
case 'gameEventList':
|
||||||
handleGameEventList(packet, this);
|
handleGameEventList(packet, this);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {GameEvent, GameEventDefinitionMap} from './GameEvent';
|
||||||
import {PacketEntity} from './PacketEntity';
|
import {PacketEntity} from './PacketEntity';
|
||||||
import {SendTable} from './SendTable';
|
import {SendTable} from './SendTable';
|
||||||
import {ServerClass} from './ServerClass';
|
import {ServerClass} from './ServerClass';
|
||||||
import {StringTable} from './StringTable';
|
import {StringTable, StringTableEntry} from './StringTable';
|
||||||
import {Vector} from './Vector';
|
import {Vector} from './Vector';
|
||||||
|
|
||||||
export interface StringTablePacket {
|
export interface StringTablePacket {
|
||||||
|
|
@ -18,7 +18,8 @@ export interface CreateStringTablePacket {
|
||||||
|
|
||||||
export interface UpdateStringTablePacket {
|
export interface UpdateStringTablePacket {
|
||||||
packetType: 'updateStringTable';
|
packetType: 'updateStringTable';
|
||||||
table: StringTable;
|
entries: StringTableEntry[];
|
||||||
|
tableId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConsoleCmdPacket {
|
export interface ConsoleCmdPacket {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import {Match} from '../Data/Match';
|
||||||
import {CreateStringTablePacket, StringTablePacket, UpdateStringTablePacket} from '../Data/Packet';
|
import {CreateStringTablePacket, StringTablePacket, UpdateStringTablePacket} from '../Data/Packet';
|
||||||
import {StringTable, StringTableEntry} from '../Data/StringTable';
|
import {StringTable, StringTableEntry} from '../Data/StringTable';
|
||||||
|
|
||||||
export function handleStringTable(packet: CreateStringTablePacket | UpdateStringTablePacket, match: Match) {
|
export function handleStringTable(packet: CreateStringTablePacket, match: Match) {
|
||||||
handleTable(packet.table, match);
|
handleTable(packet.table, match);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -12,13 +12,37 @@ export function handleStringTables(packet: StringTablePacket, match: Match) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function handleStringTableUpdate(packet: UpdateStringTablePacket, match: Match) {
|
||||||
|
const updatedTable = match.stringTables[packet.tableId];
|
||||||
|
handleStringTableEntries(updatedTable.name, packet.entries, match);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function handleTable(table: StringTable, match: Match) {
|
function handleTable(table: StringTable, match: Match) {
|
||||||
if (!match.getStringTable(table.name)) {
|
if (!match.getStringTable(table.name)) {
|
||||||
match.stringTables.push(table);
|
match.stringTables.push(table);
|
||||||
}
|
}
|
||||||
if (table.name === 'userinfo') {
|
|
||||||
for (const userData of table.entries) {
|
handleStringTableEntries(table.name, table.entries, match);
|
||||||
if (userData.extraData) {
|
}
|
||||||
|
|
||||||
|
function handleStringTableEntries(tableName: string, entries: StringTableEntry[], match: Match) {
|
||||||
|
if (tableName === 'userinfo') {
|
||||||
|
for (const userData of entries) {
|
||||||
|
saveUserData(userData, match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tableName === 'instancebaseline') {
|
||||||
|
for (const instanceBaseLine of entries) {
|
||||||
|
if (instanceBaseLine) {
|
||||||
|
saveInstanceBaseLine(instanceBaseLine, match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveUserData(userData: StringTableEntry, match: Match) {
|
||||||
|
if (userData && userData.extraData) {
|
||||||
if (userData.extraData.bitsLeft > (32 * 8)) {
|
if (userData.extraData.bitsLeft > (32 * 8)) {
|
||||||
const name = userData.extraData.readUTF8String(32);
|
const name = userData.extraData.readUTF8String(32);
|
||||||
const userId = userData.extraData.readUint32();
|
const userId = userData.extraData.readUint32();
|
||||||
|
|
@ -32,15 +56,6 @@ function handleTable(table: StringTable, match: Match) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (table.name === 'instancebaseline') {
|
|
||||||
for (const instanceBaseLine of table.entries) {
|
|
||||||
if (instanceBaseLine) {
|
|
||||||
saveInstanceBaseLine(instanceBaseLine, match);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveInstanceBaseLine(entry: StringTableEntry, match: Match) {
|
function saveInstanceBaseLine(entry: StringTableEntry, match: Match) {
|
||||||
if (entry.extraData) {
|
if (entry.extraData) {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {PacketParserMap, voidEncoder} from '../Packet/Parser';
|
||||||
import {ParseParseSounds} from '../Packet/ParseSounds';
|
import {ParseParseSounds} from '../Packet/ParseSounds';
|
||||||
import {EncodeSetConVar, ParseSetConVar} from '../Packet/SetConVar';
|
import {EncodeSetConVar, ParseSetConVar} from '../Packet/SetConVar';
|
||||||
import {ParseTempEntities} from '../Packet/TempEntities';
|
import {ParseTempEntities} from '../Packet/TempEntities';
|
||||||
import {ParseUpdateStringTable} from '../Packet/UpdateStringTable';
|
import {EncodeUpdateStringTable, ParseUpdateStringTable} from '../Packet/UpdateStringTable';
|
||||||
import {ParseUserMessage} from '../Packet/UserMessage';
|
import {ParseUserMessage} from '../Packet/UserMessage';
|
||||||
import {ParseVoiceData} from '../Packet/VoiceData';
|
import {ParseVoiceData} from '../Packet/VoiceData';
|
||||||
import {ParseVoiceInit} from '../Packet/VoiceInit';
|
import {ParseVoiceInit} from '../Packet/VoiceInit';
|
||||||
|
|
@ -42,7 +42,7 @@ export class Packet extends Parser {
|
||||||
10: {parser: ParseClassInfo, encoder: EncodeClassInfo},
|
10: {parser: ParseClassInfo, encoder: EncodeClassInfo},
|
||||||
11: make('setPause', 'paused{b}'),
|
11: make('setPause', 'paused{b}'),
|
||||||
12: {parser: ParseCreateStringTable, encoder: EncodeCreateStringTable},
|
12: {parser: ParseCreateStringTable, encoder: EncodeCreateStringTable},
|
||||||
13: {parser: ParseUpdateStringTable, encoder: voidEncoder},
|
13: {parser: ParseUpdateStringTable, encoder: EncodeUpdateStringTable},
|
||||||
14: {parser: ParseVoiceInit, encoder: voidEncoder},
|
14: {parser: ParseVoiceInit, encoder: voidEncoder},
|
||||||
15: {parser: ParseVoiceData, encoder: voidEncoder},
|
15: {parser: ParseVoiceData, encoder: voidEncoder},
|
||||||
17: {parser: ParseParseSounds, encoder: voidEncoder},
|
17: {parser: ParseParseSounds, encoder: voidEncoder},
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ export function EncodeCreateStringTable(packet: CreateStringTablePacket, stream:
|
||||||
const encodeBits = logBase2(packet.table.maxEntries);
|
const encodeBits = logBase2(packet.table.maxEntries);
|
||||||
stream.writeBits(packet.table.entries.length, encodeBits + 1);
|
stream.writeBits(packet.table.entries.length, encodeBits + 1);
|
||||||
|
|
||||||
const entryData = new BitStream(new ArrayBuffer(guessStringTableEntryLength(packet.table)));
|
const entryData = new BitStream(new ArrayBuffer(guessStringTableEntryLength(packet.table, packet.table.entries)));
|
||||||
encodeStringTableEntries(entryData, packet.table);
|
encodeStringTableEntries(entryData, packet.table, packet.table.entries);
|
||||||
|
|
||||||
const entryLength = entryData.index;
|
const entryLength = entryData.index;
|
||||||
entryData.index = 0;
|
entryData.index = 0;
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,53 @@
|
||||||
import {BitStream} from 'bit-buffer';
|
import {BitStream} from 'bit-buffer';
|
||||||
import {Match} from '../../Data/Match';
|
import {Match} from '../../Data/Match';
|
||||||
import {UpdateStringTablePacket} from '../../Data/Packet';
|
import {UpdateStringTablePacket} from '../../Data/Packet';
|
||||||
import {parseStringTableEntries} from '../StringTableParser';
|
import {encodeStringTableEntries, guessStringTableEntryLength, parseStringTableEntries} from '../StringTableParser';
|
||||||
|
|
||||||
export function ParseUpdateStringTable(stream: BitStream, match: Match): UpdateStringTablePacket { // 12: updateStringTable
|
export function ParseUpdateStringTable(stream: BitStream, match: Match): UpdateStringTablePacket { // 12: updateStringTable
|
||||||
const tableId = stream.readBits(5);
|
const tableId = stream.readBits(5);
|
||||||
|
|
||||||
const multipleChanged = stream.readBoolean();
|
const multipleChanged = stream.readBoolean();
|
||||||
const changedEntries = (multipleChanged) ? stream.readBits(16) : 1;
|
const changedEntries = multipleChanged ? stream.readUint16() : 1;
|
||||||
|
|
||||||
const bitCount = stream.readBits(20);
|
const bitCount = stream.readBits(20);
|
||||||
const data = stream.readBitStream(bitCount);
|
const data = stream.readBitStream(bitCount);
|
||||||
|
data.index = 0;
|
||||||
|
|
||||||
if (!match.stringTables[tableId]) {
|
if (!match.stringTables[tableId]) {
|
||||||
throw new Error('Table not found for update');
|
throw new Error(`Table not found for update: ${tableId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = match.stringTables[tableId];
|
const table = match.stringTables[tableId];
|
||||||
const updatedEntries = parseStringTableEntries(data, table, changedEntries, table.entries);
|
const updatedEntries = parseStringTableEntries(data, table, changedEntries, table.entries);
|
||||||
|
|
||||||
for (let i = 0; i < updatedEntries.length; i++) {
|
|
||||||
if (updatedEntries[i]) {
|
|
||||||
table.entries[i] = updatedEntries[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
packetType: 'updateStringTable',
|
packetType: 'updateStringTable',
|
||||||
table: table,
|
entries: updatedEntries,
|
||||||
|
tableId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function EncodeUpdateStringTable(packet: UpdateStringTablePacket, stream: BitStream, match: Match) {
|
||||||
|
stream.writeBits(packet.tableId, 5);
|
||||||
|
|
||||||
|
const changedEntryCount = packet.entries.filter(entry => entry).length;
|
||||||
|
const multipleChanged = changedEntryCount > 1;
|
||||||
|
stream.writeBoolean(multipleChanged);
|
||||||
|
|
||||||
|
if (multipleChanged) {
|
||||||
|
stream.writeUint16(changedEntryCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!match.stringTables[packet.tableId]) {
|
||||||
|
throw new Error(`Table not found for update: ${packet.tableId}`);
|
||||||
|
}
|
||||||
|
const table = match.stringTables[packet.tableId];
|
||||||
|
const entryData = new BitStream(new ArrayBuffer(guessStringTableEntryLength(table, packet.entries)));
|
||||||
|
encodeStringTableEntries(entryData, table, packet.entries);
|
||||||
|
|
||||||
|
const entryLength = entryData.index;
|
||||||
|
entryData.index = 0;
|
||||||
|
|
||||||
|
stream.writeBits(entryLength, 20);
|
||||||
|
stream.writeBitStream(entryData, entryLength);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import {BitStream} from 'bit-buffer';
|
import {BitStream} from 'bit-buffer';
|
||||||
import {Match} from '../Data/Match';
|
|
||||||
import {StringTable, StringTableEntry} from '../Data/StringTable';
|
import {StringTable, StringTableEntry} from '../Data/StringTable';
|
||||||
import {logBase2} from '../Math';
|
import {logBase2} from '../Math';
|
||||||
|
|
||||||
|
|
@ -11,7 +10,7 @@ export function parseStringTableEntries(stream: BitStream, table: StringTable, e
|
||||||
const history: StringTableEntry[] = [];
|
const history: StringTableEntry[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < entryCount; i++) {
|
for (let i = 0; i < entryCount; i++) {
|
||||||
const entryIndex = (!stream.readBoolean()) ? stream.readBits(entryBits) : lastEntry + 1;
|
const entryIndex = !stream.readBoolean() ? stream.readBits(entryBits) : lastEntry + 1;
|
||||||
|
|
||||||
lastEntry = entryIndex;
|
lastEntry = entryIndex;
|
||||||
|
|
||||||
|
|
@ -31,7 +30,7 @@ export function parseStringTableEntries(stream: BitStream, table: StringTable, e
|
||||||
const restOfString = stream.readASCIIString();
|
const restOfString = stream.readASCIIString();
|
||||||
|
|
||||||
if (!history[index].text) {
|
if (!history[index].text) {
|
||||||
value = restOfString; // best guess, happens in some pov demos but only for unimported tables it seems
|
value = restOfString; // best guess, happens in some pov demos but only for unimportant tables it seems
|
||||||
} else {
|
} else {
|
||||||
value = history[index].text.substr(0, bytesToCopy) + restOfString;
|
value = history[index].text.substr(0, bytesToCopy) + restOfString;
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +66,6 @@ export function parseStringTableEntries(stream: BitStream, table: StringTable, e
|
||||||
text: value,
|
text: value,
|
||||||
extraData: userData,
|
extraData: userData,
|
||||||
};
|
};
|
||||||
console.log(entries[entryIndex]);
|
|
||||||
history.push(entries[entryIndex]);
|
history.push(entries[entryIndex]);
|
||||||
}
|
}
|
||||||
if (history.length > 32) {
|
if (history.length > 32) {
|
||||||
|
|
@ -78,10 +76,10 @@ export function parseStringTableEntries(stream: BitStream, table: StringTable, e
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function guessStringTableEntryLength(table: StringTable): number {
|
export function guessStringTableEntryLength(table: StringTable, entries: StringTableEntry[]): number {
|
||||||
// a rough guess of how many bytes are needed to encode the table entries
|
// a rough guess of how many bytes are needed to encode the table entries
|
||||||
const entryBytes = Math.ceil(logBase2(table.maxEntries) / 8);
|
const entryBytes = Math.ceil(logBase2(table.maxEntries) / 8);
|
||||||
return table.entries.reduce((length: number, entry: StringTableEntry) => {
|
return entries.reduce((length: number, entry: StringTableEntry) => {
|
||||||
return length +
|
return length +
|
||||||
entryBytes +
|
entryBytes +
|
||||||
1 + // misc boolean
|
1 + // misc boolean
|
||||||
|
|
@ -90,13 +88,13 @@ export function guessStringTableEntryLength(table: StringTable): number {
|
||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeStringTableEntries(stream: BitStream, table: StringTable) {
|
export function encodeStringTableEntries(stream: BitStream, table: StringTable, entries: StringTableEntry[]) {
|
||||||
const entryBits = logBase2(table.maxEntries);
|
const entryBits = logBase2(table.maxEntries);
|
||||||
let lastIndex = -1;
|
let lastIndex = -1;
|
||||||
for (let i = 0; i < table.entries.length; i++) {
|
for (let i = 0; i < entries.length; i++) {
|
||||||
if (table.entries[i]) {
|
if (entries[i]) {
|
||||||
const entry = table.entries[i];
|
const entry = entries[i];
|
||||||
if (i !== lastIndex) {
|
if (i !== (lastIndex + 1)) {
|
||||||
stream.writeBoolean(false);
|
stream.writeBoolean(false);
|
||||||
stream.writeBits(i, entryBits);
|
stream.writeBits(i, entryBits);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -113,10 +111,14 @@ export function encodeStringTableEntries(stream: BitStream, table: StringTable)
|
||||||
if (entry.extraData) {
|
if (entry.extraData) {
|
||||||
stream.writeBoolean(true);
|
stream.writeBoolean(true);
|
||||||
|
|
||||||
if (!table.fixedUserDataSizeBits) {
|
entry.extraData.index = 0;
|
||||||
stream.writeBits(Math.ceil(entry.extraData.length / 8), 14);
|
if (table.fixedUserDataSizeBits) {
|
||||||
|
stream.writeBitStream(entry.extraData, table.fixedUserDataSizeBits);
|
||||||
|
} else {
|
||||||
|
const byteLength = Math.ceil(entry.extraData.length / 8);
|
||||||
|
stream.writeBits(byteLength, 14);
|
||||||
|
stream.writeBitStream(entry.extraData, byteLength * 8);
|
||||||
}
|
}
|
||||||
stream.writeBitStream(entry.extraData);
|
|
||||||
entry.extraData.index = 0;
|
entry.extraData.index = 0;
|
||||||
} else {
|
} else {
|
||||||
stream.writeBoolean(false);
|
stream.writeBoolean(false);
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,7 @@ suite('CreateStringTable', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Encode createStringTable', () => {
|
test('Encode createStringTable', () => {
|
||||||
// more bits are used for encoding because we don't do a lot of compression tricks
|
assertEncoder(ParseCreateStringTable, EncodeCreateStringTable, examplePacket, 388);
|
||||||
assertEncoder(ParseCreateStringTable, EncodeCreateStringTable, examplePacket, 401);
|
assertEncoder(ParseCreateStringTable, EncodeCreateStringTable, examplePacket2, 615);
|
||||||
assertEncoder(ParseCreateStringTable, EncodeCreateStringTable, examplePacket2, 628);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import {BitStream} from 'bit-buffer';
|
import {BitStream} from 'bit-buffer';
|
||||||
import {Packet} from '../../../../Data/Packet';
|
import {Packet} from '../../../../Data/Packet';
|
||||||
|
import {deepEqual} from '../../deepEqual';
|
||||||
|
|
||||||
export function getStream(data: string | number[]) {
|
export function getStream(data: string | number[]) {
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
|
|
@ -28,14 +29,20 @@ export function assertEncoder(parser: Parser, encoder: Encoder, data: any, lengt
|
||||||
stream.index = 0;
|
stream.index = 0;
|
||||||
|
|
||||||
const result = parser(stream);
|
const result = parser(stream);
|
||||||
|
deepEqual(result, data);
|
||||||
|
if (!deepEqual(result, data)) {
|
||||||
assert.deepEqual(result, data, 'Re-decoded value not equal to original value');
|
assert.deepEqual(result, data, 'Re-decoded value not equal to original value');
|
||||||
|
}
|
||||||
assert.equal(stream.index, pos, 'Number of bits used for encoding and parsing not equal');
|
assert.equal(stream.index, pos, 'Number of bits used for encoding and parsing not equal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Parser = (stream: BitStream) => any;
|
export type Parser = (stream: BitStream, match?) => any;
|
||||||
|
|
||||||
export function assertParser(parser: Parser, stream: BitStream, expected: any, length: number) {
|
export function assertParser(parser: Parser, stream: BitStream, expected: any, length: number) {
|
||||||
const start = stream.index;
|
const start = stream.index;
|
||||||
assert.deepEqual(parser(stream), expected);
|
const result = parser(stream);
|
||||||
|
if (!deepEqual(result, expected)) {
|
||||||
|
assert.deepEqual(result, expected);
|
||||||
|
}
|
||||||
assert.equal(stream.index - start, length, 'Unexpected number of bits consumed from stream');
|
assert.equal(stream.index - start, length, 'Unexpected number of bits consumed from stream');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
src/tests/unit/Parser/Packet/UpdateStringTableTest.ts
Normal file
68
src/tests/unit/Parser/Packet/UpdateStringTableTest.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import {BitStream} from 'bit-buffer';
|
||||||
|
import {assertEncoder, assertParser, getStream} from './PacketTest';
|
||||||
|
import {EncodeUpdateStringTable, ParseUpdateStringTable} from '../../../../Parser/Packet/UpdateStringTable';
|
||||||
|
import {Match} from '../../../../Data/Match';
|
||||||
|
import {StringTable} from '../../../../Data/StringTable';
|
||||||
|
import {UpdateStringTablePacket} from '../../../../Data/Packet';
|
||||||
|
|
||||||
|
const exampleData = [200, 3, 0, 48, 130, 53];
|
||||||
|
|
||||||
|
function getExistingMatch() {
|
||||||
|
const existingTable: StringTable = {
|
||||||
|
name: 'downloadables',
|
||||||
|
entries: [],
|
||||||
|
maxEntries: 2048,
|
||||||
|
fixedUserDataSize: 1,
|
||||||
|
fixedUserDataSizeBits: 1
|
||||||
|
};
|
||||||
|
existingTable.entries[70] = {text: 'maps\\pl_badwater_pro_v9.bsp'};
|
||||||
|
const match = new Match();
|
||||||
|
match.stringTables[8] = existingTable;
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
const examplePacket: UpdateStringTablePacket = {
|
||||||
|
packetType: 'updateStringTable',
|
||||||
|
entries: [],
|
||||||
|
tableId: 8
|
||||||
|
};
|
||||||
|
const extraData = getStream(exampleData);
|
||||||
|
extraData.index = 40;
|
||||||
|
examplePacket.entries[70] = {
|
||||||
|
text: 'maps\\pl_badwater_pro_v9.bsp',
|
||||||
|
extraData: extraData.readBitStream(
|
||||||
|
1
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const examplePacket2: UpdateStringTablePacket = {
|
||||||
|
packetType: 'updateStringTable',
|
||||||
|
entries: [
|
||||||
|
{text: 'foo', extraData: undefined},
|
||||||
|
{text: 'foobar', extraData: undefined},
|
||||||
|
{text: 'assadasdas', extraData: undefined},
|
||||||
|
{text: 'foo', extraData: undefined}
|
||||||
|
],
|
||||||
|
tableId: 8
|
||||||
|
};
|
||||||
|
|
||||||
|
function ParseUpdate(stream: BitStream) {
|
||||||
|
return ParseUpdateStringTable(stream, getExistingMatch());
|
||||||
|
}
|
||||||
|
|
||||||
|
function EncodeUpdate(packet: UpdateStringTablePacket, stream: BitStream) {
|
||||||
|
return EncodeUpdateStringTable(packet, stream, getExistingMatch());
|
||||||
|
}
|
||||||
|
|
||||||
|
suite('UpdateStringTable', () => {
|
||||||
|
test('Parse updateStringTable', () => {
|
||||||
|
assertParser(ParseUpdate, getStream(exampleData), examplePacket, 41);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Encode updateStringTable', () => {
|
||||||
|
assertEncoder(ParseUpdate, EncodeUpdate, examplePacket, 266);
|
||||||
|
assertEncoder(ParseUpdate, EncodeUpdate, examplePacket2, 299);
|
||||||
|
});
|
||||||
|
});
|
||||||
108
src/tests/unit/deepEqual.ts
Normal file
108
src/tests/unit/deepEqual.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import {BitStream} from 'bit-buffer';
|
||||||
|
|
||||||
|
export interface EqualOpts {
|
||||||
|
strict?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deepEqual(actual, expected, opts: EqualOpts = {}) {
|
||||||
|
// 7.1. All identical values are equivalent, as determined by ===.
|
||||||
|
if (actual === expected) {
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else if (actual instanceof Date && expected instanceof Date) {
|
||||||
|
return actual.getTime() === expected.getTime();
|
||||||
|
|
||||||
|
// 7.3. Other pairs that do not both pass typeof value == 'object',
|
||||||
|
// equivalence is determined by ==.
|
||||||
|
} else if (!actual || !expected || typeof actual != 'object' && typeof expected != 'object') {
|
||||||
|
return opts.strict ? actual === expected : actual == expected;
|
||||||
|
|
||||||
|
// 7.4. For all other Object pairs, including Array objects, equivalence is
|
||||||
|
// determined by having the same number of owned properties (as verified
|
||||||
|
// with Object.prototype.hasOwnProperty.call), the same set of keys
|
||||||
|
// (although not necessarily the same order), equivalent values for every
|
||||||
|
// corresponding key, and an identical 'prototype' property. Note: this
|
||||||
|
// accounts for both named and indexed properties on Arrays.
|
||||||
|
} else {
|
||||||
|
return objEquiv(actual, expected, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUndefinedOrNull(value) {
|
||||||
|
return value === null || value === undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBuffer(x): x is Buffer {
|
||||||
|
if (!x || typeof x !== 'object' || typeof x.length !== 'number') return false;
|
||||||
|
if (typeof x.copy !== 'function' || typeof x.slice !== 'function') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !(x.length > 0 && typeof x[0] !== 'number');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStream(stream): stream is BitStream {
|
||||||
|
return stream instanceof BitStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
function objEquiv(a, b, opts) {
|
||||||
|
let i, key;
|
||||||
|
if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
|
||||||
|
return false;
|
||||||
|
// an identical 'prototype' property.
|
||||||
|
if (a.prototype !== b.prototype) return false;
|
||||||
|
//~~~I've managed to break Object.keys through screwy arguments passing.
|
||||||
|
// Converting to array solves the problem.
|
||||||
|
if (isBuffer(a)) {
|
||||||
|
if (!isBuffer(b)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
for (i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] !== b[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isStream(a)) {
|
||||||
|
if (!isStream(b)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
a.index = 0;
|
||||||
|
b.index = 0;
|
||||||
|
while (a.bitsLeft > 0) {
|
||||||
|
const bitsToRead = Math.min(32, a.bitsLeft);
|
||||||
|
if (a.readBits(bitsToRead) !== b.readBits(bitsToRead)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.index = 0;
|
||||||
|
b.index = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const ka = Object.keys(a);
|
||||||
|
const kb = Object.keys(b);
|
||||||
|
|
||||||
|
// having the same number of owned properties (keys incorporates
|
||||||
|
// hasOwnProperty)
|
||||||
|
if (ka.length != kb.length)
|
||||||
|
return false;
|
||||||
|
//the same set of keys (although not necessarily the same order),
|
||||||
|
ka.sort();
|
||||||
|
kb.sort();
|
||||||
|
//~~~cheap key test
|
||||||
|
for (i = ka.length - 1; i >= 0; i--) {
|
||||||
|
if (ka[i] != kb[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//equivalent values for every corresponding key, and
|
||||||
|
//~~~possibly expensive deep test
|
||||||
|
for (i = ka.length - 1; i >= 0; i--) {
|
||||||
|
key = ka[i];
|
||||||
|
if (!deepEqual(a[key], b[key], opts)) return false;
|
||||||
|
}
|
||||||
|
return typeof a === typeof b;
|
||||||
|
} catch (e) {//happens when one is a string literal and the other isn't
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue