UNPKG

@rhyster/wow-casc-dbc

Version:

Fetch World of Warcraft data files from CASC and parse DBC/DB2 files.

1,324 lines (1,307 loc) 86.3 kB
'use strict'; const assert = require('node:assert'); const crypto = require('node:crypto'); const async = require('async'); const cliProgress = require('cli-progress'); const zlib = require('node:zlib'); const fs = require('node:fs/promises'); const https = require('node:https'); const path = require('node:path'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } const assert__default = /*#__PURE__*/_interopDefaultCompat(assert); const crypto__default = /*#__PURE__*/_interopDefaultCompat(crypto); const cliProgress__default = /*#__PURE__*/_interopDefaultCompat(cliProgress); const zlib__default = /*#__PURE__*/_interopDefaultCompat(zlib); const fs__default = /*#__PURE__*/_interopDefaultCompat(fs); const https__default = /*#__PURE__*/_interopDefaultCompat(https); const path__default = /*#__PURE__*/_interopDefaultCompat(path); const ADB_MAGIC = 1481004104; class ADBReader { build; entries = []; tableEntries = /* @__PURE__ */ new Map(); constructor(buffer) { const magic = buffer.readUInt32BE(0); assert__default(magic === ADB_MAGIC, `[ADB]: Invalid magic: ${magic.toString(16).padStart(8, "0")}`); const version = buffer.readUInt32LE(4); assert__default(version === 9, `[ADB]: Invalid version: ${version.toString()}`); const build = buffer.readUInt32LE(8); this.build = build; let pointer = 44; while (pointer < buffer.byteLength) { const offset = pointer; const entryMagic = buffer.readUInt32BE(offset); assert__default(entryMagic === ADB_MAGIC, `[ADB]: Invalid entry magic: ${magic.toString(16).padStart(8, "0")}`); const regionID = buffer.readInt32LE(offset + 4); const pushID = buffer.readInt32LE(offset + 8); const uniqueID = buffer.readUInt32LE(offset + 12); const tableHash = buffer.readUInt32LE(offset + 16); const recordID = buffer.readUInt32LE(offset + 20); const dataSize = buffer.readUInt32LE(offset + 24); const recordState = buffer.readUInt32LE(offset + 28); const data = buffer.subarray(offset + 32, offset + 32 + dataSize); const entry = { regionID, pushID, uniqueID, tableHash, recordID, dataSize, recordState, data }; this.entries.push(entry); if (!this.tableEntries.has(tableHash)) { this.tableEntries.set(tableHash, []); } this.tableEntries.get(tableHash)?.push(entry); pointer += 32 + dataSize; } } } class Salsa20 { fixed; key; nonce; counter = new Uint32Array([0, 0]); state = new Uint32Array(16); block = new Uint8Array(64); position = 0; constructor(key, nonce) { assert__default(key.length === 32 || key.length === 16, "Salsa20 requires 128-bit or 256-bit key"); assert__default(nonce.length === 8, "Salsa20 requires 64-bit nonce"); this.key = new Uint32Array(8); const keyView = new DataView(key.buffer); if (key.length === 32) { for (let i = 0; i < 8; i += 1) { this.key[i] = keyView.getUint32(i * 4, true); } this.fixed = new Uint32Array([ 1634760805, 857760878, 2036477234, 1797285236 ]); } else { for (let i = 0; i < 4; i += 1) { const word = keyView.getUint32(i * 4, true); this.key[i] = word; this.key[i + 4] = word; } this.fixed = new Uint32Array([ 1634760805, 824206446, 2036477238, 1797285236 ]); } this.nonce = new Uint32Array(2); const nonceView = new DataView(nonce.buffer); for (let i = 0; i < 2; i += 1) { this.nonce[i] = nonceView.getUint32(i * 4, true); } this.generateBlock(); } // eslint-disable-next-line @typescript-eslint/naming-convention QR(a, b, c, d) { let t; t = this.state[a] + this.state[d] & 4294967295; this.state[b] ^= t << 7 | t >>> 25; t = this.state[b] + this.state[a] & 4294967295; this.state[c] ^= t << 9 | t >>> 23; t = this.state[c] + this.state[b] & 4294967295; this.state[d] ^= t << 13 | t >>> 19; t = this.state[d] + this.state[c] & 4294967295; this.state[a] ^= t << 18 | t >>> 14; } generateBlock() { const init = new Uint32Array([ this.fixed[0], this.key[0], this.key[1], this.key[2], this.key[3], this.fixed[1], this.nonce[0], this.nonce[1], this.counter[0], this.counter[1], this.fixed[2], this.key[4], this.key[5], this.key[6], this.key[7], this.fixed[3] ]); this.state = new Uint32Array(init); for (let i = 0; i < 20; i += 2) { this.QR(0, 4, 8, 12); this.QR(5, 9, 13, 1); this.QR(10, 14, 2, 6); this.QR(15, 3, 7, 11); this.QR(0, 1, 2, 3); this.QR(5, 6, 7, 4); this.QR(10, 11, 8, 9); this.QR(15, 12, 13, 14); } for (let i = 0; i < 16; i += 1) { const word = this.state[i] + init[i] & 4294967295; this.block[i * 4] = word & 255; this.block[i * 4 + 1] = word >>> 8 & 255; this.block[i * 4 + 2] = word >>> 16 & 255; this.block[i * 4 + 3] = word >>> 24 & 255; } this.counter[0] = this.counter[0] + 1 & 4294967295; if (this.counter[0] === 0) { this.counter[1] = this.counter[1] + 1 & 4294967295; } } process(input) { const { length } = input; const result = new Uint8Array(length); for (let i = 0; i < length; i += 1) { if (this.position === 64) { this.generateBlock(); this.position = 0; } result[i] = input[i] ^ this.block[this.position]; this.position += 1; } return result; } } const BLTE_MAGIC = 1112298565; const ENC_TYPE_SALSA20 = 83; const EMPTY_HASH = "00000000000000000000000000000000"; class BLTEReader { buffer; blte; blocks = []; keys; processedBlock = 0; processedOffset = 0; constructor(buffer, eKey, keys = /* @__PURE__ */ new Map()) { this.blte = buffer; this.buffer = Buffer.alloc(0); this.keys = keys; const size = buffer.byteLength; assert__default(size >= 8, `[BLTE]: Invalid size: ${size.toString()} < 8`); const magic = buffer.readUInt32BE(0); assert__default(magic === BLTE_MAGIC, `[BLTE]: Invalid magic: ${magic.toString(16).padStart(8, "0")}`); const headerSize = buffer.readUInt32BE(4); if (headerSize === 0) { const blteHash2 = crypto__default.createHash("md5").update(buffer).digest("hex"); assert__default(blteHash2 === eKey, `[BLTE]: Invalid hash: expected ${eKey}, got ${blteHash2}`); this.blocks.push({ compressedSize: size - 8, decompressedSize: size - 9, hash: EMPTY_HASH }); this.processedOffset = 8; return; } const blteHash = crypto__default.createHash("md5").update(buffer.subarray(0, headerSize)).digest("hex"); assert__default(blteHash === eKey, `[BLTE]: Invalid hash: expected ${eKey}, got ${blteHash}`); assert__default(size >= 12, `[BLTE]: Invalid size: ${size.toString()} < 12`); const flag = buffer.readUInt8(8); const numBlocks = buffer.readIntBE(9, 3); assert__default(numBlocks > 0, `[BLTE]: Invalid number of blocks: ${numBlocks.toString()}`); assert__default(flag === 15, `[BLTE]: Invalid flag: ${flag.toString(16).padStart(2, "0")}`); const blockHeaderSize = numBlocks * 24; assert__default(headerSize === blockHeaderSize + 12, `[BLTE]: Invalid header size: header size ${headerSize.toString()} != block header size ${blockHeaderSize.toString()} + 12`); assert__default(size >= headerSize, `[BLTE]: Invalid size: ${size.toString()} < ${headerSize.toString()}`); for (let i = 0; i < numBlocks; i += 1) { const offset = 12 + i * 24; const compressedSize = buffer.readUInt32BE(offset); const decompressedSize = buffer.readUInt32BE(offset + 4); const hash = buffer.toString("hex", offset + 8, offset + 24); this.blocks.push({ compressedSize, decompressedSize, hash }); } this.processedOffset = headerSize; } processBlock(buffer, index, allowMissingKey) { const flag = buffer.readUInt8(0); switch (flag) { case 69: { let offset = 1; const keyNameLength = buffer.readUInt8(offset); offset += 1; const keyNameBE = buffer.toString("hex", offset, offset + keyNameLength); offset += keyNameLength; const ivLength = buffer.readUInt8(offset); offset += 1; const ivBuffer = buffer.subarray(offset, offset + ivLength); offset += ivLength; const encryptType = buffer.readUInt8(offset); offset += 1; assert__default(encryptType === ENC_TYPE_SALSA20, `[BLTE]: Invalid encrypt type: ${encryptType.toString(16).padStart(2, "0")} at block ${index.toString()}`); const keyName = [...keyNameBE.matchAll(/.{2}/g)].map((v) => v[0]).reverse().join("").toLowerCase(); const key = this.keys.get(keyName); if (!key) { if (allowMissingKey) { return keyName; } throw new Error(`[BLTE]: Missing key: ${keyName} at block ${index.toString()}`); } const iv = new Uint8Array(8); for (let i = 0; i < 8; i += 1) { if (i < ivLength) { iv[i] = ivBuffer.readUInt8(i) ^ index >>> 8 * i & 255; } else { iv[i] = 0; } } const handler = new Salsa20(key, iv); const decrypted = handler.process(buffer.subarray(offset)); if (allowMissingKey) { return this.processBlock(Buffer.from(decrypted.buffer), index, true); } return this.processBlock(Buffer.from(decrypted.buffer), index, false); } case 70: throw new Error(`[BLTE]: Frame (Recursive) block not supported at block ${index.toString()}`); case 78: return buffer.subarray(1); case 90: return zlib__default.inflateSync(buffer.subarray(1)); default: throw new Error(`[BLTE]: Invalid block flag: ${flag.toString(16).padStart(2, "0")} at block ${index.toString()}`); } } processBytes(allowMissingKey = false, size = Infinity) { const missingKeyBlocks = []; while (this.processedBlock < this.blocks.length && size > this.buffer.byteLength) { const blockIndex = this.processedBlock; const block = this.blocks[blockIndex]; const blockBuffer = this.blte.subarray( this.processedOffset, this.processedOffset + block.compressedSize ); if (block.hash !== EMPTY_HASH) { const blockHash = crypto__default.createHash("md5").update(blockBuffer).digest("hex"); assert__default(blockHash === block.hash, `[BLTE]: Invalid block hash: expected ${block.hash}, got ${blockHash}`); } if (allowMissingKey) { const buffer = this.processBlock(blockBuffer, blockIndex, allowMissingKey); if (typeof buffer === "string") { missingKeyBlocks.push({ offset: this.buffer.byteLength, size: block.decompressedSize, blockIndex, keyName: buffer }); this.buffer = Buffer.concat([ this.buffer, Buffer.alloc(block.decompressedSize) ]); } else { assert__default( buffer.byteLength === block.decompressedSize, `[BLTE]: Invalid decompressed size: expected ${block.decompressedSize.toString()}, got ${buffer.byteLength.toString()}` ); this.buffer = Buffer.concat([this.buffer, buffer]); } } else { const buffer = this.processBlock(blockBuffer, blockIndex, allowMissingKey); assert__default( buffer.byteLength === block.decompressedSize, `[BLTE]: Invalid decompressed size: expected ${block.decompressedSize.toString()}, got ${buffer.byteLength.toString()}` ); this.buffer = Buffer.concat([this.buffer, buffer]); } this.processedBlock += 1; this.processedOffset += block.compressedSize; } return allowMissingKey ? missingKeyBlocks : void 0; } } class Store { data; dataFile; promise; constructor(dataFile) { this.dataFile = dataFile; this.data = {}; this.promise = new Promise((resolve) => { fs__default.readFile(dataFile, "utf-8").then((file) => { this.data = JSON.parse(file); resolve(); }).catch(() => { resolve(); }); }); } async get(key) { await this.promise; return this.data[key]; } async set(key, value) { await this.promise; this.data[key] = value; await fs__default.writeFile(this.dataFile, JSON.stringify(this.data), "utf-8"); } } const USER_AGENT = "node-wow-casc-dbc"; const CACHE_ROOT = path__default.resolve("cache"); const CACHE_DIRS = { build: "builds", indexes: "indices", data: "data", dbd: "dbd" }; const CACHE_INTEGRITY_FILE = path__default.resolve(CACHE_ROOT, "integrity.json"); const cacheIntegrity = new Store(CACHE_INTEGRITY_FILE); const formatCDNKey = (key) => `${key.substring(0, 2)}/${key.substring(2, 4)}/${key}`; const requestData = async (url, { partialOffset, partialLength, showProgress } = {}) => new Promise((resolve, reject) => { const options = { headers: { // eslint-disable-next-line @typescript-eslint/naming-convention "User-Agent": USER_AGENT, // eslint-disable-next-line @typescript-eslint/naming-convention Range: partialOffset !== void 0 && partialLength !== void 0 ? `bytes=${partialOffset.toString()}-${(partialOffset + partialLength - 1).toString()}` : "bytes=0-" } }; https__default.get(url, options, (res) => { if (res.statusCode === 301 || res.statusCode === 302) { if (res.headers.location !== void 0) { requestData(res.headers.location, { partialOffset, partialLength, showProgress }).then(resolve).catch((err) => { throw err; }); } else { reject(new Error(`Failed to request ${url}, Status Code: ${res.statusCode.toString()}`)); } return; } if (res.statusCode === void 0 || res.statusCode < 200 || res.statusCode > 302) { reject(new Error(`Failed to request ${url}, Status Code: ${res.statusCode?.toString() ?? "undefined"}`)); return; } const lengthText = res.headers["content-length"]; const length = lengthText !== void 0 ? parseInt(lengthText, 10) : 0; const bar = showProgress === true && !Number.isNaN(length) && length >= 10485760 ? new cliProgress__default.SingleBar({ etaBuffer: 10240 }, cliProgress__default.Presets.shades_classic) : void 0; bar?.start(length, 0); const chunks = []; res.on("data", (chunk) => { bar?.increment(chunk.length); chunks.push(chunk); }); res.on("end", () => { bar?.stop(); resolve(Buffer.concat(chunks)); }); res.on("error", (err) => { bar?.stop(); reject(err); }); }).on("error", reject).end(); }); const downloadFile = (prefixes, type, key, { partialOffset, partialLength, showProgress, showAttemptFail } = {}) => { const urls = prefixes.map((prefix) => `${prefix}/${type}/${formatCDNKey(key)}`); return urls.reduce( (prev, url, index) => prev.catch((err) => { if (showAttemptFail === true && index > 0 && err instanceof Error) { console.warn(`${(/* @__PURE__ */ new Date()).toISOString()} [WARN]:`, err.message); } return requestData(url, { partialOffset, partialLength, showProgress }); }), Promise.reject(new Error("")) ); }; const getFileCache = async (file) => { const integrity = await cacheIntegrity.get(file); if (integrity !== void 0) { try { const buffer = await fs__default.readFile(path__default.resolve(CACHE_ROOT, file)); const hash = crypto__default.createHash("sha256").update(buffer).digest("hex"); if (hash === integrity) { return buffer; } } catch { } } return void 0; }; const getDataFile = async (prefixes, key, type, buildCKey, { name, partialOffset, partialLength, showProgress, showAttemptFail } = {}) => { const dir = type === "build" ? path__default.join(CACHE_DIRS[type], buildCKey) : CACHE_DIRS[type]; const file = name !== void 0 ? path__default.join(dir, name) : path__default.join(dir, key); const cacheBuffer = await getFileCache(file); if (cacheBuffer) { if (name === void 0 && partialOffset !== void 0 && partialLength !== void 0) { return cacheBuffer.subarray(partialOffset, partialOffset + partialLength); } return cacheBuffer; } const downloadBuffer = await downloadFile(prefixes, "data", key, { partialOffset, partialLength, showProgress, showAttemptFail }); if (partialOffset === void 0 && partialLength === void 0 || name !== void 0) { await fs__default.mkdir(path__default.resolve(CACHE_ROOT, dir), { recursive: true }); await fs__default.writeFile(path__default.resolve(CACHE_ROOT, file), downloadBuffer); const hash = crypto__default.createHash("sha256").update(downloadBuffer).digest("hex"); await cacheIntegrity.set(file, hash); } return downloadBuffer; }; const getConfigFile = async (prefixes, key, { showProgress, showAttemptFail } = {}) => { const downloadBuffer = await downloadFile(prefixes, "config", key, { showProgress, showAttemptFail }); return downloadBuffer.toString("utf-8"); }; const getProductVersions = async (region, product) => { const url = region !== "cn" ? `https://${region}.version.battle.net/v2/products/${product}/versions` : `https://cn.version.battlenet.com.cn/v2/products/${product}/versions`; const headers = new Headers(); headers.set("User-Agent", USER_AGENT); const res = await fetch(url, { headers }); return res.text(); }; const getProductCDNs = async (region, product) => { const url = region !== "cn" ? `https://${region}.version.battle.net/v2/products/${product}/cdns` : `https://cn.version.battlenet.com.cn/v2/products/${product}/cdns`; const headers = new Headers(); headers.set("User-Agent", USER_AGENT); const res = await fetch(url, { headers }); return res.text(); }; const hashlittle2 = (key, pc = 0, pb = 0) => { const { length } = key; let offset = 0; let a = 3735928559 + length + pc | 0; let b = 3735928559 + length + pc | 0; let c = 3735928559 + length + pc + pb | 0; while (length - offset > 12) { a += key.charCodeAt(offset + 0); a += key.charCodeAt(offset + 1) << 8; a += key.charCodeAt(offset + 2) << 16; a += key.charCodeAt(offset + 3) << 24; b += key.charCodeAt(offset + 4); b += key.charCodeAt(offset + 5) << 8; b += key.charCodeAt(offset + 6) << 16; b += key.charCodeAt(offset + 7) << 24; c += key.charCodeAt(offset + 8); c += key.charCodeAt(offset + 9) << 8; c += key.charCodeAt(offset + 10) << 16; c += key.charCodeAt(offset + 11) << 24; a -= c; a ^= c << 4 | c >>> 28; c = c + b | 0; b -= a; b ^= a << 6 | a >>> 26; a = a + c | 0; c -= b; c ^= b << 8 | b >>> 24; b = b + a | 0; a -= c; a ^= c << 16 | c >>> 16; c = c + b | 0; b -= a; b ^= a << 19 | a >>> 13; a = a + c | 0; c -= b; c ^= b << 4 | b >>> 28; b = b + a | 0; offset += 12; } if (length - offset > 0) { switch (length - offset) { case 12: c += key.charCodeAt(offset + 11) << 24; // falls through case 11: c += key.charCodeAt(offset + 10) << 16; // falls through case 10: c += key.charCodeAt(offset + 9) << 8; // falls through case 9: c += key.charCodeAt(offset + 8); // falls through case 8: b += key.charCodeAt(offset + 7) << 24; // falls through case 7: b += key.charCodeAt(offset + 6) << 16; // falls through case 6: b += key.charCodeAt(offset + 5) << 8; // falls through case 5: b += key.charCodeAt(offset + 4); // falls through case 4: a += key.charCodeAt(offset + 3) << 24; // falls through case 3: a += key.charCodeAt(offset + 2) << 16; // falls through case 2: a += key.charCodeAt(offset + 1) << 8; // falls through case 1: a += key.charCodeAt(offset + 0); } c ^= b; c -= b << 14 | b >>> 18; a ^= c; a -= c << 11 | c >>> 21; b ^= a; b -= a << 25 | a >>> 7; c ^= b; c -= b << 16 | b >>> 16; a ^= c; a -= c << 4 | c >>> 28; b ^= a; b -= a << 14 | a >>> 18; c ^= b; c -= b << 24 | b >>> 8; } return [c >>> 0, b >>> 0]; }; const getNameHash = (name) => { const normalized = name.replace(/\//g, "\\").toUpperCase(); const [pc, pb] = hashlittle2(normalized); return `${pc.toString(16).padStart(8, "0")}${pb.toString(16).padStart(8, "0")}`; }; const VERSION_SUB_OFFSET = -12; const CHECKSUM_SIZE_SUB_OFFSET = -5; const BLOCK_SIZE_OFFSET = 3; const OFFSET_BYTES_OFFSET = 4; const SIZE_BYTES_OFFSET = 5; const KEY_SIZE_OFFSET = 6; const NUM_ELEMENTS_OFFSET = 8; const CHECKSUM_OFFSET = 12; const CHECKSUM_TRIES = [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]; const tryArchiveIndexChecksumSize = (buffer, cKey) => { const res = CHECKSUM_TRIES.filter( (index) => buffer.readUInt8(buffer.byteLength - index + CHECKSUM_SIZE_SUB_OFFSET) === index && buffer.readUInt8(buffer.byteLength - index + VERSION_SUB_OFFSET) === 1 ); if (res.length === 1) { return res[0]; } throw new Error(`Invalid checksum size: ${res.join(", ")} in ${cKey}`); }; const parseArchiveIndex = (buffer, cKey) => { const checksumSize = tryArchiveIndexChecksumSize(buffer, cKey); const versionOffset = buffer.byteLength - checksumSize + VERSION_SUB_OFFSET; const footerOffset = versionOffset - checksumSize; const tocChecksum = buffer.toString("hex", footerOffset, versionOffset); const version = buffer.readUInt8(versionOffset); const blockSizeKB = buffer.readUInt8(versionOffset + BLOCK_SIZE_OFFSET); const offsetBytes = buffer.readUInt8(versionOffset + OFFSET_BYTES_OFFSET); const sizeBytes = buffer.readUInt8(versionOffset + SIZE_BYTES_OFFSET); const keySize = buffer.readUInt8(versionOffset + KEY_SIZE_OFFSET); const numElements = buffer.readUInt32LE(versionOffset + NUM_ELEMENTS_OFFSET); const footerChecksum = buffer.toString("hex", versionOffset + CHECKSUM_OFFSET); assert__default(version === 1, `Invalid archive index version: ${version.toString()} in ${cKey}`); const entrySize = keySize + offsetBytes + sizeBytes; const blockSize = blockSizeKB * 1024; const numBlocks = footerOffset / (blockSize + keySize + checksumSize); const tocSize = (keySize + checksumSize) * numBlocks; const toc = buffer.subarray(footerOffset - tocSize, footerOffset); const footer = buffer.subarray(footerOffset); const footerCheckBuffer = Buffer.concat([ buffer.subarray(versionOffset, buffer.byteLength - checksumSize), Buffer.alloc(checksumSize) ]); const hash = crypto__default.createHash("md5").update(footer).digest("hex"); assert__default(hash === cKey, `Invalid footer hash in ${cKey}: expected ${cKey}, got ${hash}`); const footerHash = crypto__default.createHash("md5").update(footerCheckBuffer).digest("hex").slice(0, checksumSize * 2); assert__default(footerHash === footerChecksum, `Invalid footer checksum in ${cKey}: expected ${footerChecksum}, got ${footerHash}`); const tocHash = crypto__default.createHash("md5").update(toc).digest("hex").slice(0, checksumSize * 2); assert__default(tocHash === tocChecksum, `Invalid toc checksum in ${cKey}: expected ${tocChecksum}, got ${tocHash}`); const result = /* @__PURE__ */ new Map(); for (let i = 0; i < numBlocks; i += 1) { const lastEkey = toc.toString("hex", i * keySize, (i + 1) * keySize); const blockChecksum = toc.toString("hex", numBlocks * keySize + i * checksumSize, numBlocks * keySize + (i + 1) * checksumSize); const blockOffset = i * blockSize; const blockHash = crypto__default.createHash("md5").update(buffer.subarray(i * blockSize, (i + 1) * blockSize)).digest("hex").slice(0, checksumSize * 2); assert__default(blockChecksum === blockHash, `Invalid block hash in ${cKey} at ${i.toString()}: expected ${blockChecksum}, got ${blockHash}`); let length = 0; while (length < blockSize) { const entryOffset = blockOffset + length * entrySize; const eKey = buffer.toString("hex", entryOffset, entryOffset + keySize); const size = buffer.readUIntBE(entryOffset + keySize, sizeBytes); const offset = buffer.readUIntBE(entryOffset + keySize + sizeBytes, offsetBytes); result.set(eKey, { key: cKey, size, offset }); length += 1; if (eKey === lastEkey) { break; } } } assert__default(result.size === numElements, `Invalid number of elements: ${result.size.toString()} != ${numElements.toString()} in ${cKey}`); return result; }; const normalizeKey = (key) => key.split("-").map((part, index) => index === 0 ? part : `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join(""); const parseConfig = (text) => { const entries = {}; text.split(/\r?\n/).filter((line) => line.trim().length !== 0 && !line.startsWith("#")).forEach((line) => { const match = /([^\s]+)\s?=\s?(.*)/.exec(line); assert__default(match !== null, "Invalid token encountered parsing CDN config"); const [key, value] = match.slice(1); entries[normalizeKey(key)] = value; }); return entries; }; const parseCDNConfig = (text) => parseConfig(text); const parseBuildConfig = (text) => parseConfig(text); const ENC_MAGIC = 17742; const MAGIC_OFFSET$1 = 0; const VERSION_OFFSET$1 = 2; const HASH_SIZE_CKEY_OFFSET = 3; const HASH_SIZE_EKEY_OFFSET = 4; const CKEY_PAGE_SIZE_OFFSET = 5; const EKEY_PAGE_SIZE_OFFSET = 7; const CKEY_PAGE_COUNT_OFFSET = 9; const EKEY_PAGE_COUNT_OFFSET = 13; const SPEC_BLOCK_SIZE_OFFSET = 18; const SPEC_BLOCK_OFFSET = 22; const parseEncodingFile = (inputBuffer, eKey, cKey) => { const reader = new BLTEReader(inputBuffer, eKey); reader.processBytes(); const { buffer } = reader; const encodingHash = crypto__default.createHash("md5").update(buffer).digest("hex"); assert__default(encodingHash === cKey, `Invalid encoding hash: expected ${cKey}, got ${encodingHash}`); const magic = buffer.readUInt16BE(MAGIC_OFFSET$1); assert__default(magic === ENC_MAGIC, `Invalid encoding magic: ${magic.toString(16).padStart(4, "0")}`); const version = buffer.readUInt8(VERSION_OFFSET$1); const hashSizeCKey = buffer.readUInt8(HASH_SIZE_CKEY_OFFSET); const hashSizeEKey = buffer.readUInt8(HASH_SIZE_EKEY_OFFSET); const cKeyPageSizeKB = buffer.readUInt16BE(CKEY_PAGE_SIZE_OFFSET); const eKeyPageSizeKB = buffer.readUInt16BE(EKEY_PAGE_SIZE_OFFSET); const cKeyPageCount = buffer.readUInt32BE(CKEY_PAGE_COUNT_OFFSET); const eKeyPageCount = buffer.readUInt32BE(EKEY_PAGE_COUNT_OFFSET); const specBlockSize = buffer.readUInt32BE(SPEC_BLOCK_SIZE_OFFSET); assert__default(version === 1, `Invalid encoding version: ${version.toString()}`); const eSpec = []; let eSpecStringStart = SPEC_BLOCK_OFFSET; for (let i = SPEC_BLOCK_OFFSET; i < SPEC_BLOCK_OFFSET + specBlockSize; i += 1) { if (buffer[i] === 0) { eSpec.push(buffer.toString("ascii", eSpecStringStart, i)); eSpecStringStart = i + 1; } } const cKey2FileSize = /* @__PURE__ */ new Map(); const cKey2EKey = /* @__PURE__ */ new Map(); const cKeyPageIndexOffset = SPEC_BLOCK_OFFSET + specBlockSize; const cKeyPageIndexEntrySize = hashSizeCKey + 16; const cKeyPageOffset = cKeyPageIndexOffset + cKeyPageIndexEntrySize * cKeyPageCount; const cKeyPageSize = cKeyPageSizeKB * 1024; for (let i = 0; i < cKeyPageCount; i += 1) { const indexOffset = cKeyPageIndexOffset + i * cKeyPageIndexEntrySize; const pageOffset = cKeyPageOffset + i * cKeyPageSize; const firstCKey = buffer.toString("hex", indexOffset, indexOffset + hashSizeCKey); const pageChecksum = buffer.toString("hex", indexOffset + hashSizeCKey, indexOffset + hashSizeCKey + 16); const pageBuffer = buffer.subarray(pageOffset, pageOffset + cKeyPageSize); const pageHash = crypto__default.createHash("md5").update(pageBuffer).digest("hex"); assert__default(pageHash === pageChecksum, `Invalid ckey page ${i.toString()} checksum: expected ${pageChecksum}, got ${pageHash}`); const pageFirstCKey = pageBuffer.toString("hex", 6, 6 + hashSizeCKey); assert__default(pageFirstCKey === firstCKey, `Invalid ckey page ${i.toString()} first ckey: expected ${firstCKey}, got ${pageFirstCKey}`); let pagePointer = 0; while (pagePointer < cKeyPageSize) { const keyCount = pageBuffer.readUInt8(pagePointer); pagePointer += 1; if (keyCount === 0) { break; } const fileSize = pageBuffer.readUIntBE(pagePointer, 5); pagePointer += 5; const fileCKey = pageBuffer.toString("hex", pagePointer, pagePointer + hashSizeCKey); pagePointer += hashSizeCKey; cKey2FileSize.set(fileCKey, fileSize); if (keyCount === 1) { const fileEKey = pageBuffer.toString("hex", pagePointer, pagePointer + hashSizeEKey); cKey2EKey.set(fileCKey, fileEKey); pagePointer += hashSizeEKey; } else { const fileEKeys = []; for (let j = 0; j < keyCount; j += 1) { const fileEKey = pageBuffer.toString("hex", pagePointer, pagePointer + hashSizeEKey); fileEKeys.push(fileEKey); pagePointer += hashSizeEKey; } cKey2EKey.set(fileCKey, fileEKeys); } } } const eKey2ESpecIndex = /* @__PURE__ */ new Map(); const eKey2FileSize = /* @__PURE__ */ new Map(); const eKeyPageIndexOffset = cKeyPageOffset + cKeyPageSize * cKeyPageCount; const eKeyPageIndexEntrySize = hashSizeEKey + 16; const eKeyPageOffset = eKeyPageIndexOffset + eKeyPageIndexEntrySize * eKeyPageCount; const eKeyPageSize = eKeyPageSizeKB * 1024; const eKeyPageEntrySize = hashSizeEKey + 4 + 5; for (let i = 0; i < eKeyPageCount; i += 1) { const indexOffset = eKeyPageIndexOffset + i * eKeyPageIndexEntrySize; const pageOffset = eKeyPageOffset + i * eKeyPageSize; const firstEKey = buffer.toString("hex", indexOffset, indexOffset + hashSizeEKey); const pageChecksum = buffer.toString("hex", indexOffset + hashSizeEKey, indexOffset + hashSizeEKey + 16); const pageBuffer = buffer.subarray(pageOffset, pageOffset + eKeyPageSize); const pageHash = crypto__default.createHash("md5").update(pageBuffer).digest("hex"); assert__default(pageHash === pageChecksum, `Invalid ekey page ${i.toString()} checksum: expected ${pageChecksum}, got ${pageHash}`); const pageFirstEKey = pageBuffer.toString("hex", 0, hashSizeEKey); assert__default(pageFirstEKey === firstEKey, `Invalid ekey page ${i.toString()} first ekey: expected ${firstEKey}, got ${pageFirstEKey}`); let pagePointer = 0; while (pagePointer + eKeyPageEntrySize <= eKeyPageSize) { const fileEKey = pageBuffer.toString("hex", pagePointer, pagePointer + hashSizeEKey); pagePointer += hashSizeEKey; const eSpecIndex = pageBuffer.readUInt32BE(pagePointer); pagePointer += 4; eKey2ESpecIndex.set(fileEKey, eSpecIndex); const fileSize = pageBuffer.readUIntBE(pagePointer, 5); pagePointer += 5; eKey2FileSize.set(fileEKey, fileSize); } } return { eSpec, cKey2FileSize, cKey2EKey, eKey2ESpecIndex, eKey2FileSize }; }; const INSTALL_MAGIC = 18766; const MAGIC_OFFSET = 0; const VERSION_OFFSET = 2; const HASH_SIZE_OFFSET = 3; const NUM_TAGS_OFFSET = 4; const NUM_ENTRIES_OFFSET = 6; const TAGS_OFFSET = 10; const parseInstallFile = (inputBuffer, eKey, cKey) => { const reader = new BLTEReader(inputBuffer, eKey); reader.processBytes(); const { buffer } = reader; const installHash = crypto__default.createHash("md5").update(buffer).digest("hex"); assert__default(installHash === cKey, `Invalid root hash: expected ${cKey}, got ${installHash}`); const magic = buffer.readUInt16BE(MAGIC_OFFSET); assert__default(magic === INSTALL_MAGIC, `Invalid install magic: ${magic.toString(16).padStart(4, "0")}`); const version = buffer.readUInt8(VERSION_OFFSET); const hashSize = buffer.readUInt8(HASH_SIZE_OFFSET); const numTags = buffer.readUInt16BE(NUM_TAGS_OFFSET); const numEntries = buffer.readUInt32BE(NUM_ENTRIES_OFFSET); assert__default(version === 1, `Invalid install version: ${version.toString()}`); let pointer = TAGS_OFFSET; const tags = []; for (let i = 0; i < numTags; i += 1) { const startOffset = pointer; while (buffer[pointer] !== 0) { pointer += 1; } const name = buffer.toString("utf-8", startOffset, pointer); pointer += 1; const type = buffer.readUInt16BE(pointer); pointer += 2; const files2 = []; const finalOffset = pointer + Math.ceil(numEntries / 8); while (pointer < finalOffset) { const byte = buffer.readUInt8(pointer); pointer += 1; for (let j = 7; j >= 0; j -= 1) { files2.push((byte & 1 << j) > 0); } } tags.push({ name, type, files: files2 }); } const files = []; for (let i = 0; i < numEntries; i += 1) { const startOffset = pointer; while (buffer[pointer] !== 0) { pointer += 1; } const name = buffer.toString("utf-8", startOffset, pointer); pointer += 1; const hash = buffer.toString("hex", pointer, pointer + hashSize); pointer += hashSize; const size = buffer.readUInt32BE(pointer); pointer += 4; const fileTags = tags.filter((tag) => tag.files[i]); files.push({ name, hash, size, tags: fileTags }); } return { tags, files }; }; const parseProductConfig = (text) => { const lines = text.split(/\r?\n/); const headers = lines[0].split("|").map((header) => header.split("!")[0].replace(" ", "")); const entries = lines.filter((line, index) => index > 0 && line.trim().length !== 0 && !line.startsWith("#")).map((line) => { const node = {}; const entryFields = line.split("|"); for (let i = 0, n = entryFields.length; i < n; i += 1) { node[headers[i]] = entryFields[i]; } return node; }); return entries; }; const parseProductVersions = (text) => parseProductConfig(text); const parseProductCDNs = (text) => parseProductConfig(text); const MFST_MAGIC = 1296454484; const ContentFlags = { Install: 4, LoadOnWindows: 8, LoadOnMacOS: 16, x86_32: 32, x86_64: 64, LowViolence: 128, DoNotLoad: 256, UpdatePlugin: 2048, ARM64: 32768, Encrypted: 134217728, NoNameHash: 268435456, UncommonResolution: 536870912, Bundle: 1073741824, NoCompression: 2147483648 }; const LocaleFlags = { enUS: 2, koKR: 4, frFR: 16, deDE: 32, zhCN: 64, esES: 128, zhTW: 256, enGB: 512, // enCN: 0x400, // enTW: 0x800, esMX: 4096, ruRU: 8192, ptBR: 16384, itIT: 32768, ptPT: 65536 }; const parseRootFile = (inputBuffer, eKey, cKey) => { const reader = new BLTEReader(inputBuffer, eKey); reader.processBytes(); const { buffer } = reader; const rootHash = crypto__default.createHash("md5").update(buffer).digest("hex"); assert__default(rootHash === cKey, `Invalid root hash: expected ${cKey}, got ${rootHash}`); const fileDataID2CKey = /* @__PURE__ */ new Map(); const nameHash2FileDataID = /* @__PURE__ */ new Map(); const magic = buffer.readUInt32LE(0); if (magic === MFST_MAGIC) { const firstEntry = buffer.readUInt32LE(4); const newFormat = firstEntry < 100; const headerSize = newFormat ? firstEntry : 12; const version = newFormat ? buffer.readUInt32LE(8) : 0; const totalFileCount = newFormat ? buffer.readUInt32LE(12) : firstEntry; const namedFileCount = newFormat ? buffer.readUInt32LE(16) : buffer.readUInt32LE(8); assert__default(version >= 0 && version <= 2, `Invalid root version: ${version.toString()}`); const allowNonNamedFiles = totalFileCount !== namedFileCount; let pointer = headerSize; while (pointer < buffer.byteLength) { const numRecords = buffer.readUInt32LE(pointer); let contentFlags; let localeFlags; if (version >= 2) { localeFlags = buffer.readUInt32LE(pointer + 4); const contentFlags1 = buffer.readUInt32LE(pointer + 8); const contentFlags2 = buffer.readUInt32LE(pointer + 12); const contentFlags3 = buffer.readUInt8(pointer + 16); contentFlags = contentFlags1 | contentFlags2 | contentFlags3 << 17; pointer += 17; } else { contentFlags = buffer.readUInt32LE(pointer + 4); localeFlags = buffer.readUInt32LE(pointer + 8); pointer += 12; } const fileDataIDs = []; let currFileDataID = -1; for (let i = 0; i < numRecords; i += 1) { currFileDataID += buffer.readUInt32LE(pointer) + 1; fileDataIDs.push(currFileDataID); pointer += 4; } for (let i = 0; i < numRecords; i += 1) { const fileDataID = fileDataIDs[i]; const fileCKey = buffer.toString("hex", pointer, pointer + 16); pointer += 16; if (fileDataID2CKey.has(fileDataID)) { fileDataID2CKey.get(fileDataID)?.push({ cKey: fileCKey, contentFlags, localeFlags }); } else { fileDataID2CKey.set(fileDataID, [ { cKey: fileCKey, contentFlags, localeFlags } ]); } } if (!(allowNonNamedFiles && contentFlags & ContentFlags.NoNameHash)) { for (let i = 0; i < numRecords; i += 1) { const fileDataID = fileDataIDs[i]; const nameHash = buffer.readBigUInt64LE(pointer).toString(16).padStart(16, "0"); pointer += 8; nameHash2FileDataID.set(nameHash, fileDataID); } } } } else { let pointer = 0; while (pointer < buffer.byteLength) { const numRecords = buffer.readUInt32LE(pointer); const contentFlags = buffer.readUInt32LE(pointer + 4); const localeFlags = buffer.readUInt32LE(pointer + 8); pointer += 12; const fileDataIDs = []; let currFileDataID = -1; for (let i = 0; i < numRecords; i += 1) { currFileDataID += buffer.readUInt32LE(pointer) + 1; fileDataIDs.push(currFileDataID); pointer += 4; } for (let i = 0; i < numRecords; i += 1) { const fileDataID = fileDataIDs[i]; const fileCKey = buffer.toString("hex", pointer, pointer + 16); const nameHash = buffer.toString("hex", pointer + 16, pointer + 24); pointer += 24; if (fileDataID2CKey.has(fileDataID)) { fileDataID2CKey.get(fileDataID)?.push({ cKey: fileCKey, contentFlags, localeFlags }); } else { fileDataID2CKey.set(fileDataID, [ { cKey: fileCKey, contentFlags, localeFlags } ]); } nameHash2FileDataID.set(nameHash, fileDataID); } } } return { fileDataID2CKey, nameHash2FileDataID }; }; const JEDEC = [ "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]; const formatFileSize = (input) => { if (Number.isNaN(input)) return ""; let size = input; const isNegative = size < 0; const result = []; if (isNegative) size = -size; let exponent = Math.floor(Math.log(size) / Math.log(1024)); if (exponent < 0) exponent = 0; if (exponent > 8) exponent = 8; if (size === 0) { result[0] = 0; result[1] = JEDEC[exponent]; } else { const val = size / 2 ** (exponent * 10); result[0] = Number(val.toFixed(exponent > 0 ? 2 : 0)); if (result[0] === 1024 && exponent < 8) { result[0] = 1; exponent += 1; } result[1] = JEDEC[exponent]; } if (isNegative) result[0] = -result[0]; return result.join(" "); }; const resolveCDNHost = async (hosts, path) => { const latencies = await Promise.allSettled( hosts.map(async (host) => { const start = Date.now(); await fetch(`https://${host}/`); const end = Date.now(); return { host, latency: end - start }; }) ); const resolved = latencies.filter((result) => result.status === "fulfilled").map((result) => result.value).sort((a, b) => a.latency - b.latency); return resolved.map((result) => `https://${result.host}/${path}`); }; const WDC5_MAGIC = 1464091445; const readBitpackedValue = (buffer, fieldOffsetBits, fieldSizeBits, signed = false) => { const offsetBytes = fieldOffsetBits >>> 3; const bitOffset = fieldOffsetBits & 7; const sizeBytes = Math.ceil((fieldSizeBits + bitOffset) / 8); let value = 0n; for (let i = sizeBytes - 1; i >= 0; i -= 1) { const byte = buffer.readUInt8(offsetBytes + i); value = value << 8n | BigInt(byte); } const result = signed ? BigInt.asIntN(fieldSizeBits, value >> BigInt(bitOffset)) : BigInt.asUintN(fieldSizeBits, value >> BigInt(bitOffset)); return fieldSizeBits <= 32 ? Number(result) : result; }; const isDataRangeAllZero = (buffer, offset, length) => { const end = offset + length; for (let pointer = offset; pointer < end; pointer += 1) { if (buffer[pointer] !== 0) { return false; } } return true; }; class WDCReader { tableHash; layoutHash; locale; isNormal; hasRelationshipData; fields; fieldsInfo; rows = /* @__PURE__ */ new Map(); relationships = /* @__PURE__ */ new Map(); copyTable = /* @__PURE__ */ new Map(); hotfixes = /* @__PURE__ */ new Map(); constructor(buffer, blocksOrOption, adbInput) { const options = blocksOrOption === void 0 || Array.isArray(blocksOrOption) ? { blocks: blocksOrOption, adb: adbInput } : blocksOrOption; const blocks = options.blocks ?? []; const adb = options.adb ?? adbInput; const detectIsZeroedByData = options.detectIsZeroedByData ?? false; const mergedBlocks = []; if (!detectIsZeroedByData) { blocks.sort((a, b) => a.offset - b.offset).forEach(({ offset, size }) => { const lastBlock = mergedBlocks[mergedBlocks.length - 1]; if (mergedBlocks.length > 0 && lastBlock.offset + lastBlock.size >= offset) { lastBlock.size = Math.max( lastBlock.offset + lastBlock.size, offset + size ) - lastBlock.offset; } else { mergedBlocks.push({ offset, size }); } }); } const magic = buffer.readUInt32BE(0); const version = buffer.readUInt32LE(4); const fieldCount = buffer.readUInt32LE(140); const recordSize = buffer.readUInt32LE(144); const tableHash = buffer.readUInt32LE(152); const layoutHash = buffer.readUInt32LE(156); const locale = buffer.readUInt32LE(168); const flags = buffer.readUInt16LE(172); const idIndex = buffer.readUInt16LE(174); const fieldStorageInfoSize = buffer.readUInt32LE(188); const commonDataSize = buffer.readUInt32LE(192); const palletDataSize = buffer.readUInt32LE(196); const sectionCount = buffer.readUInt32LE(200); assert__default(magic === WDC5_MAGIC, `Invalid WDC5 magic: ${magic.toString(16).padStart(8, "0")}`); assert__default(version === 5, `Invalid WDC5 version: ${version.toString()}`); this.tableHash = tableHash; this.layoutHash = layoutHash; this.locale = locale; const isNormal = !(flags & 1); const hasRelationshipData = !!(flags & 2); this.isNormal = isNormal; this.hasRelationshipData = hasRelationshipData; const sectionHeaders = []; const sectionHeadersOffset = 204; for (let i = 0; i < sectionCount; i += 1) { const sectionHeaderOffset = sectionHeadersOffset + i * 40; sectionHeaders.push({ tactKeyHash: buffer.readBigUInt64LE(sectionHeaderOffset), fileOffset: buffer.readUInt32LE(sectionHeaderOffset + 8), recordCount: buffer.readUInt32LE(sectionHeaderOffset + 12), stringTableSize: buffer.readUInt32LE(sectionHeaderOffset + 16), offsetRecordsEnd: buffer.readUInt32LE(sectionHeaderOffset + 20), idListSize: buffer.readUInt32LE(sectionHeaderOffset + 24), relationshipDataSize: buffer.readUInt32LE(sectionHeaderOffset + 28), offsetMapIDCount: buffer.readUInt32LE(sectionHeaderOffset + 32), copyTableCount: buffer.readUInt32LE(sectionHeaderOffset + 36) }); } const fields = []; const fieldsOffset = 204 + sectionCount * 40; for (let i = 0; i < fieldCount; i += 1) { const fieldOffset = fieldsOffset + i * 4; fields.push({ size: buffer.readInt16LE(fieldOffset), position: buffer.readUInt16LE(fieldOffset + 2) }); } this.fields = fields; const fieldsInfo = []; const fieldsInfoOffset = fieldsOffset + fieldCount * 4; for (let i = 0; i < fieldStorageInfoSize / 24; i += 1) { const fieldInfoOffset = fieldsInfoOffset + i * 24; const fieldOffsetBits = buffer.readUInt16LE(fieldInfoOffset); const fieldSizeBits = buffer.readUInt16LE(fieldInfoOffset + 2); const additionalDataSize = buffer.readUInt32LE(fieldInfoOffset + 4); const storageType = buffer.readUInt32LE(fieldInfoOffset + 8); const arg1 = buffer.readUInt32LE(fieldInfoOffset + 12); const arg2 = buffer.readUInt32LE(fieldInfoOffset + 16); const arg3 = buffer.readUInt32LE(fieldInfoOffset + 20); switch (storageType) { case 0: fieldsInfo.push({ fieldOffsetBits, fieldSizeBits, additionalDataSize, storageType: "none" }); break; case 1: fieldsInfo.push({ fieldOffsetBits, fieldSizeBits, additionalDataSize, storageType: "bitpacked", bitpackingOffsetBits: arg1, bitpackingSizeBits: arg2, flags: arg3 }); break; case 2: fieldsInfo.push({ fieldOffsetBits, fieldSizeBits, additionalDataSize, storageType: "commonData", defaultValue: arg1 }); break; case 3: fieldsInfo.push({ fieldOffsetBits, fieldSizeBits, additionalDataSize, storageType: "bitpackedIndexed", bitpackingOffsetBits: arg1, bitpackingSizeBits: arg2 }); break; case 4: fieldsInfo.push({ fieldOffsetBits, fieldSizeBits, additionalDataSize, storageType: "bitpackedIndexedArray", bitpackingOffsetBits: arg1, bitpackingSizeBits: arg2, arrayCount: arg3 }); break; case 5: fieldsInfo.push({ fieldOffsetBits, fieldSizeBits, additionalDataSize, storageType: "bitpackedSigned", bitpackingOffsetBits: arg1, bitpackingSizeBits: arg2, flags: arg3 }); break; default: throw new Error(`Unknown storage type: ${storageType.toString(16).padStart(8, "0")}`); } } this.fieldsInfo = fieldsInfo; const palletData = /* @__PURE__ */ new Map(); const palletDataOffset = fieldsInfoOffset + fieldStorageInfoSize; let palletDataPointer = palletDataOffset; for (let i = 0; i < fieldsInfo.length; i += 1) { const fieldInfo = fieldsInfo[i]; if (fieldInfo.storageType === "bitpackedIndexed" || fieldInfo.storageType === "bitpackedIndexedArray") { const data = []; for (let j = 0; j < fieldInfo.additionalDataSize / 4; j += 1) { data.push(buffer.readUInt32LE(palletDataPointer)); palletDataPointer += 4; } palletData.set(i, data); } } assert__default( palletDataPointer === palletDataOffset + palletDataSize, `Invalid pallet data size: ${(palletDataPointer - palletDataOffset).toString()} != ${palletDataSize.toString()}` ); const commonData = /* @__PURE__ */ new Map(); const commonDataOffset = palletDataPointer; let commonDataPointer = commonDataOffset; for (let i = 0; i < fieldsInfo.length; i += 1) { const fieldInfo = fieldsInfo[i]; if (fieldInfo.storageType === "commonData") { const map = /* @__PURE__ */ new Map(); for (let j = 0; j < fieldInfo.additionalDataSize / 8; j += 1) { map.set( buffer.readUInt32LE(commonDataPointer), buffer.readUInt32LE(commonDataPointer + 4) ); commonDataPointer += 8; } commonData.set(i, map); } } assert__default( commonDataPointer === commonDataOffset + commonDataSize, `Invalid common data size: ${(commonDataPointer - commonDataOffset).toString()} != ${commonDataSize.toString()}` ); const encryptedIDs = /* @__PURE__ */ new Map(); const encryptedRecordsOffset = commonDataPointer; let encryptedRecordsPointer = encryptedRecordsOffset; for (let i = 0; i < sectionHeaders.length; i += 1) { const sectionHeader = sectionHeaders[i]; if (sectionHeader.tactKeyHash !== 0n) { const count = buffer.readUInt32LE(encryptedRecordsPointer); encryptedRecordsPointer += 4; const data = []; for (let j = 0; j < count; j += 1) { data.push(buffer.readUInt32LE(encryptedRecordsPointer)); encryptedRecordsPointer += 4; } encryptedIDs.set(i, data); } } const stringTable = /* @__PURE__ */ new Map(); let stringTableDelta = 0; const sectionsOffset = encryptedRecordsPointer; let sectionPointer = sectionsOffset; const sections = sectionHeaders.map((sectionHeader) => { assert__default( sectionPointer === sectionHeader.fileOffset, `Invalid section offset: ${sectionPointer.toString()} != ${sectionHeader.fileOffset.toStrin