# aidem_media_playground Narzędzie do **analizy różnicowej** silników gier Aidem Media (Piklib / BlooMoo). Cel: katalog gier (z ISO/ZIP), wersje silnika z hashami, oraz ekstrakcja i porównywanie "powierzchni silnika" — typów, metod, eventów i pól klas `CMC_*` — między wersjami. ## Status Faza 1: **ekstraktor `snapshot.json` z Ghidry** (walidacja na golden pair PIKLIB8 ↔ bloomoo). Infrastruktura (FastAPI + worker + DB + front) dochodzi dopiero, gdy format snapshotu będzie sprawdzony na realnych binariach. ## Architektura (docelowa) Modularny monolit + worker. Backend Python/FastAPI tylko zleca, wersjonuje i diffuje. Cała ekstrakcja żyje w **workerze = Ghidra headless + ten skrypt**, bo wymaga dostępu do call-grafu, referencji i vtable. Worker emituje `snapshot.json`, monolit go konsumuje. ``` Front (centrum dowodzenia) ─ FastAPI (katalog/hashe/diff) ─ PostgreSQL │ kolejka Worker: Ghidra headless + extract_engine_surface.py ``` ## Zasada ekstrakcji Ekstrakcja stoi na **kotwicach semantycznych** (cele wywołań, referowane literały stringów, immediaty `PUSH`), a **nie** na tekście dekompilatu. Dzięki temu jeden skrypt działa na MSVC6 (Piklib) i MSVC8 (BlooMoo) mimo różnego kodu wynikowego. | Co | Kotwica w Ghidrze | Status | |----|-------------------|--------| | Typy | `CMC_ObjectsContainer::resolve`: `operator==("NAME")` → `operator_new(SIZE)` → `CMC_X::CMC_X` | ✅ (+ `dispatch_addr`, `via_module_iface`) | | Metody | `CMC_*_Runner::prepareMthHashSet`: `CInteger(id)` + `CStringHashCode("NAME")` + `CHashtable::put` | ✅ (+ `method_inheritance`) | | Eventy | `CMC_*::getBehavioursList`: lista literałów `CXString` | ✅ (lista per klasa, bez dziedziczenia) | | Pola (skryptowe) | ctory `CMC_*`: literały czytane przez `CMElement::getPropertyValue` → nazwa + typ pola (FPS, PRELOAD, VISIBLE…) | ✅ (+ `field_inheritance`) | | Layout C++ (bonus) | ctory `CMC_*`: store'y `this+offset` przez P-code (rozmyte, `is_vtable`) | ✅ pod `struct_layout` | ## Uruchomienie ekstraktora **W GUI Ghidry** (najszybciej do walidacji): skopiuj `ghidra_scripts/extract_engine_surface.py` do swojego katalogu skryptów (Script Manager → *Manage Script Directories*), otwórz program, uruchom skrypt. Wynik trafi do `.snapshot.json` w katalogu roboczym Ghidry. **Headless** (tryb docelowy): ```bash analyzeHeadless -process PIKLIB8.dll \ -postScript extract_engine_surface.py "$(pwd)/snapshots/PIKLIB8.snapshot.json" ``` ## Akwizycja — ISO/ZIP → katalog Worker, który domyka łańcuch od *pliku gry* do wpisu w katalogu: rozpakowuje archiwum, **sam znajduje DLL silnika** (po markerach w binarce — `CMC_ObjectsContainer` w RTTI — więc działa nawet po zmianie nazwy pliku), liczy hashe (sha256 + md5 + opcjonalnie ssdeep), odpala Ghidrę headless z ekstraktorem i ląduje snapshotem w bazie. ```bash pip install -e ".[api,acquire]" # acquire = ppdeep (fuzzy hash, opcjonalny) export GHIDRA_HEADLESS=/path/to/ghidra/support/analyzeHeadless # albo GHIDRA_HOME python -m ams.acquire game.iso --game "Reksio i UFO" # ISO/ZIP/katalog/luźny DLL python -m ams.acquire dump_dir --game "Reksio i UFO" --sink http --post http://127.0.0.1:8000 python -m ams.acquire PIKLIB8.dll --identify-only # tylko unpack+identify+hash, bez Ghidry ``` `--sink db` (domyślnie) importuje wprost do bazy, `--sink http` POST-uje na `/snapshots`, `--sink none` zostawia sam snapshot. `--identify-only` to suchy bieg do walidacji bez Ghidry. Rozpakowywanie stoi na `bsdtar` (libarchive — czyta i ISO9660, i ZIP); ZIP ma fallback na czysty Python. Snapshot dostaje doklejony blok `binary.acquisition` (źródło, nazwa DLL) oraz `binary.fuzzy/md5/size`. ## Diff engine (CLI) ```bash python -m ams OLD.snapshot.json NEW.snapshot.json [--owner CMC_Animo] \ [--only types,methods,events,fields,layout] [--json] ``` Porównuje dwa snapshoty po 4 osiach (added/removed/changed) + wykrywa metody przeniesione w hierarchii. Oś `struct_layout` jest sensowna tylko między wersjami tego samego kompilatora. ## Backend (FastAPI + katalog) Modularny monolit nad SQLAlchemy — domyślnie SQLite (zero setupu), gotowy pod Postgres przez `DATABASE_URL`. Pełny snapshot trzymany jest w bazie verbatim; diff czyta go z powrotem przez `ams.diff`. ```bash pip install -e ".[api,dev]" # zależności python -m ams.api.importer --game "Reksio i UFO" snapshots/PIKLIB8.dll.snapshot.json uvicorn ams.api.app:create_app --factory --reload # serwer ``` Endpointy: `POST/GET /games`, `POST/GET /snapshots` (import deduplikowany po sha256), `GET /diff?old=&new=[&owner=]`, `GET /health`. Testy: `pytest` (11, w tym integracyjne na golden pair). ## Front — Command Center Po starcie serwera otwórz **http://127.0.0.1:8000/** (`/` → `/ui/`). Statyczny UI bez build-stepu (czysty HTML/CSS/JS w `ams/api/static/`, serwowany przez FastAPI): lista gier/wersji, wybór dwóch wersji (A/B), wizualny diff po 4 osiach z filtrem klasy i przeglądarka pojedynczej powierzchni. ## Format snapshotu `schema_version`, `binary{name,sha256,engine,compiler,factory_addr}`, oraz listy `types` / `methods` / `events` / `fields`. Diff = operacje na zbiorach dwóch snapshotów.