@runejs/filestore
Version:
Tools for managing the RuneJS filestore.
109 lines (108 loc) • 3.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.decompress = decompress;
exports.decryptXtea = decryptXtea;
exports.decompressBzip = decompressBzip;
const common_1 = require("@runejs/common");
const node_zlib_1 = require("node:zlib");
const seekBzip = require('seek-bzip');
function decompress(buffer, keys) {
if (!buffer || buffer.length === 0) {
return { compression: -1, buffer: null, version: -1 };
}
const compression = buffer.get('BYTE', 'UNSIGNED');
const compressedLength = buffer.get('INT');
if (keys &&
keys.length === 4 &&
(keys[0] !== 0 || keys[1] !== 0 || keys[2] !== 0 || keys[3] !== 0)) {
const readerIndex = buffer.readerIndex;
let lengthOffset = readerIndex;
if (buffer.length - (compressedLength + readerIndex + 4) >= 2) {
lengthOffset += 2;
}
const decryptedData = decryptXtea(buffer, keys, buffer.length - lengthOffset);
decryptedData.copy(buffer, readerIndex, 0);
buffer.readerIndex = readerIndex;
}
if (compression === 0) {
// Uncompressed file
const data = new common_1.ByteBuffer(compressedLength);
buffer.copy(data, 0, buffer.readerIndex, compressedLength);
const decryptedData = decryptXtea(data, keys, compressedLength);
buffer.readerIndex = buffer.readerIndex + compressedLength;
let version = -1;
if (buffer.readable >= 2) {
version = buffer.get('SHORT');
}
return { compression, buffer: decryptedData, version };
}
// Compressed file
const uncompressedLength = buffer.get('INT');
if (uncompressedLength < 0) {
throw new Error('Invalid uncompressed length');
}
const decryptedData = new common_1.ByteBuffer(compression === 1
? uncompressedLength
: buffer.length - buffer.readerIndex + 2);
buffer.copy(decryptedData, 0, buffer.readerIndex);
let decompressed;
if (compression === 1) {
// BZIP2
decompressed = decompressBzip(decryptedData);
}
else if (compression === 2) {
// GZIP
decompressed = new common_1.ByteBuffer((0, node_zlib_1.gunzipSync)(decryptedData));
}
else {
throw new Error('Invalid compression type');
}
buffer.readerIndex = buffer.readerIndex + compressedLength;
if (decompressed.length !== uncompressedLength) {
throw new Error('Length mismatch');
}
let version = -1;
if (buffer.readable >= 2) {
version = buffer.get('SHORT');
}
return { compression, buffer: decompressed, version };
}
function decryptXtea(input, keys, length) {
if (!keys || keys.length === 0) {
return input;
}
const output = new common_1.ByteBuffer(length);
const numBlocks = Math.floor(length / 8);
for (let block = 0; block < numBlocks; block++) {
let v0 = input.get('INT');
let v1 = input.get('INT');
let sum = 0x9e3779b9 * 32;
for (let i = 0; i < 32; i++) {
v1 -=
((toInt(v0 << 4) ^ toInt(v0 >>> 5)) + v0) ^
(sum + keys[(sum >>> 11) & 3]);
v1 = toInt(v1);
sum -= 0x9e3779b9;
v0 -=
((toInt(v1 << 4) ^ toInt(v1 >>> 5)) + v1) ^
(sum + keys[sum & 3]);
v0 = toInt(v0);
}
output.put(v0, 'INT');
output.put(v1, 'INT');
}
input.copy(output, output.writerIndex, input.readerIndex);
return output;
}
function toInt(value) {
return value | 0;
}
function decompressBzip(data) {
const buffer = Buffer.alloc(data.length + 4);
data.copy(buffer, 4);
buffer[0] = 'B'.charCodeAt(0);
buffer[1] = 'Z'.charCodeAt(0);
buffer[2] = 'h'.charCodeAt(0);
buffer[3] = '1'.charCodeAt(0);
return new common_1.ByteBuffer(seekBzip.decode(buffer));
}