#!/usr/bin/env python3 """Promote Win16 imports only after the complete caller ledger is verified.""" from __future__ import annotations import argparse import csv from collections import Counter, defaultdict from pathlib import Path ROOT = Path(__file__).resolve().parent.parent IMPORT_COLUMNS = ( "address", "library", "import_name", "stack_purge_bytes", "call_sites", "status", "stage", "prototype", "evidence", "notes", ) def read_tsv(name: str) -> list[dict[str, str]]: with (ROOT / name).open(newline="", encoding="utf-8") as handle: return list(csv.DictReader(handle, delimiter="\t")) def expected_rows() -> list[dict[str, str]]: rows = read_tsv("IMPORT_RECONSTRUCTION.tsv") functions = read_tsv("FUNCTION_RECONSTRUCTION.tsv") verified_names = { row["raw_name"] for row in functions if row["status"] == "verified" } if len(verified_names) != len(functions): raise SystemExit("refusing import promotion before every function is verified") calls: Counter[str] = Counter() callers: dict[str, set[str]] = defaultdict(set) for reference in read_tsv("REFERENCES.tsv"): if "CALL" not in reference["reference_type"]: continue target = reference["to_address"] if not target.startswith("1228:"): continue calls[target] += 1 callers[target].add(reference["from_function"]) for row in rows: if not row["prototype"]: raise SystemExit(f"missing prototype for {row['address']}") expected_count = int(row["call_sites"]) if calls[row["address"]] != expected_count: raise SystemExit( f"call-count drift for {row['address']}: " f"ledger={expected_count}, references={calls[row['address']]}" ) unresolved = callers[row["address"]] - verified_names if unresolved: raise SystemExit( f"unverified callers for {row['address']}: " + ", ".join(sorted(unresolved)) ) if row["status"] == "verified": continue row["status"] = "verified" if expected_count == 0: row["stage"] = "unused-slot-verified" row["evidence"] += ( "; complete Ghidra CALL inventory and NE relocation inventory: " "zero call sites" ) row["notes"] = ( "Explicit imported NE slot with no CALL reference anywhere in the " "complete image; no TDKPIN call-site behavior exists to reconstruct." ) else: row["stage"] = "call-sites-verified" row["evidence"] += ( f"; all {expected_count} direct call sites map to verified " "address-linked function rows and readable C" ) row["notes"] = ( f"All {expected_count} relocation-backed direct call sites are in " "verified functions whose evidence records argument order, segmented " "pointer or handle use, return handling, side effects, and branches." ) return rows def serialize(rows: list[dict[str, str]]) -> str: from io import StringIO output = StringIO() writer = csv.DictWriter( output, fieldnames=IMPORT_COLUMNS, delimiter="\t", lineterminator="\n" ) writer.writeheader() writer.writerows(rows) return output.getvalue() def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--check", action="store_true") arguments = parser.parse_args() path = ROOT / "IMPORT_RECONSTRUCTION.tsv" expected = serialize(expected_rows()) current = path.read_text(encoding="utf-8") if arguments.check: if current != expected: raise SystemExit("IMPORT_RECONSTRUCTION.tsv needs deterministic promotion") return path.write_text(expected, encoding="utf-8") if __name__ == "__main__": main()