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

591 lines
25 KiB
Markdown

# 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`](https://git.teletypegames.org/engines/warpstore) (közös mag),
> [`tools/warp-engine-retroarch-store`](https://git.teletypegames.org/tools/warp-engine-retroarch-store) (motor),
> [`tools/ttg-retroarch-store`](https://git.teletypegames.org/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](https://wiki.teletypegames.org/engines/warpstore),
> [motor](https://wiki.teletypegames.org/others/warp-engine-retroarch-store),
> [store](https://wiki.teletypegames.org/others/ttg-retroarch-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:
```json
{
"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:
```sh
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):
```json
{
"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:
```json
"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étezik** → `default_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:
```sh
# 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.0``2.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:
```json
{
"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:
```sh
# 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_arch` → `resolve_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.