mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
cab78de506
* feat(domain): parse and validate GameCube/Wii DOL headers Add a pure parser for the 0x100-byte DOL header (7 text and 11 data slots, BSS, entry point). Used slots must lie after the header and inside the file and the 0x80000000..0x81800000 load window; text slots are 4-byte aligned; sections may not overlap in memory or in the file; the entry point must be inside a text section. BSS may overlap data sections, as .sdata/.sdata2 do in shipped DOLs. Refs #1741 * feat(binary): admit caller-selected GameCube/Wii DOL targets * feat(ghidra): import GameCube/Wii DOL targets through a section-map pre-script * test(ghidra): cover DOL launcher arguments, attestation and raw profile digests * test(ghidra): add the real verify:ghidra:dol lane and DOL docs * fix(ghidra): read only the DOL header and section ranges instead of the whole file * test(ghidra): derive DOL lane addresses from readelf and admit Gekko from the install catalog * fix(ghidra): attest DOL block permissions, space, overlay and load state * fix(ghidra): keep load-image permissions optional for older inspections * fix(contracts): accept DOL targets in binary_session output * fix(ghidra): adapt the DOL lane to the current upstream address schema Validate DOL load-image coordinates with the upstream observed-address schema, drop a duplicate import left by the rebase, and re-pin the raw profile digests to the unmodified upstream resolver. * fix(ghidra): preserve bridge inventory symbol provenance * fix(ghidra): align DOL verification with shared workflow contracts * feat(domain): parse and validate GameCube/Wii DOL headers Add a pure parser for the 0x100-byte DOL header (7 text and 11 data slots, BSS, entry point). Used slots must lie after the header and inside the file and the 0x80000000..0x81800000 load window; text slots are 4-byte aligned; sections may not overlap in memory or in the file; the entry point must be inside a text section. BSS may overlap data sections, as .sdata/.sdata2 do in shipped DOLs. Refs #1741 * feat(binary): admit caller-selected GameCube/Wii DOL targets * feat(ghidra): import GameCube/Wii DOL targets through a section-map pre-script * test(ghidra): cover DOL launcher arguments, attestation and raw profile digests * test(ghidra): add the real verify:ghidra:dol lane and DOL docs * fix(ghidra): read only the DOL header and section ranges instead of the whole file * test(ghidra): derive DOL lane addresses from readelf and admit Gekko from the install catalog * fix(ghidra): attest DOL block permissions, space, overlay and load state * fix(ghidra): keep load-image permissions optional for older inspections * fix(contracts): accept DOL targets in binary_session output * fix(ghidra): adapt the DOL lane to the current upstream address schema Validate DOL load-image coordinates with the upstream observed-address schema, drop a duplicate import left by the rebase, and re-pin the raw profile digests to the unmodified upstream resolver. * docs(ghidra): qualify the DOL memory window * refactor(ghidra): own DOL BSS ranges in the parsed header * docs(ghidra): record real DOL workflows and task costs * docs(ghidra): record final DOL integration verification * fix(mcp): share the native procedure search row contract * docs(ghidra): record public procedure search verification --------- Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
113 lines
4.2 KiB
TypeScript
113 lines
4.2 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { truncate, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js";
|
|
import { buildGamecubeDol } from "../../../src/domain/gamecubeDol.fixture.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
describe("gamecube-dol admission", () => {
|
|
it("admits an explicitly selected DOL as big-endian 32-bit powerpc", async () => {
|
|
const directory = await createTestTempDirectory("rea-dol-admit-");
|
|
const bytes = buildGamecubeDol();
|
|
await writeFile(join(directory, "game.dol"), bytes);
|
|
const target = await parseBinaryTarget("game.dol", {
|
|
cwd: directory,
|
|
formatHint: "gamecube-dol",
|
|
});
|
|
expect(target).toMatchObject({
|
|
ok: true,
|
|
value: {
|
|
kind: "executable",
|
|
format: "gamecube-dol",
|
|
architecture: "powerpc",
|
|
availableArchitectures: ["powerpc"],
|
|
byteOrder: "big",
|
|
bits: 32,
|
|
entryPoint: "0x80003100",
|
|
entryPointSource: "dol-header",
|
|
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
dol: { entryPoint: 0x8000_3100, bssOverlaps: [] },
|
|
},
|
|
});
|
|
});
|
|
|
|
it("refuses a caller architecture that conflicts with the DOL header", async () => {
|
|
const directory = await createTestTempDirectory("rea-dol-arch-");
|
|
await writeFile(join(directory, "game.dol"), buildGamecubeDol());
|
|
const target = await parseBinaryTarget("game.dol", {
|
|
cwd: directory,
|
|
formatHint: "gamecube-dol",
|
|
architecture: "x86",
|
|
});
|
|
expect(target.ok).toBe(false);
|
|
if (!target.ok)
|
|
expect(target.error.message).toContain(
|
|
"takes its architecture from the DOL header",
|
|
);
|
|
});
|
|
|
|
it("refuses a caller entry that conflicts with the DOL header entry", async () => {
|
|
const directory = await createTestTempDirectory("rea-dol-entry-");
|
|
await writeFile(join(directory, "game.dol"), buildGamecubeDol());
|
|
const target = await parseBinaryTarget("game.dol", {
|
|
cwd: directory,
|
|
formatHint: "gamecube-dol",
|
|
entryPoint: "0x80003104",
|
|
});
|
|
expect(target.ok).toBe(false);
|
|
if (!target.ok)
|
|
expect(target).toMatchObject({
|
|
error: { constraint: "load_selection_rejected" },
|
|
});
|
|
});
|
|
|
|
it("refuses a selected file whose header fails parsing", async () => {
|
|
const directory = await createTestTempDirectory("rea-dol-bad-");
|
|
await writeFile(join(directory, "bad.dol"), Buffer.alloc(0x100));
|
|
const target = await parseBinaryTarget("bad.dol", {
|
|
cwd: directory,
|
|
formatHint: "gamecube-dol",
|
|
});
|
|
expect(target.ok).toBe(false);
|
|
if (!target.ok)
|
|
expect(target).toMatchObject({
|
|
error: {
|
|
constraint: "file_format_rejected",
|
|
message: expect.stringContaining("DOL has no used text section"),
|
|
},
|
|
});
|
|
});
|
|
|
|
it("refuses a large selected file with an invalid header after reading only the header", async () => {
|
|
// Sparse: the 2 GiB length is a metadata extension, so no real disk is used.
|
|
// A whole-file read would allocate the full length before the header check.
|
|
const directory = await createTestTempDirectory("rea-dol-sparse-");
|
|
const path = join(directory, "huge.dol");
|
|
await writeFile(path, Buffer.alloc(0x100));
|
|
await truncate(path, 2 * 1024 ** 3);
|
|
const target = await parseBinaryTarget("huge.dol", {
|
|
cwd: directory,
|
|
formatHint: "gamecube-dol",
|
|
});
|
|
expect(target.ok).toBe(false);
|
|
if (!target.ok)
|
|
expect(target).toMatchObject({
|
|
error: {
|
|
constraint: "file_format_rejected",
|
|
message: expect.stringContaining(
|
|
"gamecube-dol header rejected: DOL has no used text section",
|
|
),
|
|
},
|
|
});
|
|
});
|
|
|
|
it("never auto-detects a DOL without the caller's format selection", async () => {
|
|
const directory = await createTestTempDirectory("rea-dol-detect-");
|
|
await writeFile(join(directory, "game.dol"), buildGamecubeDol());
|
|
const target = await parseBinaryTarget("game.dol", { cwd: directory });
|
|
if (target.ok) expect(target.value.format).not.toBe("gamecube-dol");
|
|
});
|
|
});
|