@runejs/filestore
Version:
Tools for managing the RuneJS filestore.
233 lines (232 loc) • 7.87 kB
JavaScript
"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;