diff --git a/src/installPatch.ts b/src/installPatch.ts index e868186..6937d7c 100644 --- a/src/installPatch.ts +++ b/src/installPatch.ts @@ -1,6 +1,10 @@ import getSolidpkg from './getSolidpkg'; +import readSolidpkg from './readSolidpkg'; (async () => { const buffer = await getSolidpkg('assets_swtor_de_de', -1, 0); console.log(buffer.length, buffer); + + const output = readSolidpkg(buffer); + console.log(output); })(); diff --git a/src/readSolidpkg.ts b/src/readSolidpkg.ts new file mode 100644 index 0000000..d9ee50a --- /dev/null +++ b/src/readSolidpkg.ts @@ -0,0 +1,83 @@ +const MAGIC_END_OF_CENTRAL_DIR = 0x06054b50; +const MAGIC_CENTRAL_DIR = 0x02014b50; +const COMPRESSION_DEFLATE = 8; + +export default function readSolidpkg(buffer: Buffer) { + const dv = new DataView(buffer.buffer); + + //--------------- READ END OF CENTRAL DIR --------------- + + //Go to end of file + let pos = buffer.length - 22; //end of central dir is at least 22 bytes long + + //Find end of central dir + while (pos >= 0 && dv.getUint32(pos, true) !== MAGIC_END_OF_CENTRAL_DIR) { + pos -= 1; + } + if (pos < 0) { + throw new Error('Could not find end of central dir.'); + } + + //skip 6 bytes + pos += 6; + + /** Total number of entries in the central directory */ + const numEntries = dv.getUint16(pos, true); pos += 2; + /** Size of the central directory */ + const centralDirSize = dv.getUint32(pos, true); pos += 4; + /** Offset of start of central directory with respect to the starting disk number */ + const centralDirOffset = dv.getUint32(pos, true); pos += 4; + + if (numEntries !== 1) { + throw new Error(`Expected numEntries == 1 in end of central dir but it was "${numEntries}"`); + } + if (centralDirSize < 46 * numEntries) { + throw new Error('centralDirSize was smaller than expected in end of central dir.'); + } + if (pos - centralDirSize < 0) { + throw new Error(`Central dir points before file start (0x${(pos - centralDirSize).toString(16)})`); + } + + //--------------- READ CENTRAL DIR --------------- + + //Go to start of central dir + pos -= -20 - centralDirSize; + + { + const magicNum = dv.getUint32(pos, true); pos += 4; + if (magicNum !== MAGIC_CENTRAL_DIR) { + throw new Error(`Expected central dir signature but found "0x${magicNum.toString(16)}"`); + } + } + + pos += 4; //skip version + + /** The bitflag stores whether the file is encrypted or not. Most files are encrypted but there are + * some exceptions: assets_swtor_test_main_248to249.solidpkg, assets_swtor_test_en_us_270to271.solidpkg, and + * retailclient_publictest_246to247.solidpkg are not encrypted. + */ + const bitFlag = dv.getUint16(pos, true); pos += 2; + + /** Type of compression, 8 = DEFLATE. Files are always compressed. */ + const compression = dv.getUint16(pos, true); pos += 2; + if (compression !== COMPRESSION_DEFLATE) { + throw new Error(`File is not using DEFLATE compression but "${compression}"`); + } + + const lastMod1 = dv.getUint16(pos, true); pos += 2; + const lastMod2 = dv.getUint16(pos, true); pos += 2; + const fileCrc = dv.getUint32(pos, true); pos += 4; + const comprSize = dv.getUint32(pos, true); pos += 4; + const uncomprSize = dv.getUint32(pos, true); pos += 4; + const fileNameLength = dv.getUint16(pos, true); pos += 2; + const extraFieldLength = dv.getUint16(pos, true); pos += 2; + const fileCommentLength = dv.getUint16(pos, true); pos += 2; + pos += 8; //skip disk num start and file attributes + const relOffset = dv.getUint32(pos, true); pos += 4; + pos += fileNameLength; //skip file name + //TODO: read password from extra field + + //... + + return { lastMod1, lastMod2, fileCrc, comprSize, uncomprSize, fileNameLength, extraFieldLength, fileCommentLength, relOffset }; +}