re_behaviour: the emulator check for given functions of a worktree

This commit is contained in:
KeelrLeek committed 2026-10-05 16:54:58 -05:00
1 parent 0d764799a7
commit 5211cee9c4
3 files changed
+49 -3

No files matched your search

+7 -3
View File
@@ -255,7 +255,7 @@ def build_parser():
p = sub.add_parser("who", help="who has worked on a scope (notes, reviews, claims, messages)")
p.add_argument("scope")
p = sub.add_parser("re", help="reverse-engineering lookups from the project's image and annotations")
p.add_argument("action", choices=["disasm", "xrefs", "bytes", "lookup", "ghidra", "ghidra-tools", "debugger-tools", "match", "similar", "struct-audit"])
p.add_argument("action", choices=["disasm", "xrefs", "bytes", "lookup", "ghidra", "ghidra-tools", "debugger-tools", "match", "similar", "struct-audit", "behaviour"])
p.add_argument("target", nargs="?", default="", help="an address (0x4AD850) or a name; for ghidra, the tool")
p.add_argument("arguments", nargs="?", default="{}", help="ghidra: the tool's arguments as JSON")
p.add_argument("--decomp", help="match: the checkout or worktree to compile from")
@@ -690,8 +690,12 @@ def main(argv=None):
out(r, f"summary #{r['id']} pinned to {r['thread']}")
elif cmd == "re":
from .reverse import Ghidra, Reverse
r = None if args.action.startswith(("ghidra", "debugger", "match")) else Reverse(harbor.project.reverse)
if args.action in ("similar", "struct-audit"):
r = None if args.action.startswith(("ghidra", "debugger", "match", "behaviour")) else Reverse(harbor.project.reverse)
if args.action == "behaviour":
from .reverse import behaviour_check
res = behaviour_check(harbor.project.reverse, args.target, args.decomp)
out(res, "\n".join(f"{r['address']} {r.get('verdict', ''):<8} {r.get('name', '')} {r.get('reason') or ''}" for r in res))
elif args.action in ("similar", "struct-audit"):
from .reverse import run_toolkit
res = (run_toolkit(harbor.project.reverse, "similar", [args.target]) if args.action == "similar" else
run_toolkit(harbor.project.reverse, "struct_audit", ["--struct", args.target], json_to_file=True))
+14
View File
@@ -539,6 +539,20 @@ def re_match_program(targets: str = "", decomp: str | None = None) -> dict:
raise ToolError(str(error)) from None
@mcp.tool()
def re_behaviour(targets: str, decomp: str | None = None) -> list:
"""The emulator check: run the game's own machine code and your compiled C of each function (up to 30
addresses; decomp = your worktree) from the same registers, stack and memory, 12 trials each, and compare the
result, every memory write, the calls made and their arguments. verdict 'same' means it behaves like the game;
'differs' gives the first divergence in words (e.g. 'returns 1 vs 0', 'original writes 0x86BAC0=5, ours 4');
'weak' means no trial ran to the end in both. Use it before submitting anything that does not byte-match."""
try:
from .reverse import behaviour_check
return behaviour_check(harbor().project.reverse, targets, decomp)
except (HarborError, projects.ProjectError) as error:
raise ToolError(str(error)) from None
@mcp.tool()
def re_similar(target: str, k: int = 10, min_score: float = 0.7) -> dict:
"""Functions whose instructions look like this one (normalized: addresses blanked, registers and small
+28
View File
@@ -614,3 +614,31 @@ def match_program(config, targets=None, decomp=None):
"ret_pops", "ours_ret_pops", "relocation_problems", "note")
return {"counts": counts, "results": [{k: r[k] for k in keep if k in r} for r in rows] if wanted else
[{k: r[k] for k in keep if k in r} for r in rows if r.get("status") != "differs"][:200]}
def behaviour_check(config, targets, decomp=None, precision=24):
"""Run the original machine code and our compiled C of each function in an emulator from the same inputs and
compare results, memory writes and calls (diffrun.py). targets: 1 to 30 addresses."""
import subprocess
import tempfile
config = config or {}
command = config.get("diffrun_command")
if not command:
raise HarborError("this project has no diffrun_command in its [reverse] table")
wanted = [t.strip() for t in re.split(r"[,\s]+", str(targets)) if t.strip()]
if not wanted or len(wanted) > 30:
raise HarborError("give 1 to 30 addresses")
command = command if isinstance(command, list) else [command]
with tempfile.TemporaryDirectory(dir=config.get("debugger_temp")) as tmp:
out = Path(tmp) / "behaviour.json"
argv = [*command, *wanted, "--json", str(out), "--precision", str(int(precision)), "--jobs", "4"]
if decomp:
argv += ["--decomp", str(decomp)]
result = subprocess.run(argv, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=1500,
cwd=str(Path(command[-1]).parent),
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
if not out.exists():
raise HarborError("the emulator check failed: " + (result.stdout + result.stderr).strip()[-800:])
rows = json.loads(out.read_text(encoding="utf-8"))
keep = ("address", "name", "verdict", "reason", "weak", "counts", "notes")
return [{k: r[k] for k in keep if k in r} for r in rows]