UNPKG

@runejs/filestore

Version:

Tools for managing the RuneJS filestore.

233 lines (232 loc) 7.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FileIndex = exports.getIndexId = exports.indexIdMap = void 0; const common_1 = require("@runejs/common"); const archive_1 = require("./archive"); const file_data_1 = require("./file-data"); const data_1 = require("./data"); const util_1 = require("./util"); const NAME_FLAG = 0x01; const WHIRLPOOL_FLAG = 0x02; /** * A map of unique index keys to numeric ids. */ exports.indexIdMap = { skeletons: 0, frames: 1, configs: 2, widgets: 3, sounds: 4, regions: 5, music: 6, models: 7, sprites: 8, textures: 9, binary: 10, jingles: 11, scripts: 12, }; /** * Finds the corresponding string index key for the given numeric id. * @param index The numeric index id to find the name of. */ const getIndexId = (index) => { const ids = Object.keys(exports.indexIdMap); for (const id of ids) { if (exports.indexIdMap[id] === index) { return id; } } return null; }; exports.getIndexId = getIndexId; class FileIndex { /** * The ID of this File Index. */ indexId; /** * The file format used by the File Index. */ format; /** * The current version of the File Index, if versioned. */ version; /** * The method used by the File Index for data compression. */ compression; /** * Additional settings and information about the File Index (name & whirlpool information). */ settings; /** * A map of all files housed within this File Index. Values are either an `Archive` or `FileData` object. */ files = new Map(); filestoreChannels; /** * Creates a new File Index with the specified index ID and filestore channel. * @param indexId The ID of this File Index. * @param filestoreChannels The main filestore channel for data access. */ constructor(indexId, filestoreChannels) { this.indexId = indexId; this.filestoreChannels = filestoreChannels; } getFile(fileIdOrName, keys) { let fileData; if (typeof fileIdOrName === 'string') { fileData = this.findByName(fileIdOrName); } else { const archiveId = fileIdOrName; fileData = this.files.get(archiveId); } if (!fileData) { return null; } if (fileData.type === 'archive') { common_1.logger.error(fileData); throw new Error(`Requested item ${fileIdOrName} in index ${this.indexId} is of type Archive, not FileData.`); } try { fileData.decompress(keys); } catch (e) { common_1.logger.warn(`Unable to decompress file ${fileIdOrName} in index ${this.indexId} with keys ${keys}`); return null; } return fileData; } getArchive(archiveIdOrName) { let archive; if (typeof archiveIdOrName === 'string') { archive = this.findByName(archiveIdOrName); } else { const archiveId = archiveIdOrName; archive = this.files.get(archiveId); } if (!archive) { return null; } if (archive.type === 'file') { throw new Error(`Requested item ${archiveIdOrName} in index ${this.indexId} is of type FileData, not Archive.`); } archive.decodeArchiveFiles(); return archive; } /** * Fetches an archive or file from this index by name. * @param fileName The name of the archive or file to search for. * @returns An Archive or FileData object, or null if no matching files were found with the specified name. */ findByName(fileName) { const indexFileCount = this.files.size; const nameHash = (0, util_1.hash)(fileName); for (let fileId = 0; fileId < indexFileCount; fileId++) { const item = this.files.get(fileId); if (item?.nameHash === nameHash) { return item; } } return null; } /** * Decodes the packed index file data from the filestore on disk. */ decodeIndex() { const indexEntry = (0, data_1.readIndexedDataChunk)(this.indexId, 255, this.filestoreChannels); const { compression, version, buffer } = (0, data_1.decompress)(indexEntry.dataFile); this.version = version; this.compression = compression; /* file header */ this.format = buffer.get('BYTE', 'UNSIGNED'); if (this.format >= 6) { this.version = buffer.get('INT'); } this.settings = buffer.get('BYTE', 'UNSIGNED'); /* file ids */ const fileCount = buffer.get('SHORT', 'UNSIGNED'); const ids = new Array(fileCount); let accumulator = 0; let size = -1; for (let i = 0; i < ids.length; i++) { const delta = buffer.get('SHORT', 'UNSIGNED'); ids[i] = accumulator += delta; if (ids[i] > size) { size = ids[i]; } } size++; for (const id of ids) { this.files.set(id, new file_data_1.FileData(id, this, this.filestoreChannels)); } /* read the name hashes if present */ if ((this.settings & NAME_FLAG) !== 0) { for (const id of ids) { const nameHash = buffer.get('INT'); this.files.get(id).nameHash = nameHash; } } /* read the crc values */ for (const id of ids) { this.files.get(id).crc = buffer.get('INT'); } /* read the whirlpool values */ if ((this.settings & WHIRLPOOL_FLAG) !== 0) { for (const id of ids) { buffer.copy(this.files.get(id).whirlpool, 0, buffer.readerIndex, buffer.readerIndex + 64); buffer.readerIndex = buffer.readerIndex + 64; } } /* read the version numbers */ for (const id of ids) { this.files.get(id).version = buffer.get('INT'); } /* read the child sizes */ const members = new Array(size).fill([]); for (const id of ids) { members[id] = new Array(buffer.get('SHORT', 'UNSIGNED')); } /* read the child ids */ for (const id of ids) { accumulator = 0; size = -1; for (let i = 0; i < members[id].length; i++) { const delta = buffer.get('SHORT', 'UNSIGNED'); members[id][i] = accumulator += delta; if (members[id][i] > size) { size = members[id][i]; } } size++; /* allocate specific entries within the array */ const file = this.files.get(id); if (members[id].length > 1) { if (file.type === 'file') { this.files.set(id, new archive_1.Archive(file, this, this.filestoreChannels)); } const archive = this.files.get(id); for (const childId of members[id]) { archive.files.set(childId, new file_data_1.FileData(childId, this, this.filestoreChannels)); } } } /* read the child name hashes */ if ((this.settings & NAME_FLAG) !== 0) { for (const id of ids) { const archive = this.files.get(id); for (const childId of members[id]) { const nameHash = buffer.get('INT'); if (archive?.files?.get(childId)) { archive.files.get(childId).nameHash = nameHash; } } } } } } exports.FileIndex = FileIndex;