Files
devarea/RETROARCH_STORE.md
T
mr.zeroandClaude Opus 5 24ece3cb35 Move the stores into a stores org, and record what it took
The `tools` definition — "one person installs and runs it for themselves" — stopped
describing the store engines: they are products other people can build on. So the
four store repositories left `tools` for a `stores` org of their own, and the org
map, the clone list, the wiki table and the ecosystem plan follow. `check-repos.sh`
is green at 49.

Worth writing down for the next org change: `tea` has no transfer subcommand, but
`tea api` makes authenticated calls with tea's own credentials, which is enough for
all of it — POST /orgs, POST /orgs/{org}/teams, PUT /teams/{id}/members/{user},
POST /repos/{owner}/{repo}/transfer. And the transfer keeps Gitea's redirect: the
old `tools/...` raw URLs answer 301, so the installers on already-configured boxes
did not break. That is why this was a transfer and not a migrate-and-delete.

`warpstore` stayed in `engines`: it is a library other repos depend on, which is
what that org is for. The launcher gap moved out of the tools chapter of
ECOSYSTEM_PLAN into the new stores one, where it belongs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 08:33:36 +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), stores/warp-engine-retroarch-store (motor), stores/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:

stores/warp-engine-retroarch-store     a motor: retroarch_store.py, install.sh
        ▲
        │ config.json
stores/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.