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>
63 lines
1.6 KiB
Python
63 lines
1.6 KiB
Python
"""Loading and light typed access to an engine-surface snapshot.json."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
|
|
@dataclass
|
|
class Snapshot:
|
|
"""Thin wrapper over a parsed snapshot.json. Axes are returned as plain lists of dicts so
|
|
they round-trip cleanly to JSON; the diff engine works directly on those records."""
|
|
|
|
raw: dict[str, Any]
|
|
|
|
@classmethod
|
|
def load(cls, path: str) -> "Snapshot":
|
|
with open(path, "r", encoding="utf-8") as fh:
|
|
return cls(json.load(fh))
|
|
|
|
@property
|
|
def binary(self) -> dict[str, Any]:
|
|
return self.raw.get("binary", {})
|
|
|
|
@property
|
|
def types(self) -> list[dict]:
|
|
return self.raw.get("types", [])
|
|
|
|
@property
|
|
def methods(self) -> list[dict]:
|
|
return self.raw.get("methods", [])
|
|
|
|
@property
|
|
def events(self) -> list[dict]:
|
|
return self.raw.get("events", [])
|
|
|
|
@property
|
|
def fields(self) -> list[dict]:
|
|
return self.raw.get("fields", [])
|
|
|
|
@property
|
|
def struct_layout(self) -> list[dict]:
|
|
return self.raw.get("struct_layout", [])
|
|
|
|
@property
|
|
def method_dispatch(self) -> list[dict]:
|
|
return self.raw.get("method_dispatch", [])
|
|
|
|
@property
|
|
def method_inheritance(self) -> list[dict]:
|
|
return self.raw.get("method_inheritance", [])
|
|
|
|
@property
|
|
def field_inheritance(self) -> list[dict]:
|
|
return self.raw.get("field_inheritance", [])
|
|
|
|
@property
|
|
def label(self) -> str:
|
|
b = self.binary
|
|
return "{0} [{1}/{2}]".format(
|
|
b.get("name", "?"), b.get("engine", "?"), b.get("compiler", "?"))
|