deadem
Version:
JavaScript (Node.js & Browsers) parser for Deadlock (Valve Source 2 Engine) demo/replay files
213 lines (162 loc) • 6.22 kB
JavaScript
import Assert from '#core/Assert.js';
import EventEmitter from '#core/EventEmitter.js';
import Logger from '#core/Logger.js';
import SnappyDecompressor from '#core/SnappyDecompressor.instance.js';
import StringTableEvent from '#data/enums/StringTableEvent.js';
import StringTableType from '#data/enums/StringTableType.js';
import StringTableEntryExtractor from '#extractors/StringTableEntryExtractor.js';
import StringTable from './StringTable.js';
import StringTableEntry from './StringTableEntry.js';
import StringTableInstructions from './StringTableInstructions.js';
class StringTableContainer {
/**
* @public
* @constructor
* @param {Logger} logger
*/
constructor(logger) {
Assert.isTrue(logger instanceof Logger);
this._eventEmitter = new EventEmitter(this);
this._registry = {
tableById: new Map(),
tableByName: new Map()
};
this._logger = logger;
}
/**
* @public
*
* @param {number} id
* @returns {StringTable}
*/
getById(id) {
return this._registry.tableById.get(id) || null;
}
/**
* @public
*
* @param {String} name
* @returns {StringTable}
*/
getByName(name) {
return this._registry.tableByName.get(name) || null;
}
/**
* @public
*/
handleClear() {
this._clear();
}
/**
* @public
* @param {CSVCMsg_CreateStringTable} createData
*/
handleCreate(createData) {
const stringTableType = StringTableType.parseByName(createData.name);
if (stringTableType === null) {
this._logger.warn(`Unable to identify table [ ${createData.name} ]`);
return;
}
const existing = this.getByName(stringTableType.name);
if (existing !== null) {
this._logger.warn(`StringTable [ ${stringTableType.name} ] exists in the registry. Overwriting`);
}
const instructions = new StringTableInstructions(createData.userDataSizeBits, createData.userDataFixedSize, createData.usingVarintBitcounts);
let payload;
if (createData.dataCompressed) {
payload = SnappyDecompressor.decompress(createData.stringData);
} else {
payload = createData.stringData;
}
const stringTable = new StringTable(stringTableType, createData.flags, instructions);
this._register(stringTable);
const entryExtractor = new StringTableEntryExtractor(payload, stringTable, createData.numEntries);
const extractor = entryExtractor.retrieve();
for (const entry of extractor) {
stringTable.updateEntry(entry);
}
this._eventEmitter.fire(StringTableEvent.TABLE_CREATED.name, stringTable);
this._eventEmitter.fire(StringTableEvent.TABLE_CHANGED.name, stringTable);
}
/**
* @public
* @param {CDemoStringTables} instantiateData
*/
handleInstantiate(instantiateData) {
instantiateData.tables.forEach((tableData) => {
const type = StringTableType.parseByName(tableData.tableName);
if (type === null) {
this._logger.warn(`Unable to identify table [ ${tableData.tableName} ]`);
return;
}
const stringTable = new StringTable(type, tableData.tableFlags, null);
tableData.items.forEach((entryData, index) => {
const entry = StringTableEntry.fromBuffer(entryData.data, stringTable.type, index, entryData.str);
stringTable.updateEntry(entry);
});
this._register(stringTable);
this._eventEmitter.fire(StringTableEvent.TABLE_CREATED.name, stringTable);
this._eventEmitter.fire(StringTableEvent.TABLE_CHANGED.name, stringTable);
});
}
/**
* @public
* @param {CSVCMsg_UpdateStringTable} updateData
*/
handleUpdate(updateData) {
const stringTable = this._registry.tableById.get(updateData.tableId) || null;
if (stringTable === null) {
throw new Error(`Unknown StringTable [ ${updateData.tableId} ]`);
}
this._logger.debug(`Updating StringTable: [ ${updateData.tableId} ] [ ${stringTable.type.name} ] [ ${updateData.numChangedEntries} ]`);
const entryExtractor = new StringTableEntryExtractor(updateData.stringData, stringTable, updateData.numChangedEntries);
const extractor = entryExtractor.retrieve();
for (const entry of extractor) {
stringTable.updateEntry(entry);
}
this._eventEmitter.fire(StringTableEvent.TABLE_UPDATED.name, stringTable);
this._eventEmitter.fire(StringTableEvent.TABLE_CHANGED.name, stringTable);
}
/**
* @public
* @param {StringTableEvent} event
* @param {Function} callback
*/
subscribe(event, callback) {
Assert.isTrue(event instanceof StringTableEvent);
Assert.isTrue(typeof callback === 'function');
this._eventEmitter.register(event.name, callback);
}
/**
* @public
* @param {StringTableEvent} event
* @param {Function} callback
*/
unsubscribe(event, callback) {
Assert.isTrue(event instanceof StringTableEvent);
Assert.isTrue(typeof callback === 'function');
this._eventEmitter.unregister(event.name, callback);
}
/**
* @private
*/
_clear() {
this._logger.debug('Clearing StringTable registry');
this._registry.tableById.forEach((stringTable) => {
this._eventEmitter.fire(StringTableEvent.TABLE_REMOVED.name, stringTable);
});
this._registry.tableByName.clear();
this._registry.tableById.clear();
}
/**
* @private
* @param {StringTable} stringTable
*/
_register(stringTable) {
const id = this._registry.tableById.size;
this._logger.debug(`Registering StringTable: [ ${id} ] [ ${stringTable.type.name} ]`);
this._registry.tableByName.set(stringTable.type.name, stringTable);
this._registry.tableById.set(id, stringTable);
}
}
export default StringTableContainer;