Files
2026-10-16 23:33:13 -07:00

64 lines
3.2 KiB
JavaScript

// Diagnostic copy only. Never overwrite the metadata consumed by the game.
const fs = require('node:fs');
const crypto = require('node:crypto');
const [source, destination] = process.argv.slice(2);
if (!source || !destination || require('node:path').resolve(source) === require('node:path').resolve(destination))
throw new Error('Specify separate source and diagnostic output files.');
const input = fs.readFileSync(source);
const header = Buffer.from(input.subarray(0, 256));
header.fill(0, 0xc0, 0xc4);
header.fill(0, 0xc8, 0xcc);
if (crypto.createHash('sha256').update(header).digest('hex') !== '2af65a04fe43613b66fafbe269264a6ec2b4c834304db272f0df68e7503b59e2')
throw new Error('Not the verified 20230825 header.');
// Standard v27 section order. Each pair is the offset/size FIELD POSITION
// in the shuffled header, not a hardcoded section address.
const fields = [
[0x2c,0xbc], [0xc8,0xc0], [0xe8,0xc4], [0x20,0xa8],
[0x24,0x68], [0x44,0xb0], [0x64,0xec], [0xf0,0x04],
[0x38,0x3c], [0x28,0x80], [0xa0,0x50], [0xf8,0xd4],
[0xe0,0x5c], [0x08,0x00], [0x18,0x58], [0x4c,0xb4],
[0x30,0x9c], [0x34,0x98], [0x6c,0x8c], [0x14,0x48],
[0xd8,0xcc], [0x0c,0xb8], [0x88,0x70], [0x7c,0xd0],
[0xa4,0x90], [0x78,0x94], [0xe4,0xf4], [0x54,0x40],
[0xfc,0x10], [0x74,0x84], [0xac,0xdc]
];
const output = Buffer.from(input);
output.writeUInt32LE(0xfab11baf, 0);
output.writeUInt32LE(27, 4);
for (let i=0; i<fields.length; i++) {
const [a,b] = fields[i];
const offset=input.readUInt32LE(a), size=input.readUInt32LE(b);
if (offset < 256 || offset + size > input.length) throw new Error(`Section ${i} out of bounds`);
output.writeUInt32LE(offset, 8+i*8);
output.writeUInt32LE(size, 12+i*8);
}
if (!input.subarray(256).equals(output.subarray(256))) throw new Error('Payload changed');
// This release also places invalid generic-container indices in each image's
// synthetic <Module> type. Those types cannot have generic parameters. Repair
// only verified module rows in the tooling copy; game metadata is untouched.
const types = output.readUInt32LE(8 + 19*8);
const typeCount = output.readUInt32LE(12 + 19*8) / 88;
const images = output.readUInt32LE(8 + 20*8);
const imageCount = output.readUInt32LE(12 + 20*8) / 40;
const strings = output.readUInt32LE(24);
if (imageCount !== 305 || typeCount !== 42537 || output.subarray(strings+42, strings+51).toString() !== '<Module>\0')
throw new Error('Unexpected August type/image structure');
const modules = new Set();
for (let i=0; i<imageCount; i++) {
const index = output.readInt32LE(images + i*40 + 8);
const p = types + index*88;
if (index < 0 || index >= typeCount || modules.has(index) ||
output.readUInt32LE(p+84) !== 0x02000001 || output.readInt32LE(p) !== 0 ||
output.readInt32LE(p+24) < 6673)
throw new Error(`Unexpected module row for image ${i}`);
modules.add(index);
output.writeInt32LE(42, p);
output.writeInt32LE(-1, p+24);
}
for (let i=0; i<typeCount; i++) {
const generic = output.readInt32LE(types+i*88+24);
if (generic < -1 || generic >= 6673) throw new Error(`Invalid generic container on type ${i}`);
}
fs.writeFileSync(destination, output);
console.log(`Wrote tooling copy: ${destination}; normalized header and ${modules.size} synthetic module rows.`);