osrscachereader
Version:
Read Old-School Runescape Cache files
171 lines (141 loc) • 6.3 kB
JavaScript
import * as gzip from "gzip-js";
import IndexType from "./cacheTypes/IndexType.js";
import compressjs from "@ledgerhq/compressjs";
import WorkerPool from "./WorkerPool.js";
export default class CacheRequester {
constructor(datFile) {
this.workerPool = new WorkerPool(8);
this.promises = {};
this.datData = datFile;
}
setXteas(xteas) {
this.xteas = xteas;
}
readDataThreaded(index, size, segment, archiveId = 0) {
let key;
if (index.id == IndexType.MAPS.id && this.xteas != undefined) {
if (this.xteas[archiveId] != undefined) {
//if its not a mapdef then it will have a key
key = this.xteas[archiveId].key;
}
}
let compressedData = new Uint8Array(size);
this.readSector(compressedData, segment, archiveId);
compressedData = compressedData.buffer;
return this.workerPool.doWork(index, segment, archiveId, compressedData, key);
}
readData(index, size, segment, archiveId = 0, keys) {
let key;
if (index.id == IndexType.MAPS.id && this.xteas != undefined) {
if (this.xteas[archiveId] != undefined) {
//if its not a mapdef then it will have a key
key = this.xteas[archiveId].key;
}
}
return new Promise(async (resolve, reject) => {
let compressedData = new Uint8Array(size);
this.readSector(compressedData, segment, archiveId);
let dataview = new DataView(compressedData.buffer);
let compressionOpcode = dataview.getUint8(0);
let compressedLength = dataview.getUint32(1);
let data;
let decompressedData;
if (compressionOpcode == 0) {
//none
data = new Uint8Array(dataview.buffer.slice(5, 5 + compressedLength));
data = this.decrypt(data, compressedLength, key);
decompressedData = data;
index.revision = dataview.getUint16(data.buffer.byteLength);
} else if (compressionOpcode == 1) {
//bz2
let decompressedLength = dataview.getInt32(5);
data = new Uint8Array(dataview.buffer.slice(9, 9 + compressedLength));
this.decrypt(data, compressedLength, key);
let bzData = new Uint8Array(4 + data.length);
bzData[0] = "B".charCodeAt(0);
bzData[1] = "Z".charCodeAt(0);
bzData[2] = "h".charCodeAt(0);
bzData[3] = "1".charCodeAt(0);
bzData.set(data, 4);
decompressedData = compressjs.Bzip2.decompressFile(bzData);
} else if (compressionOpcode == 2) {
//gzip
let unencryptedData = new Uint8Array(dataview.buffer.slice(5, 9 + compressedLength));
data = this.decrypt(unencryptedData, unencryptedData.length, key);
let leftOver = unencryptedData.slice(data.length);
let mergedArray = new Uint8Array(data.length + leftOver.length);
mergedArray.set(data);
mergedArray.set(leftOver, data.length);
//add the end of the compressed data onto the decrypted?
let decryptedDataview = new DataView(mergedArray.buffer);
data = new Uint8Array(decryptedDataview.buffer.slice(4));
let unzipped;
try {
unzipped = gzip.unzip(data);
} catch {
console.warn("Could not unzip with key:" + key);
}
decompressedData = new Uint8Array(unzipped);
}
resolve({ index, archiveId, decompressedData });
});
}
readSector(buffer, pos, archiveId) {
let convertedPos = pos * 520;
let dataview = new DataView(this.datData.buffer);
let currentArchive;
let currentPart;
let nextSector;
let currentIndex;
if (archiveId > 0xffff) {
currentArchive = dataview.getUint32(convertedPos + 0);
currentPart = dataview.getUint16(convertedPos + 4);
nextSector = dataview.getUint24(convertedPos + 6);
currentIndex = dataview.getUint8(convertedPos + 4);
} else {
currentArchive = dataview.getUint16(convertedPos + 0);
currentPart = dataview.getUint16(convertedPos + 2);
nextSector = dataview.getUint24(convertedPos + 4);
currentIndex = dataview.getUint8(convertedPos + 7);
}
let data;
if (nextSector != 0 || buffer.byteLength == 512 || buffer.byteLength % 512 == 0)
data = new Uint8Array(dataview.buffer.slice(convertedPos + 8, convertedPos + 520));
else
data = new Uint8Array(
dataview.buffer.slice(convertedPos + 8, convertedPos + 8 + (buffer.byteLength % 512)),
);
buffer.set(data, dataview.getInt16(convertedPos + 2) * 512);
if (nextSector != 0) this.readSector(buffer, nextSector);
}
decrypt(data, len, key) {
if (key == undefined) {
return data;
}
let GOLDEN_RATIO = 0x9e3779b9;
let ROUNDS = 32;
let dataview = new DataView(data.buffer);
let out = [];
let numBlocks = Math.floor(len / 8);
//console.log(numBlocks);
for (let block = 0; block < numBlocks; ++block) {
let v0 = dataview.readInt32();
let v1 = dataview.readInt32();
let sum = GOLDEN_RATIO * ROUNDS;
for (let i = 0; i < ROUNDS; ++i) {
v1 -= (((v0 << 4) ^ (v0 >>> 5)) + v0) ^ (sum + key[(sum >>> 11) & 3]);
sum -= GOLDEN_RATIO;
v0 -= (((v1 << 4) ^ (v1 >>> 5)) + v1) ^ (sum + key[sum & 3]);
}
out.push((v0 >> 24) & 0xff);
out.push((v0 >> 16) & 0xff);
out.push((v0 >> 8) & 0xff);
out.push(v0 & 0xff);
out.push((v1 >> 24) & 0xff);
out.push((v1 >> 16) & 0xff);
out.push((v1 >> 8) & 0xff);
out.push(v1 & 0xff);
}
return new Uint8Array(out);
}
}