Files
devarea/RETROARCH_STORE.md
T
mr.zeroandClaude Opus 5 0f2fe6de62 Work out and build the RetroArch store
RETROARCH_STORE.md is the design, written from measurements rather than
documentation: the format samples, thumbnail layout and core names come from
a real RetroArch install on this machine, the core availability from the
libretro buildbot index, and the CRC32s from the catalog itself.

Three findings shaped it. The playlist folder is not necessarily inside the
RetroArch directory — on the machine measured, playlist_directory is under
~/Documents while cores and thumbnails are under ~/Library — so reading
retroarch.cfg is a requirement, not a nicety. Thumbnails must be PNG, and
one of our covers is a GIF, which no store had cared about before. And a
.lpl entry has nowhere to put a description, so desc and author do not
survive the trip.

Built the same day, in three repositories: engines/warpstore (the shared
core, extracted immediately rather than in the second phase the plan
proposed), tools/warp-engine-retroarch-store and tools/ttg-retroarch-store.
The closing section of the design records what the implementation changed
about it.

MORE_STORES.md item 4 is closed, its "a minta" section now points at the
extracted core, and REPO_REFACT.md carries the three new repos —
check-repos.sh is green at 49.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:08:59 +02:00

25 KiB

RetroArch store — kidolgozott terv

Készült 2026-08-17-én, a MORE_STORES.md 4. pontjának kifejtéseként. Minden alább szereplő formátum- és útvonal-állítás egy valódi RetroArch-telepítésen mérve (macOS, ~/Library/Application Support/RetroArch), a mag-elérhetőség pedig a libretro buildbot indexéből.

Megvalósítva ugyanaznap. Három repó: engines/warpstore (közös mag), tools/warp-engine-retroarch-store (motor), tools/ttg-retroarch-store (a mi store-unk). A 11. pont javaslatával szemben a közös rész azonnal kiemelve — lásd az „Amit a megvalósítás megváltoztatott" szakaszt a végén. Wiki: warpstore, motor, store.


1. Mit ad, és mit nem

Új asset-fajtát nem fogyaszt: a RetroArch magok emulátorok, tehát pontosan azt eszik, amit a Batocera-store is — a cartridge-et. A nyereség nem a katalógus szélessége, hanem a gazdagépek száma: asztali Linux/Windows/macOS, Android, Steam Deck, konzolportok.

A mai katalógusból (released + archived, 2026-08-17-i mérés) ez 6 cím, összesen 233 KB:

Platform Szoftver Cím Fájl Méret CRC32
c64 blessingofra Blessing of Ra blessingofra-2.0.0.prg 10 561 4253CFC6
c64 rabbitc64 Rabbit Scooter rabbit-1.0.0.prg 8 072 31BF558B
c64 c64demo C64 Demo for Arok Party 2026 c64demo-1.0.prg 4 641 AF681FFC
tic80 impostor Definitely not an Impostor impostor-1.0.tic 163 497 8D9A45DA
tic80 bombexpert BombExpert bombexpert-0.2.tic 25 227 A67DDF4A
tic80 mranderson Mr Anderson's Adventure mranderson-0.1.tic 21 657 E98C7712

Egy egész store negyed megabájt — ez az, ami miatt a szinkron gyakorlatilag ingyen van, és amiért az Android-célpont zip-ként is elküldhető.

A két mag mindenhol van. A buildbot indexét végigkérdezve:

Cél vice_x64 tic80 Fájlnév-alak
apple/osx/arm64, apple/osx/x86_64 *_libretro.dylib
linux/x86_64, linux/aarch64, linux/armhf, linux/armv7-neon-hf *_libretro.so
windows/x86_64 *_libretro.dll
android/arm64-v8a, android/armeabi-v7a *_libretro_android.so

Tehát nincs olyan RetroArch-gazdagép, amin a katalógusunk ne futna — az Androidon viszont a mag fájlneve más (_android.so), és ez a tervben külön kezelendő.

Amire nem jó. A html és a natív zipek maradnak kiszolgálatlanul: nincs böngésző-mag, és a LÖVE-hoz sincs valódi mag (a lutro nem LÖVE-kompatibilis). Batocerán és a többi ES-frontendes gépen szintén nem ez a helyes út — ott a RetroArch a háttérben fut, a könyvtár az ES-é, tehát a Batocera-store dolgozik.


