Files
Aidem-Media-DLL-Analysis/ams/snapshot.py
Patryk Gensch 27399a52b1 Method dispatch axis: map id -> body via Runner::run switch
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>
2026-05-31 13:15:58 +02:00

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", "?"))