re_match_program: the whole-program LTCG build and check; progress counts its exact matches

This commit is contained in:
KeelrLeek committed 2026-10-05 15:11:21 -05:00
1 parent d2c365d531
commit 5a0fb44c7d
3 files changed
+54 -2

No files matched your search

+17 -1
View File
@@ -43,7 +43,8 @@ review queue, presence and stop flags. Protocol:
12. Pick functions with next_functions() (easiest first in your bands). Before writing, read re_lookup (it states the
calling convention read from the bytes) and re_similar (a readable near-duplicate is a head start). Before
submitting, run re_match on your worktree: 'match' or 'arguments in registers' is byte-identical, otherwise
similarity shows how close you are. After two failed attempts, defer(address, reason, evidence) instead of
similarity shows how close you are; re_match_program (slow, once before submitting) builds the whole
program the way the game was built, so register conventions reproduce. After two failed attempts, defer(address, reason, evidence) instead of
looping. Use re_struct_audit before changing a struct in types.h."""
KindName = Literal["note", "question", "answer", "request", "finding", "retraction", "decision", "status", "handoff"]
@@ -523,6 +524,21 @@ def re_match(targets: str, decomp: str | None = None) -> dict:
raise ToolError(str(error)) from None
@mcp.tool()
def re_match_program(targets: str = "", decomp: str | None = None) -> dict:
"""The exact-match check the way the game was built: every readable function of your worktree (decomp)
goes into one /GL + /LTCG image with the game's compiler, so register-argument conventions and inlining
reproduce (re_match compiles one file at a time and cannot). Slow (1.5 to 9 minutes): run it once before
submitting, with targets = the functions you changed. Statuses: exact, exact-relaxed (identical after
dropping leading stack-argument loads), exact-but-targets-differ, differs (with similarity and the
register inputs on both sides)."""
try:
from .reverse import match_program
return match_program(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
+2 -1
View File
@@ -205,7 +205,8 @@ class FunctionQueue:
# ------------------------------------------------------------------ progress
def progress(self):
facts, statuses, deferred = self.r._facts(), self.statuses(), self.deferrals()
exact = {a for a, row in self._results("match_results").items() if str(row.get("status")) == "match"}
exact = {a for a, row in self._results("match_results").items()
if str(row.get("status")) in ("match", "exact", "exact-relaxed")}
verified = {a for a, row in self._results("diffrun_results").items() if str(row.get("verdict")) == "same"}
library = set(self._results("library_results"))
ranges = self.library_ranges()
+35
View File
@@ -531,3 +531,38 @@ def run_toolkit(config, key, argv, json_to_file=False, timeout=900):
return json.loads(text)
except ValueError:
raise HarborError(f"{key} failed: " + (result.stdout + result.stderr).strip()[-800:]) from None
def match_program(config, targets=None, decomp=None):
"""Build every readable function into one /GL + /LTCG image the way the game was built (stand-in callers, stubs
for the rest, globals at their original addresses) and check the targets against the original bytes. Slow: a
build takes 1.5 to 9 minutes depending on load."""
import subprocess
import sys
import tempfile
config = config or {}
script = config.get("ltcg")
if not script or not Path(script).exists():
raise HarborError("this project has no ltcg script in its [reverse] table")
wanted = [t.strip() for t in re.split(r"[,\s]+", str(targets or "")) if t.strip()]
extra = ["--decomp", str(decomp)] if decomp else []
run = lambda argv, timeout: subprocess.run([sys.executable, "-B", str(script), *argv], capture_output=True,
text=True, encoding="utf-8", errors="replace", timeout=timeout,
cwd=str(Path(script).parent),
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
built = run(["build", *extra], 1800)
if built.returncode != 0:
raise HarborError("the whole-program build failed: " + (built.stdout + built.stderr).strip()[-800:])
with tempfile.TemporaryDirectory(dir=config.get("debugger_temp")) as tmp:
out = Path(tmp) / "check.json"
checked = run(["check", *extra, "--json", str(out)] + (["--only", ",".join(wanted)] if wanted else []), 900)
if not out.exists():
raise HarborError("the whole-program check failed: " + (checked.stdout + checked.stderr).strip()[-800:])
rows = json.loads(out.read_text(encoding="utf-8"))
counts = {}
for r in rows:
counts[r.get("status")] = counts.get(r.get("status"), 0) + 1
keep = ("address", "name", "status", "first_difference", "similarity", "registers_in", "ours_registers_in",
"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]}