import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import { execFileSync } from "node:child_process"; const root = ".reference/pokecrystal"; const expected = "f2f52230b536214ef7c9924f483392993e226cfb"; const romPath = process.env.CRYSTAL_ROM_PATH ?? "Pokemon - Crystal Version (UE) (V1.1) [C][!].gbc"; const supplied = readFileSync(romPath), rebuilt = readFileSync(`${root}/pokecrystal11.gbc`); if ( createHash("sha1").update(supplied).digest("hex") !== expected || !supplied.equals(rebuilt) ) throw new Error( "The supplied ROM and reference rebuild must exactly match revision 1.1.", ); const revision = execFileSync("git", ["-C", root, "rev-parse", "HEAD"], { encoding: "utf8", }).trim(); if (revision !== "7a7881d0d62e0ddbd82dcf10e7116807487ac651") throw new Error("Unexpected PRET revision."); const symbols = new Map( readFileSync(`${root}/pokecrystal11.sym`, "utf8") .split("\n") .flatMap((line) => { const match = /^([0-9a-f]{2}):([0-9a-f]{4}) (\S+)$/.exec(line); if (!match) return []; const bank = parseInt(match[1], 16), address = parseInt(match[2], 16); return [ [ match[3], address < 0x4000 ? address : bank * 0x4000 + address - 0x4000, ], ]; }), ); const names = { mapGroups: "MapGroupPointers", tilesets: "Tilesets", collisions: "CollisionPermissionTable", environmentColors: "EnvironmentColorsPointers", backgroundPalettes: "TilesetBGPalette", objectPalettes: "MapObjectPals", roofPalettes: "RoofPals", roofTypes: "MapGroupRoofs", roofs: "Roofs", housePalettes: "HousePalette", sprites: "OverworldSprites", facings: "Facings", standardScripts: "StdScripts", }; const profile = readFileSync("src/game/rom/profile.ts", "utf8"); for (const [key, symbol] of Object.entries(names)) { const match = new RegExp(`${key}:\\s*(0x[0-9a-f]+)`, "i").exec(profile); if (!match || Number(match[1]) !== symbols.get(symbol)) throw new Error(`Profile address mismatch: ${key} / ${symbol}`); } console.log( "User ROM, pinned PRET rebuild and all native profile addresses match.", );