Files
Aidem-Media-DLL-Analysis/tests/test_versions.py
Patryk Gensch 8875540186 Extract Piklib 6.1 methods by name from Runner::run
6.1 has no prepareMthHashSet and no id-switch: CMC_*_Runner::run takes the method
*name* (CXString) and dispatches via a `CXString(tmp,"name"); equalsIgnoreCase(name)`
chain. extract_methods now falls back (only when the hashset pass finds nothing) to
scanning run() for that pattern, recovering the names (no numeric ids).

6.1 now yields 186 methods (Animo: show/hide/play/setFPS/...); dispatch stays 0 since
the string chain isn't a jump table. 7.1/8.x untouched (they have prepareMthHashSet).

Note: 6.1 names are camelCase/lowercase vs 7.1+ UPPERCASE (engine compares case-
insensitively), so a cross-version method diff needs case folding to be clean.

38/38 tests (test_versions updated: 6.1 methods present with id=None).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 20:25:20 +02:00

59 lines
2.5 KiB
Python

"""Per-version coverage on the Piklib evolution 6.1 -> 7.1 (-> 8.x). Documents what each
early engine actually exposes, and guards the tagged-factory type extraction (6.1)."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from ams.diff import compute_diff
from ams.snapshot import Snapshot
SNAP_DIR = Path(__file__).resolve().parents[1] / "snapshots"
P61 = SNAP_DIR / "PIKLib61.dll.snapshot.json"
P71 = SNAP_DIR / "PIKLIB71.dll.snapshot.json"
pytestmark = pytest.mark.skipif(not P61.exists(), reason="6.1/7.1 snapshots not present")
def _load(p: Path) -> Snapshot:
with open(p, encoding="utf-8") as fh:
return Snapshot(json.load(fh))
def test_piklib61_early_engine_partial_surface():
s = _load(P61)
assert s.binary["engine"] == "Piklib"
# types come from the tag-based CMC_Scene::resolve ladder (names recovered, ctor/size not)
names = {t["script_name"] for t in s.types}
assert len(names) >= 20 and {"ANIMO", "ARRAY", "BUTTON"} <= names
assert all(t.get("cpp_class") is None or t["cpp_class"].startswith("CMC_") for t in s.types)
assert s.events and s.fields
# 6.1 predates prepareMthHashSet: methods are dispatched by NAME in run(), so they're
# recovered without numeric ids, and the id-switch dispatch axis stays empty.
assert s.methods and all(m["id"] is None for m in s.methods)
assert s.method_dispatch == []
animo = {m["name"] for m in s.methods if m["owner"] == "CMC_Animo"}
assert {"show", "hide", "play"} <= animo # 6.1 uses lower/camelCase names
def test_piklib71_full_surface():
s = _load(P71)
assert s.binary["engine"] == "Piklib"
assert s.types and s.methods and s.events and s.fields and s.method_dispatch
# 7.1 uses the inline-ctor factory, so most types resolve their C++ class
assert sum(1 for t in s.types if t.get("cpp_class")) > len(s.types) // 2
def test_61_to_71_method_id_evolution():
s61, s71 = _load(P61), _load(P71)
shared = {t["script_name"] for t in s61.types} & {t["script_name"] for t in s71.types}
assert {"ANIMO", "ARRAY", "BUTTON"} <= shared # stable type core across the two early versions
# both expose a rich method surface, but only 7.1 carries numeric ids (prepareMthHashSet);
# 6.1's are name-only. (Names also differ in case - 6.1 camelCase vs 7.1 UPPERCASE - which is
# why a raw method diff is noisy without case folding.)
assert len(s61.methods) > 100 and all(m["id"] is None for m in s61.methods)
assert len(s71.methods) > 100 and all(m["id"] is not None for m in s71.methods)