2. A gazdaplatform, ahogy valóban kinéz

Négy dolgot érdemes tudni, mert mind a négy megcáfol egy kézenfekvő feltevést.

(a) A retroarch.cfg az egyetlen igazságforrás, és a könyvtárak szét vannak szórva. A mért gépen:

libretro_directory   = ~/Library/Application Support/RetroArch/cores
thumbnails_directory = ~/Library/Application Support/RetroArch/thumbnails
playlist_directory   = ~/Documents/RetroArch/playlists      ← más ág!
system_directory     = ~/Documents/RetroArch/system

A playlist-könyvtár tehát nem a RetroArch alapkönyvtárában van. Aki a <base>/playlists mintát égeti bele, az ezen a gépen csendben a semmibe ír. A motor kötelezően a retroarch.cfg-ből olvassa a négy útvonalat, és csak akkor tippel, ha nincs cfg. A ~ feloldandó, ahogy a Windows-os portable telepítések : prefixe is (a RetroArch saját alapkönyvtárát jelenti).

(b) A .lpl sémája. Egy valódi playlist a gépről (rövidítve) — ez a formátum, amit írunk, nem egy dokumentációból vett közelítés:

{
  "version": "1.5",
  "default_core_path": "",
  "default_core_name": "",
  "label_display_mode": 0,
  "right_thumbnail_mode": 0,
  "left_thumbnail_mode": 0,
  "thumbnail_match_mode": 0,
  "sort_mode": 0,
  "items": [
    {
      "path": "/…/Altered Beast (USA).md",
      "entry_slot": -1,
      "label": "Altered Beast (USA)",
      "core_path": "/…/cores/genesis_plus_gx_libretro.dylib",
      "core_name": "Sega - MS/GG/MD/CD (Genesis Plus GX)",
      "crc32": "00000000|crc",
      "db_name": "Sega - Mega Drive - Genesis.lpl"
    }
  ]
}

Két tanulság: a path absztolút út a célgépen (ez a 7. pont egész problémája), a crc32 pedig "<8 hex>|crc" alakú — a RetroArch maga gyakran csak nullákat ír bele, mi a valódit tudjuk beírni (zlib.crc32, ingyen).

(c) A borítókép útja a playlist nevéből jön. A mért gépen:

thumbnails/Sega - Mega Drive - Genesis/Named_Boxarts/*.png
                                       Named_Titles/*.png
                                       Named_Snaps/*.png

Vagyis thumbnails/<db_name a .lpl nélkül>/Named_*/<label>.png, és kizárólag PNG — a RetroArch a fájlnevet .png-re képezi. Ez a 6. pont.

(d) A magok saját nevet és kiterjesztést hoznak. A .info fájlokból:

Mag corename supported_extensions database
tic80_libretro TIC-80 tic TIC-80
vice_x64_libretro VICE x64 d64|…|prg|p00|crt|… Commodore - 64

A prg valóban ott van a VICE listájában, tehát a nyers .prg-ket a mag közvetlenül indítja — nem kell .d64-be csomagolni.

Amit a formátum nem tud: leírást. A .lpl bejegyzésnek nincs desc vagy developer mezője. A katalógus desc/author adata itt elveszik — a Batocera gamelist.xml-hez képest ez visszalépés, és nem konfigurálható. (Elvileg egy generált .rdb adná meg az Információ-panelt és az Explore nézetet, de az bináris libretrodb-formátum, külső eszközlánccal — ez a terven kívül, külön feladat.)


3. A megoldás felépítése

A Batocera-store bevált kettéosztását követi, ugyanazokkal a nevekkel:

tools/warp-engine-retroarch-store      a motor: retroarch_store.py, install.sh
        ▲
        │ config.json
tools/ttg-retroarch-store              a mi store-unk: config + wrapper installer

Amit telepít:

