@rhyster/wow-casc-dbc
Version:
Fetch World of Warcraft data files from CASC and parse DBC/DB2 files.
160 lines (129 loc) • 6.76 kB
text/typescript
import assert from 'node:assert';
import crypto from 'node:crypto';
import BLTEReader from '../blte.ts';
const ENC_MAGIC = 0x454e;
const MAGIC_OFFSET = 0;
const VERSION_OFFSET = 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 UNK11_OFFSET = 17;
const SPEC_BLOCK_SIZE_OFFSET = 18;
const SPEC_BLOCK_OFFSET = 22;
interface EncodingData {
eSpec: string[],
cKey2FileSize: Map<string, number>,
cKey2EKey: Map<string, string | string[]>,
eKey2ESpecIndex: Map<string, number>,
eKey2FileSize: Map<string, number>,
}
const parseEncodingFile = (inputBuffer: Buffer, eKey: string, cKey: string): EncodingData => {
const reader = new BLTEReader(inputBuffer, eKey);
reader.processBytes();
const { buffer } = reader;
const encodingHash = crypto.createHash('md5').update(buffer).digest('hex');
assert(encodingHash === cKey, `Invalid encoding hash: expected ${cKey}, got ${encodingHash}`);
const magic = buffer.readUInt16BE(MAGIC_OFFSET);
assert(magic === ENC_MAGIC, `Invalid encoding magic: ${magic.toString(16).padStart(4, '0')}`);
const version = buffer.readUInt8(VERSION_OFFSET);
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(version === 1, `Invalid encoding version: ${version.toString()}`);
const eSpec: string[] = [];
let eSpecStringStart = SPEC_BLOCK_OFFSET;
for (
let i = SPEC_BLOCK_OFFSET;
i < SPEC_BLOCK_OFFSET + specBlockSize;
i += 1
) {
if (buffer[i] === 0x00) {
eSpec.push(buffer.toString('ascii', eSpecStringStart, i));
eSpecStringStart = i + 1;
}
}
const cKey2FileSize = new Map<string, number>();
const cKey2EKey = new Map<string, string | string[]>();
const cKeyPageIndexOffset = SPEC_BLOCK_OFFSET + specBlockSize;
const cKeyPageIndexEntrySize = hashSizeCKey + 0x10;
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 + 0x10);
const pageBuffer = buffer.subarray(pageOffset, pageOffset + cKeyPageSize);
const pageHash = crypto.createHash('md5').update(pageBuffer).digest('hex');
assert(pageHash === pageChecksum, `Invalid ckey page ${i.toString()} checksum: expected ${pageChecksum}, got ${pageHash}`);
const pageFirstCKey = pageBuffer.toString('hex', 6, 6 + hashSizeCKey);
assert(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 === 0x00) {
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: string[] = [];
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 = new Map<string, number>();
const eKey2FileSize = new Map<string, number>();
const eKeyPageIndexOffset = cKeyPageOffset + cKeyPageSize * cKeyPageCount;
const eKeyPageIndexEntrySize = hashSizeEKey + 0x10;
const eKeyPageOffset = eKeyPageIndexOffset + eKeyPageIndexEntrySize * eKeyPageCount;
const eKeyPageSize = eKeyPageSizeKB * 1024;
const eKeyPageEntrySize = hashSizeEKey + 0x04 + 0x05;
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 + 0x10);
const pageBuffer = buffer.subarray(pageOffset, pageOffset + eKeyPageSize);
const pageHash = crypto.createHash('md5').update(pageBuffer).digest('hex');
assert(pageHash === pageChecksum, `Invalid ekey page ${i.toString()} checksum: expected ${pageChecksum}, got ${pageHash}`);
const pageFirstEKey = pageBuffer.toString('hex', 0, hashSizeEKey);
assert(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,
};
};
export default parseEncodingFile;
export type { EncodingData };