@abextm/cache2
Version:
Utilities for reading OSRS "caches"
153 lines (152 loc) • 4.9 kB
JavaScript
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 = {}));