<content_root>/<subfolder>/<platform>/     blessingofra-2.0.0.prg, impostor-1.0.tic, …
<playlists_dir>/Teletype Games - Commodore 64.lpl
<playlists_dir>/Teletype Games - TIC-80.lpl
<thumbnails_dir>/Teletype Games - Commodore 64/Named_Boxarts/Blessing of Ra.png
                                              /Named_Titles/…  /Named_Snaps/…
<store_home>/config.json, state.json, catalog.json, store.log

content_root alapértéke <retroarch_dir>/content, a subfolder a store azonosítója. A retroarch.cfg-hez soha nem nyúlunk — így a store nem tud elrontani semmit, amit a felhasználó beállított.

Playlist platformonként, nem egyben. Nem esztétikai döntés: a default_core_path/default_core_name a playlist fejlécében van, tehát csak akkor tud minden bejegyzés magválasztás nélkül, egy kattintással indulni, ha egy playlisten egy mag van. Egy összevont „Teletype Games" listánál minden elemnek saját, gépspecifikus core_path-ot kellene adni — ami az exportnál (7. pont) azonnal elromlik.

A név sablonból jön: {store} - {label}Teletype Games - Commodore 64. Ez egyben a db_name és a borítókép-könyvtár neve is, tehát a store nevével elszigetelt névtérben vagyunk: nem írunk bele a hivatalos Commodore - 64.lpl-be, és a hivatalos thumbnail-pakkokkal sem ütközünk.

Cserébe a saját playlist-nevünkhöz nincs ikon. A RetroArch a menü-ikont assets/xmb/<téma>/png/<Playlist neve>.png alapján keresi (a mért gépen ott vannak a Commodore - 64.png / Commodore - 64-content.png párok), tehát egy TTG-ikont le tudnánk tenni — de az assets/ a RetroArch online updaterének tulajdona, ami a következő frissítéskor felülírja. Opcionális, alapból ki (branding.icons: false), és a README mondja ki, hogy nem tartós.

CLI, a Batocera-store-ral azonos alakban:

retroarch-store list        # kompatibilis címek; * telepítve, ^ frissítés van
retroarch-store sync        # letöltés + playlist + borító
retroarch-store sync impostor
retroarch-store -n sync     # dry run
retroarch-store remove c64:blessingofra
retroarch-store export <dir> --target-prefix <út>   # 7. pont
retroarch-store config

4. A playlist, amit írunk

Konkrétan, valódi adatokkal (a fenti CRC-k mértek):

{
  "version": "1.5",
  "default_core_path": "/Users/tasi/Library/Application Support/RetroArch/cores/vice_x64_libretro.dylib",
  "default_core_name": "VICE x64",
  "label_display_mode": 0,
  "right_thumbnail_mode": 0,
  "left_thumbnail_mode": 0,
  "thumbnail_match_mode": 0,
  "sort_mode": 0,
  "items": [
    {
      "path": "/Users/tasi/Library/Application Support/RetroArch/content/teletypegames/c64/blessingofra-2.0.0.prg",
      "entry_slot": -1,
      "label": "Blessing of Ra",
      "core_path": "DETECT",
      "core_name": "DETECT",
      "crc32": "4253CFC6|crc",
      "db_name": "Teletype Games - Commodore 64.lpl"
    }
  ]
}
  • label = a katalógus title-je, mert ez a megjelenő név és a borítókép-fájlnév egyszerre.
  • core_path/core_name a tételekben DETECT: a fejléc alapmagja dönt. Így egy playlist-fájl akkor is használható marad, ha a magok máshol vannak — a legrosszabb eset egy magválasztó kérdés, nem egy halott bejegyzés.
  • db_name a saját fájlnevünk, ezen keresztül találja meg a borítót.
  • A *_thumbnail_mode és label_display_mode marad 0 = a felhasználó globális beállítása érvényes. Nem írjuk felül a preferenciáit (lásd 6. pont).

5. Mag-feloldás

A configban a mag fájlneve suffix nélkül és a megjelenő neve áll:

"core": { "file": "vice_x64_libretro", "name": "VICE x64" }

