Files
rea/tests/boundary/filesystem/gamecubeDolTarget.test.ts
T
Ariel Fikru Avicenna cab78de506 feat(ghidra): analyze GameCube/Wii DOL executables through Ghidra (#6240)
* 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>
2026-10-24 17:56:31 +08:00

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");
});
});