UNPKG

@abextm/cache2

Version:

Utilities for reading OSRS "caches"

153 lines (152 loc) 4.9 kB
import { gunzipSync } from "fflate"; import * as bz2 from "./bz2.js"; import { Reader } from "./Reader.js"; import { CompressionType } from "./types.js"; import { decryptXTEA } from "./xtea.js"; export class ArchiveFile { id; namehash; data; constructor(id, namehash) { this.id = id; this.namehash = namehash; } } export class ArchiveData { index; archive; constructor(index, archive) { this.index = index; this.archive = archive; } compressedData; namehash; revision; crc; decompressedSize; compressedSize; /**@internal*/ files = new Map(); key; /**@internal*/ decryptedData; maxFile = 0; /**@internal*/ addFile(id, nameHash) { this.maxFile = Math.max(id, this.maxFile); this.files.set(id, new ArchiveFile(id, nameHash)); } get compression() { return this.compressedData[0]; } /**@internal*/ getCryptedBlob() { let dv = Reader.makeViewOf(DataView, this.compressedData); let cLen = dv.getInt32(1); return this.compressedData.subarray(5, 5 + cLen + (this.compression == CompressionType.NONE ? 0 : 4)); } getDecryptedData() { if (this.decryptedData) { return this.decryptedData; } let mode = this.compression; let data = this.getCryptedBlob(); if (this.key) { data = decryptXTEA(data, this.key); } if (mode == CompressionType.NONE) { // noop } else if (mode == CompressionType.BZ2) { let outLen = Reader.makeViewOf(DataView, data).getUint32(0); let inData = data.subarray(4); data = bz2.decompress(inData, 1, outLen); } else if (mode == CompressionType.GZIP) { let inData = data.subarray(4); data = gunzipSync(inData); } else { throw new Error(`unsupported compression ${mode}`); } this.decryptedData = data; if (this.files.size == 1) { this.files.values().next().value.data = data; } else { let fileCount = this.files.size; let dv = Reader.makeViewOf(DataView, data); let numChunks = dv.getUint8(dv.byteLength - 1); let off = dv.byteLength - 1 - numChunks * fileCount * 4; let doff = 0; if (numChunks == 1) { let size = 0; for (let file of this.files.values()) { size += dv.getInt32(off); off += 4; file.data = data.subarray(doff, doff + size); doff += size; } } else { let sizeStride = numChunks + 1; let sizes = new Uint32Array(sizeStride * fileCount); for (let ch = 0; ch < numChunks; ch++) { let size = 0; for (let id = 0; id < fileCount; id++) { size += dv.getInt32(off); off += 4; let soff = id * sizeStride; sizes[soff] += size; sizes[soff + 1 + ch] = size; } } let fileData = []; for (let file of this.files.values()) { let soff = fileData.length * sizeStride; fileData.push(file.data = new Uint8Array(sizes[soff])); sizes[soff] = 0; } for (let ch = 0; ch < numChunks; ch++) { for (let id = 0; id < fileCount; id++) { let soff = id * sizeStride; let cSize = sizes[soff + 1 + ch]; let start = sizes[soff]; fileData[id].set(data.subarray(doff, doff + cSize), start); sizes[soff] = start + cSize; doff += cSize; } } } } return data; } getFile(id) { this.getDecryptedData(); return this.files.get(id); } getFiles() { this.getDecryptedData(); return this.files; } } export function hash(name) { if (typeof name === "number") { return name; } let bytes = new TextEncoder().encode(name); let h = 0; for (let v of bytes) { h = ~~((31 * h) + (v & 0xFF)); } return h; } export var CacheVersion; (function (CacheVersion) { function isAfter(prev, after) { if (prev === undefined) { return true; } if (prev.era == after.era) { return prev.indexRevision > after.indexRevision; } return false; } CacheVersion.isAfter = isAfter; })(CacheVersion || (CacheVersion = {}));