mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 16:57:19 +00:00
* 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>
98 lines
4.0 KiB
TypeScript
98 lines
4.0 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { join, resolve } from "node:path";
|
|
import { promisify } from "node:util";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
import { expect, it } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const packerUrl = pathToFileURL(
|
|
resolve("scripts/verify-package-pack.mjs"),
|
|
).href;
|
|
const catalogUrl = pathToFileURL(resolve("dist/catalogIdentity.js")).href;
|
|
const bridge = "package/bridge/hopper_bridge.py";
|
|
const members = [
|
|
bridge,
|
|
"package/bridge/cutter/rea_bridge.py",
|
|
"package/bridge/ghidra/ReaGhidraBridge.java",
|
|
"package/bridge/ghidra/ReaGhidraBundleActivator.java",
|
|
"package/bridge/ghidra/ReaGhidraExtensions.java",
|
|
"package/bridge/ghidra/ReaGhidraPrepareCom.java",
|
|
"package/bridge/ghidra/ReaGhidraPrepareRaw.java",
|
|
"package/bridge/ghidra/ReaGhidraPrepareDol.java",
|
|
"package/bridge/ghidra/ReaGhidraApplySeeds.java",
|
|
"package/bridge/ghidra/ReaGhidraAnalysisOptions.java",
|
|
"package/bridge/ghidra/ReaGhidraNoReturnFix.java",
|
|
"package/bridge/ghidra/ReaGhidraMipsSoftFloat.java",
|
|
"package/scripts/lib/cli-process-environment.mjs",
|
|
];
|
|
|
|
async function verifyListing(listing: string) {
|
|
const workspace = await createTestTempDirectory("rea-tar-listing-");
|
|
const { stdout } = await execFileAsync(process.execPath, [
|
|
"--input-type=module",
|
|
"--eval",
|
|
`import { FULL_CATALOG_IDENTITY } from ${JSON.stringify(catalogUrl)};
|
|
let bridgeParsed = false;
|
|
const exec = async (command, args) => {
|
|
let stdout;
|
|
if ((command === "npm" && args[0] === "pack") || (command === process.execPath && args[1] === "pack")) stdout = "fixture.tgz\\n";
|
|
else if (command === "tar" && args[0] === "-tf") stdout = ${JSON.stringify(listing)};
|
|
else if (command === "tar" && args[0] === "-xOf" && args[2] === "package/package.json")
|
|
stdout = JSON.stringify({ bin: { rea: "scripts/rea.mjs", "rea-agents": "scripts/rea.mjs" }, mcpName: "io.github.morluto/rea", dependencies: { "@lydell/node-pty": "1.1.0" } });
|
|
else if (command === "tar" && args[0] === "-xOf" && args[2] === "package/skills/reverse-engineer-anything/SKILL.md")
|
|
stdout = " tool_count: " + FULL_CATALOG_IDENTITY.counts.mcp_tools + "\\n";
|
|
else if (command === "tar" && args[0] === "-xf") stdout = "";
|
|
else if (command === "python3") { bridgeParsed = true; stdout = ""; }
|
|
else throw new Error("Unexpected command: " + command + " " + JSON.stringify(args));
|
|
return { stdout, stderr: "" };
|
|
};
|
|
const commands = { exec, execNpm: (args) => exec("npm", args) };
|
|
const { verifyPackagePack } = await import(${JSON.stringify(packerUrl)});
|
|
try {
|
|
const result = await verifyPackagePack({ root: process.cwd(), workspace: ${JSON.stringify(workspace)}, commands });
|
|
process.stdout.write(JSON.stringify({ ...result, bridgeParsed }));
|
|
} catch (cause) {
|
|
process.stdout.write(JSON.stringify({ error: cause.message, bridgeParsed }));
|
|
}`,
|
|
]);
|
|
return { result: JSON.parse(stdout), workspace };
|
|
}
|
|
|
|
it("preserves a literal trailing CR in an LF-listed member", async () => {
|
|
const listing =
|
|
members.map((path) => (path === bridge ? `${path}\r` : path)).join("\n") +
|
|
"\n";
|
|
const { result } = await verifyListing(listing);
|
|
expect(result).toEqual({
|
|
error: `package omitted ${bridge}`,
|
|
bridgeParsed: false,
|
|
});
|
|
});
|
|
|
|
it("accepts exact bridge entries from a CRLF tar listing and still rejects generated bytecode", async () => {
|
|
const ending = "\r\n";
|
|
const { result, workspace } = await verifyListing(
|
|
members.join(ending) + ending,
|
|
);
|
|
expect(result).toEqual({
|
|
tarball: join(workspace, "pack/fixture.tgz"),
|
|
bridgeParsed: true,
|
|
});
|
|
|
|
const rejected = await verifyListing(
|
|
[...members, "package/bridge/stale.pyc"].join(ending) + ending,
|
|
);
|
|
expect(rejected.result).toEqual({
|
|
error: "package contained generated Python bytecode",
|
|
bridgeParsed: false,
|
|
});
|
|
});
|
|
|
|
it("accepts exact bridge entries from an LF tar listing", async () => {
|
|
const { result } = await verifyListing(members.join("\n") + "\n");
|
|
expect(result.bridgeParsed).toBe(true);
|
|
});
|