A motor a suffixet a célgépből teszi hozzá — darwin.dylib, linux.so, windows.dll, android_android.so —, majd megnézi, hogy a fájl létezik-e a libretro_directory-ban:

  • létezik → a playlist fejlécébe absztolút útként bekerül, a name mellé;
  • nem létezikdefault_core_path üres marad, default_core_name a name, a tételek DETECT-en. A playlist működik, a RetroArch az első indításnál kérdez. A list/sync kiírja: magot nem találtam: vice_x64_libretro (.dylib) — DETECT, és mellé az egy soros megoldást: Online Updater ▸ Core Downloader ▸ VICE x64.

Ez a legfontosabb ergonómiai döntés: a hiányzó mag nem hiba, hanem figyelmeztetés. Egy store, ami magtelepítést követel meg, nem fog lefutni senkinek elsőre.


6. Borítókép

Egy letöltés, három hely. A thumbnail_kinds alapból mindhárom Named_* könyvtár, mert a felhasználó globális beállítása dönti el, melyiket kéri (bal/jobb panel), és mi nem akarjuk átírni a beállítását. Három 100 KB-os fájl 6 játéknál semmi; ahol a fájlrendszer engedi, hardlink, egyébként másolat.

A PNG-kényszer valódi problémát is jelent. A katalógus képei ma:

Formátum Címek
image/png bombexpert, mranderson, rabbitc64, c64demo, blessingofra (+ nem-cartridge: rabbitroller, trickster-tiles)
image/gif impostor

A RetroArch PNG-t (és JPEG/TGA/BMP-t) tud, GIF-et nem, a fájlnevet pedig .png-re képezi. Az impostor borítója tehát ma nem jelenne meg. A /api/image/:id send_file-lal az eredetit adja vissza, átkódolás nélkül — a szerver nem segít.

A motor sorrendje:

  1. magic bytes alapján PNG? → kiírjuk, kész.
  2. nem PNG, és van külső átkódoló? → sips (macOS gyári), magick/convert (ImageMagick), ffmpeg — az első elérhető nyer.
  3. semmi? → figyelmeztetés, a bejegyzés borító nélkül is jó.

Stdlib-only megkötéssel Pythonból nem kódolunk át (GIF-dekóder kellene) — és nem is kellene. A helyes javítás felfelé van: a warp_engine imageUrl-je adhatna PNG-változatot (vagy a feltöltés normalizálhatna). Egy sor a MORE_STORES.md-be: ez a store az első fogyasztó, aminek a kép formátuma is számít, nem csak a léte.


7. Út-átírás: a motor egyetlen valódi új képessége

A .lpl absztolút utakat tartalmaz a célgépen. Amíg a store ott fut, ahol a RetroArch, ez nem kérdés. Csak épp a legérdekesebb célpont, az Android, nem tud Pythont futtatni — ott a store nem tud „ott" futni.

A megoldás egy kettéválasztás, ami minden esetet lefed:

hova írunk mi kerül a playlistbe
stage_root valódi fájlműveletek helye
target_prefix ezzel az előtaggal az útvonalak

Alapból a kettő ugyanaz (helyi telepítés). Ha eltér, egy „idegen gépre szánt" RetroArch-alakú fát írunk:

# 1. Android, adb-vel
retroarch-store export /tmp/ra-ttg \
  --target-prefix /storage/emulated/0/RetroArch \
  --core-suffix _android.so
adb push /tmp/ra-ttg/. /storage/emulated/0/RetroArch/

# 2. SD-kártya: a Mac-en /Volumes/SDCARD, a kézikonzolon /mnt/sdcard
retroarch-store export /Volumes/SDCARD/RetroArch --target-prefix /mnt/sdcard/RetroArch

Ugyanez a mechanizmus adja a Steam Deck flatpak-elrendezését (~/.var/app/org.libretro.RetroArch/config/retroarch) is, ott viszont target_prefix nélkül, mert helyben fut.

Az Android útvonalait nem égetjük be: az export --android (2. fázis) az adb shell cat /storage/emulated/0/RetroArch/retroarch.cfg-ből olvassa ki a készülék saját könyvtárait, ugyanazzal a parserrel, amivel helyben. A fenti /storage/emulated/0/RetroArch csak a tartalék tipp — és ez az egyik pont, amit valódi készüléken meg kell mérni (12. pont).


