Recovers how a script method id maps to its implementation, the foundation for body-level normalisation. Each CMC_*_Runner::run is a switch(id) (vtable slot 17); every case is the method body — inline (MSVC6) or a tail-call to a separate show()/load() (MSVC8). The extractor parses the jump table at the disassembly level (Ghidra's decompiler jump-table recovery silently dropped the big runners), fingerprints each case by its ordered CALL anchors (Class::method / vtbl+0xNN), and expands thin wrappers one level so MSVC8 lines up with MSVC6. Validated on the golden pair: Animo SHOW..RESUME (id 1-4) yield identical leaves (getAnimo + vtbl+0xa0/0xa4/0x4c/0x50) across both compilers. Coverage 30/32 runners; Piklib 475 / BlooMoo 619 dispatch rows. - extract_engine_surface.py: extract_method_dispatch (schema_version -> 4) - snapshots regenerated with the method_dispatch axis - ams: Snapshot.method_dispatch; diff axis keyed (owner,id) on [impl,calls] with method-name join; render METHOD BODIES section; cli --only dispatch; owner filter - UI: "Ciała metod" diff axis + browse tab - tests: body-change unit + cross-compiler vtbl assertion -> 29/29 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
1.5 KiB
Python
50 lines
1.5 KiB
Python
"""CLI: diff two engine-surface snapshots.
|
|
|
|
python -m ams OLD.snapshot.json NEW.snapshot.json [--owner CMC_Animo] [--only types,methods] [--json]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
from .diff import compute_diff, filter_by_owner
|
|
from .render import render_text
|
|
from .snapshot import Snapshot
|
|
|
|
_AXES = ["types", "methods", "events", "fields", "layout", "dispatch"]
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
p = argparse.ArgumentParser(prog="ams", description="Diff two engine-surface snapshots.")
|
|
p.add_argument("old", help="older snapshot.json")
|
|
p.add_argument("new", help="newer snapshot.json")
|
|
p.add_argument("--owner", help="restrict to one class, e.g. CMC_Animo")
|
|
p.add_argument("--only", help="comma-separated axes to show: " + ",".join(_AXES))
|
|
p.add_argument("--json", action="store_true", help="emit machine-readable JSON")
|
|
args = p.parse_args(argv)
|
|
|
|
old = Snapshot.load(args.old)
|
|
new = Snapshot.load(args.new)
|
|
diff = compute_diff(old, new)
|
|
if args.owner:
|
|
diff = filter_by_owner(diff, args.owner)
|
|
|
|
if args.json:
|
|
print(json.dumps(diff, indent=2, sort_keys=True))
|
|
return 0
|
|
|
|
only = None
|
|
if args.only:
|
|
only = {a.strip() for a in args.only.split(",") if a.strip()}
|
|
bad = only - set(_AXES)
|
|
if bad:
|
|
p.error("unknown axis: {0} (choose from {1})".format(",".join(sorted(bad)), ",".join(_AXES)))
|
|
print(render_text(diff, only=only))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|