8. Tulajdonlás, prune, állapot

Ez a store a három közül a legtisztább: minden, amit ír, a nevét viseli.

  • Tartalom: csak <content_root>/<subfolder>/ alatt törlünk, soha kívül.
  • Playlist: a fájl a miénk, de a merge mégis megőrző — újraírás előtt kiválogatjuk azokat a tételeket, amiknek a path-ja nem a mi tartalmi könyvtárunkban van, és visszaírjuk őket (ha a felhasználó kézzel vett fel valamit a listánkba, nem tűnik el). Ha a végén nulla tétel marad, a fájlt töröljük, nem hagyunk üres listát a menüben.
  • Borító: <thumbnails_dir>/<playlist neve>/Named_*/<label>.png, prune-nál mind a három példány megy.
  • Állapot: state.json, <platform>:<name> kulccsal (itt nincs „system", a platform az). Külön store, külön fájl — a Batocera v2 állapotával nincs migrációs viszony.
  • A verzióváltás (1.02.0.0) a régi fájlt előbb törli, mint ahogy az újat hozza, ahogy ma is.

A label fájlnévvé alakításánál a RetroArch a &*/: ` <>?\| karaktereket _-ra cseréli; a sanitizert ugyanígy írjuk meg. A mai hat címben egyik sem szerepel (az Mr Anderson's Adventure aposztrófja nincs a listán), tehát ez ma elméleti — de egy jövőbeli Foo: Bar cím néma borítóhiba lenne.


9. Verseny a futó RetroArch-csal

Ugyanaz a csapda, mint az EmulationStation gamelistjeivel: a RetroArch a playlisteket memóriában tartja, és visszaírja őket, ha módosultak (kedvencek, utolsó játék, rendezés). Ha futás közben írunk, a kilépése eltörölheti a munkánkat.

Batocerán ezt a sync-from-es + apply-gamelists --wait-pid páros oldja meg, de ott van egy Ports-menü, ami elindít minket. Itt nincs: a RetroArch nem tud szkriptet futtatni, tehát a store gazdagépen belüli indítója nem létezik. Ezért a döntés egyszerűbb és szigorúbb:

  • behavior.refuse_while_running: true (alapérték) — ha fut RetroArch process, a sync nem ír, hanem kiírja, hogy zárjuk be. --force felülbírálja.
  • A sync után az új playlistek a RetroArch újraindítása után látszanak; a menü a listát indulásnál építi. Ezt a README első bekezdésében kell kimondani, különben „nem működik" lesz a tapasztalat.
  • Indítás: shell, illetve ütemezve (cron, systemd --user timer, launchd, Task Scheduler). Ez a store-nak nincs in-app belépője, és ezt nem érdemes szépíteni.

10. A store configja

ttg-retroarch-store/config.json teljes egészében:

{
  "store": {
    "id": "ttg",
    "name": "Teletype Games",
    "base_url": "https://teletypegames.org",
    "api": { "catalog": "/api/software", "download": "/api/download" }
  },

  "paths": {
    "retroarch_dir": null,
    "playlists_dir": null,
    "thumbnails_dir": null,
    "libretro_dir": null,
    "content_root": null,
    "subfolder": "teletypegames",
    "target_prefix": null
  },

  "playlist": {
    "name_template": "{store} - {label}",
    "write_crc32": true,
    "thumbnail_kinds": ["Named_Boxarts", "Named_Titles", "Named_Snaps"]
  },

  "catalog": {
    "statuses": ["released", "archived"],
    "owner_id": null,
    "only": [],
    "exclude": []
  },

  "platforms": {
    "c64": {
      "label": "Commodore 64",
      "kind": "cartridge",
      "ext": ".prg",
      "core": { "file": "vice_x64_libretro", "name": "VICE x64" },
      "enabled": true
    },
    "tic80": {
      "label": "TIC-80",
      "kind": "cartridge",
      "ext": ".tic",
      "core": { "file": "tic80_libretro", "name": "TIC-80" },
      "enabled": true
    }
  },

  "behavior": {
    "prune": true,
    "timeout": 30,
    "insecure": false,
    "refuse_while_running": true,
    "convert_images": true
  },

  "branding": { "icons": false }
}

A null útvonalak mindenütt azt jelentik: derítsd ki — env (RETROARCH_DIR), majd retroarch.cfg, majd OS-enkénti tipplista. A config alparancs kiírja, mire jutott, ahogy a Batocera-store a detected_arch-ot:

detected: os=darwin arch=aarch64 core_suffix=.dylib
retroarch_dir  ~/Library/Application Support/RetroArch      (candidate)
playlists_dir  ~/Documents/RetroArch/playlists              (retroarch.cfg)
libretro_dir   ~/Library/Application Support/RetroArch/cores (retroarch.cfg)
cores          vice_x64_libretro ✗   tic80_libretro ✗       → DETECT

11. A közös rész: mit mikor emeljünk ki

A MORE_STORES.md helyesen mondja, hogy a katalógus-lekérés, kiadásválasztás, state.json, borító és prune-védelem közös. A most kézenfekvő lépés mégis nem az azonnali kiemelés.

1. fázis — a RetroArch-motor önálló, egyfájlos, és igen, duplikál. A retroarch_store.py átveszi a HTTP/katalógus/állapot réteget (~250 sor). Ok: egyetlen minta alapján kiemelt absztrakció rossz absztrakció, és a Batocera-store élesben fut — nem az a pillanat, hogy alatta cseréljünk réteget, amikor még azt sem tudjuk, mit tanul a második adapter. A telepítés is egyszerűbb marad: egy curl egy fájlra.

2. fázis — kiemelés, amikor két adapter már megmondja, mi közös. Amit ma is látok jelöltnek: http_get/http_download, fetch_catalog + gyorsítótár, pick_release, a config-merge, a state.json séma és az atomikus írás. Ezek egy warpstore_core.py-ba mennek, mindkét installer két fájlt tölt le. Amit a RetroArch már most tanított, és a közös részbe tartozik:

  • a resolve_for_arch általánosítása: a Batocerán csak az architektúra volt a változó, itt az operációs rendszer is (mag-suffix). Egy resolve_for_host, ami x86_64 / aarch64 / linux / darwin / windows / android / * kulcsokat is fogad, mindkettőt kiszolgálja;
  • a stage_root / target_prefix kettősség (7. pont). Ez nem RetroArch-specifikus: az asztali store (MORE_STORES.md 2.) és a kézikonzolos ES-disztrók ugyanígy „innen írok, ott fog futni" helyzetben vannak.

12. Munka és ellenőrzés

Becslés, a Batocera-motor mérete alapján (store.py 1073 sor):

Rész Új sor (kb.)
átvett réteg (HTTP, katalógus, állapot, config) 250
retroarch.cfg parser + útvonal-felderítés 90
playlist írás/merge (.lpl) 120
mag-feloldás 60
borító 3 könyvtárba + átkódolás 80
export / út-átírás 70
CLI, logolás 90
összesen ~760

Plusz install.sh (a Batocera-inak asztali változata: ~/.local/share-be, nem /userdata-ba) és két README.

Amit valódi gépen meg kell mérni — ez a lista a terv gyenge pontjai, nem formalitás:

  1. A két mag valóban elindítja-e a fájljainkat. Ez a store létjogosultsága. Nem drága ellenőrizni:
    # magok a scratchpadbe, nem a felhasználó RetroArch-jába
    curl -fsSLO https://buildbot.libretro.com/nightly/apple/osx/arm64/latest/vice_x64_libretro.dylib.zip
    curl -fsSLO https://buildbot.libretro.com/nightly/apple/osx/arm64/latest/tic80_libretro.dylib.zip
    unzip -o '*.zip'
    retroarch -L ./vice_x64_libretro.dylib ./blessingofra-2.0.0.prg
    retroarch -L ./tic80_libretro.dylib ./impostor-1.0.tic
    
    Külön kérdés a .prg autostartja: a VICE-mag betölti, de futtatja-e magától, vagy RUN-ra vár. Ha vár, kell egy magopció (vice_autostart) vagy .d64-be csomagolás — ez a terv legvalószínűbb sérülési pontja.
  2. A generált playlist megjelenik-e, a saját nevével, és a borító előjön-e a Named_* könyvtárakból, a felhasználó globális thumbnail-beállításával.
  3. Az Android útvonalai (playlist_directory, libretro_directory a készülék cfg-jében) és hogy az adb push-olt tartalom olvasható-e a RetroArch-nak scoped storage mellett.
  4. A visszaírás-verseny valódi mérete: mennyit ír vissza a RetroArch a playlistünkből kilépéskor, elég-e a refuse_while_running.
  5. A label → borító-fájlnév sanitizálás pontos karakterkészlete (a fenti lista következtetés, nem mérés) — egy Foo: Bar címmel kipróbálva kiderül.

Javasolt sorrend

  1. Az 1. ellenőrzési pont (magok + a két fájltípus indítása) — mielőtt kód íródna. Egy órányi munka, és megválaszolja, hogy a store egyáltalán működhet-e.
  2. warp-engine-retroarch-store: cfg-parser + list + sync a helyi telepítéssel, macOS-en és Linuxon.
  3. ttg-retroarch-store config, a fenti tartalommal.
  4. Export/út-átírás + Android próba valódi készüléken.
  5. Kiemelés warpstore_core.py-ba (11. pont, 2. fázis), és az imageUrl PNG-normalizálása a warp_engine-ben (6. pont).

Amit a megvalósítás megváltoztatott

(2026-08-17, a fenti terv leírása után.)

A közös rész azonnal kiemelve. A 11. pont fázisolását felülírtuk: a warpstore.py a saját repójában (engines/warpstore) született meg, és a Batocera-motor ugyanabban a körben átült rá — 1073 sorból 426 eltűnt, 115 jött vissza. A tools/ alá nem került, mert az org-definíció szerint az engines/ a „saját motor és könyvtár, amitől más repók függnek", és ez pontosan az. Mindkét telepítő két fájlt tölt le, a warpstore.py-t a saját repójából; egyik sem pin-el verziót, tehát egy magbeli javítás a következő telepítéssel mindkét store-hoz megérkezik.

Az élesben futó Batocera-store miatt az átültetés visszamérve: a cartridge-út, a Ports-út, a borító, a gamelist-merge, az idempotens újraszinkron, a prune és az eltávolítás ugyanazt adja, mint előtte. A régi state.json (rekordokban system, scope nélkül) ugyanúgy kulcsolódik, tehát a frissítés után az első szinkron nem tölt le és nem prune-ol semmit — ezt szándékosan a record_scope() tartaléka adja, nem migráció.

Két általánosítás, amit a második adapter kényszerített ki:

  • resolve_for_archresolve_for_host. A Batocerán csak az architektúra volt a változó; a RetroArch-nál az OS is (.dll / .dylib / _android.so). A kulcssorrend os-arch, arch, os, *.
  • Az állapot scope szerint kulcsolódik, nem ES-system szerint. A RetroArch-nál a scope a platform, nem a playlist neve — így a store átnevezése nem kulcsolja újra a telepítést.

Amit a terven felül kapott a motor: paths alparancs. A négy útvonal felderítése annyira nem magától értetődő (lásd az (a) pontot), hogy önmagában megérdemel egy kimenetet, ami azt is megmutatja, melyik válasz honnan jött — config, retroarch.cfg, vagy tartalék. Ezt írja ki a telepítő is.

Ami az 5. pontból pontosításra került: a hiányzó mag nem csak „figyelmeztetés", hanem konkrét lépés is a kimenetben (Online Updater ▸ Core Downloader ▸ VICE x64).

Amit végigmértünk: letöltés, playlist, borító (a GIF sips-szel PNG-vé konvertálva), idegen playlist-bejegyzés megőrzése, mag-felismerés, prune, eltávolítás, üres playlist törlése, Android-export _android.so-val és POSIX célútvonalakkal, végül teljes telepítés a forge-ról curl | bash-sal. A CRC32-k egyeznek a doksi 1. pontjában független módszerrel mért értékekkel.

Ami továbbra is nyitott, változatlanul: a VICE .prg-autostart és az Android adb push scoped storage kérdése. Ezt kód nem tudja megválaszolni.