A
warp-engine-batocera-storeegy WarpEngine-katalógusból húzza le a játékokat egy Batocera-dobozra, EmulationStation-metaadatokkal és borítóképekkel, és saját bejegyzést ad az áruháznak az EmulationStation menüjében. Ez csak a motor: ismeri a WarpEngine API-t, és semmit nem tud egyetlen konkrét oldalról sem. A hoszt, az áruház neve és az, hogy hova kerülnek a játékok, egy külön áruházrepóconfig.jsonfájljából jön. Az a fele, amely nem a Batoceráról szól, a warpstore, amelyen a RetroArch-motorral osztozik.
warpstore a közös mag — warpstore.py
▲
warp-engine-batocera-store ez a motor — store.py, install.sh
▲
│ config.json
│
┌─────┴─────┬───────────────┐
│ │ │
ttg-… my-… other-… áruházrepók
│ │ │
▼ ▼ ▼
teletypegames.org my.example other.example
A referenciaáruház a ttg-batocera-store.
Repó: https://git.teletypegames.org/stores/warp-engine-batocera-store Katalógusoldal: WarpEngine Közös mag: warpstore Testvérmotor: RetroArch áruházmotor Batocera: https://batocera.org
es_systems_<id>.cfg overlayként íródik ki; az indítóparancs és az emulátorlista a doboz saját rendszereiről másolódik, tehát semmi nincs az emulátorokból bedrótozva.state.json nyilvántart.python3-at szállít, pip-et nem."<áruház neve>" ▸ "Update <áruház neve>"
│
├─ GET /api/software a teljes katalógus
├─ a doboz által futtatható platformok c64 → c64, tic80 → tic80
├─ a legfrissebb nem dev kiadás amely a megfelelő assetet hordozza
├─ GET /api/download?path=<asset> .prg / .tic az áruház mappájába
├─ GET <software.imageUrl> borítókép
├─ gamelist.xml kiírása cím, leírás, szerző, kép
└─ es_systems_<id>.cfg kiírása az áruház menübejegyzése
Minden, amit az áruház telepít, egyetlen saját mappában él, a /userdata/roms/<subfolder> alatt, benne platformonként egy könyvtárral. Az EmulationStation felhasználói konfigurációjában lévő es_systems_<id>.cfg platformonként egy ES-rendszert deklarál, mindegyiket ugyanazzal a <group> értékkel — az ES pedig a csoportból egyetlen körhinta-bejegyzést csinál, benne rendszerenként egy mappával:
fő körhinta: … ▸ Teletype Games ▸ ┌ Commodore 64
├ TIC-80
├ Ports
└ Update Teletype Games
Semmi nincs az emulátorokból bedrótozva. A motor beolvassa a doboz saját /usr/share/emulationstation/es_systems.cfg fájlját, és arról a rendszerről másolja az indítóparancsot, a kiterjesztéseket, a platformot, a témát és az emulátorlistát, amelyiktől kölcsönöz — ez a fájl ehhez az image-hez készült a Batocerától, tehát pontosan azokat az emulátorokat sorolja fel, amelyek a dobozon megvannak. Az egyetlen átírt dolog a %SYSTEM%: ezt az ES az indított rendszer nevére cseréli, ami nálunk ttg-c64 lenne, olyan név, amelyet a configgen sosem hallott. Helyette a doboz saját rendszerneve kerül bele, így a mi játékunk pontosan úgy indul, ahogy a doboz indítaná.
Öt részlet, mind az EmulationStationé, és kettő közülük egy délutánba került egy valódi dobozon:
HideUniqueGroups (alapból bekapcsolva) pedig feloldja azt a csoportot, amelyben egyetlen rendszer van, hacsak nem létezik ilyen nevű rendszer. A deklarált szülő mindkettőt megoldja: visz egy <theme> értéket (alapból ports, ami minden témában megvan), és a helyén tartja a bejegyzést olyan dobozon is, amelyen csak c64-es játékok vannak.loadSystem ezt naplózza — System "..." has no games! Ignoring it. —, és kihajítja. Tehát a bejegyzés nem lehet üres polc: maga a store/ mappa, és a benne lévő frissítő az a játék, amely életben tartja. Ezért is ül az Update … közvetlenül a bejegyzésben, nem egy almappában.<dir>/* bejegyzéssel jelöl meg a fájl-gyorsítótárában, és a jelölést azelőtt teszi be, hogy beolvasná a tartalmat; onnantól minden alatta lévő útvonal, amely maga nincs gyorsítótárazva, azt válaszolja, hogy „nem létezik" (FileSystemUtil.cpp, getCacheEntry). A rendszerek szálkészletben töltődnek be, tehát egy, a rendszerek felett ülő bejegyzés véletlenszerűen elveszíti ezt a versenyt néhányukra nézve, és az ES eldobja őket ezzel: System "..." path does not exist ! — minden indításkor másik kettőre, ami pontosan úgy néz ki, mint egy témaprobléma. A store/ a testvérük, tehát nincs verseny, amit el lehetne veszíteni.Ha a dobozon már van olyan rendszer, amelynek a neve megegyezik az áruházazonosítóval — mondjuk egy nes nevű áruház esetén —, a bejegyzés <id>-store néven jön létre, és a napló ezt közli: az az overlay, amelynek a <name> értéke egy meglévő rendszerrel egyezik, azt a rendszert módosítja, nem újat ad hozzá.
Az EmulationStation a rendszer körhinta-logóját a témán keresztül oldja fel (a SystemData::getProperty("image") a témától kéri a system/logo értéket, a téma pedig a rendszer témamappájából építi fel az útvonalat — a carbon az art/logos/${system.theme}.png, majd a .svg fájlt próbálja). Felhasználói szintű felülbírálás nincs, tehát egy egyedi rendszer vagy kölcsönvesz egy témamappát, amelyben már van grafika, vagy a fájl oda kerül, ahol a témák keresik. Mindkettő támogatott:
menu.logo nélkül a bejegyzés a ports mappát kölcsönzi, ami minden témában megvan, tehát van egy ikonja — csak nem az áruházé;menu.logo esetén (útvonal vagy URL, illetve ezek listája több formátumhoz) a motor minden téma logómappájába telepíti. Ezt a mappát nem a témaelrendezések ismeretéből találja meg, hanem úgy, hogy megkeresi azoknak a rendszereknek a logóit, amelyek minden témában megvannak (ports, snes, nes, …); a fájlt <menu.theme>.<ext> néven írja ki — a menu.theme alapértéke ekkor az áruházazonosító, így a téma saját fájljaiból semmi nem íródik felül —, és minden útvonalat rögzít a state.json fájlban, így az eltávolítás pontosan ezeket veszi vissza, a névütközést pedig jelenti és kihagyja;Batocera-fintor: a rendszerimage-dzsel szállított témák a /usr/share alatt vannak, a Batocera gyökere pedig RAM-overlay — a másolat azonnal megjelenik, de újraindítás után eltűnik. A következő szinkron visszateszi; a batocera-save-overlay pedig véglegesíti. A /userdata/themes alatti témák megtartják.
A régi viselkedéshez — a játékok a doboz saját rendszerein belül, a gamelistjeibe olvasztva — állítsd az emulationstation.menu.mode értéket merge-re.
A 4.0-s motorverzió hozza az elrendezésváltást. A frissítés utáni első szinkron leszedi a régi telepítést a dobozról — ROM-ok, borítóképek, port-tartalmak, a mi csomópontjaink a doboz gamelistjeiben, az üres mappák, a Ports-bejegyzés —, majd mindent újra letölt az áruház saját mappájába. A fájlokat szándékosan nem viszi át: a katalógus kicsi, és egy félig átköltöztetett telepítés rosszabb, mint egy kicsit hosszabb szinkron. A -n az egészet megmutatja anélkül, hogy bármit végrehajtana.
A telepítő paraméterként kapja az áruházat, így bármilyen konfigurációval működik:
curl -fsSL https://git.teletypegames.org/stores/warp-engine-batocera-store/raw/branch/master/install.sh |
STORE_CONFIG=https://git.example.org/tools/my-store/raw/branch/master/config.json sh
Checkoutból, helyi konfigurációval:
scp -r warp-engine-batocera-store root@batocera:/tmp/
ssh root@batocera 'STORE_CONFIG=/tmp/my-config.json /tmp/warp-engine-batocera-store/install.sh'
Kiolvassa a store.id értéket a konfigurációból, a motort és a konfigurációt a /userdata/system/batocera-store/<id>/ könyvtárba telepíti, kiírja az <id>-store indítót, és lefuttatja az első szinkront — ez hozza létre a menübejegyzést. Az EmulationStationnek újra kell indulnia, hogy lássa.
| Környezeti változó | Jelentés |
|---|---|
STORE_CONFIG |
kötelező — az áruház config.json fájljának útvonala vagy URL-je |
BATOCERA_STORE_ROOT |
hol élnek az áruházak (alapérték /userdata/system/batocera-store) |
BATOCERA_PORTS_DIR |
Ports-mappa (alapérték /userdata/roms/ports) |
BATOCERA_PORT_NAME |
a Ports-bejegyzés neve olyan áruháznak, amely még kér egyet (alapérték: az áruház name értéke) |
ENGINE_RAW_BASE |
honnan töltse le a store.py fájlt |
WARPSTORE_RAW_BASE |
honnan töltse le a warpstore.py fájlt |
WARPSTORE_SRC |
egy helyi warpstore.py, amit letöltés helyett telepítsen |
Három fájl:
my-batocera-store/
├── config.json az áruház: URL, név, almappa, platform-leképezés
├── install.sh burkoló, amely átadja ezt a konfigurációt a motor telepítőjének
└── README.md
Az install.sh teljes egészében:
#!/bin/bash
set -euo pipefail
ENGINE_RAW_BASE="${ENGINE_RAW_BASE:-https://git.teletypegames.org/stores/warp-engine-batocera-store/raw/branch/master}"
export STORE_CONFIG="${STORE_CONFIG:-https://git.example.org/tools/my-batocera-store/raw/branch/master/config.json}"
curl -fsSL "$ENGINE_RAW_BASE/install.sh" | sh
Olyan store.id és paths.subfolder értéket válassz, amit senki más nem használ — ezek tartják távol egymástól az ugyanazon a dobozon lévő két áruházat, és az azonosító adja a menübejegyzés és az es_systems fájl nevét.
Az eszközön. „\<áruház neve>" ▸ „Update \<áruház neve>". A szkript letölt mindent, ami új, majd újraindítja az EmulationStationt. Az ES által indított szkriptnek nincs konzolja, így a kimenet az áruház könyvtárában lévő store.log fájlba megy.
SSH-n, a telepítő által kiírt indítón keresztül:
S=/userdata/system/batocera-store/example-store
$S list # kompatibilis katalógusbejegyzések; * telepítve, ^ van frissítés
$S sync # minden újdonság letöltése, a gamelistek és a menü frissítése
$S sync blessingofra # csak egy cím (soha nem takarít)
$S -n sync # szárazon futtatás
$S remove c64:demo # eltávolítás (a csupasz név is jó)
$S purge # minden eltávolítása, amit ez az áruház telepített
$S config # az érvényes konfiguráció, és hogy hol van a menübejegyzés
A globális kapcsolók az alparancs elé jönnek: $S --roms-root /tmp/roms sync.
ssh root@batocera 'curl -fsSL https://git.teletypegames.org/stores/warp-engine-batocera-store/raw/branch/master/uninstall.sh | sh'
Az eltávolítást a motor végzi, nem a shellszkript: előbb purge, majd az esetleges Ports-bejegyzés, az indító és az áruház könyvtára. Csak a state.json tudja, mely ROM-ok, borítóképek, port-tartalmak, gamelist-bejegyzések és ES-rendszerek voltak a mieink — és ha nem tud végezni (mondjuk egy felcsatolatlan ROM-gyökér miatt), semmi máshoz nem nyúl, így soha nem maradsz úgy, hogy a fájlok megvannak, de a motor nincs, amely ismerné őket.
| Környezeti változó | Jelentés |
|---|---|
STORE_ID |
melyik áruházat távolítsa el; csak több telepített áruház esetén kell |
STORE_CONFIG |
a STORE_ID alternatívája — az azonosítót a konfigurációból olvassa ki |
DRY_RUN |
1 esetén kiírja, mi tűnne el, és semmit nem távolít el |
KEEP_HOME |
1 esetén megtartja az áruház könyvtárát (állapot, katalógus-gyorsítótár, napló) az újratelepítéshez |
BATOCERA_STORE_ROOT, BATOCERA_PORTS_DIR, BATOCERA_PORT_NAME |
ugyanaz, mint a telepítőnél |
Az áruház saját elrendezésében az eltávolítás teljes: a ROM-mappa, a gamelistek és az es_systems_<id>.cfg mind eltűnnek, a doboz pedig olyan, mint volt. merge módban ezek szándékosan maradnak:
ports/.data szintén marad: egy másik áruház is tarthat ott tartalmat.<game> és <folder> csomópontjaink eltűnnek; a felhasználó saját játékai, játékszámlálói és kedvencei érintetlenül visszaíródnak. Az általunk létrehozott, üresen maradt gamelist törlődik, hiszen soha nem volt az övé.gamelist.xml.<áruházazonosító>-backup fájlok, a futás végén kilistázva: a gamelistek abban az állapotban, ahogy először találtuk őket.A szárazon futtatás fájlokat listáz, könyvtártörléseket nem: nem tudhatja, mely könyvtárak maradnak üresen.
Ha minden áruházat le akarsz szedni egy gépről — mindkét motort, a közös magot, az indítókat —, használd a warpstore szkriptjét:
curl -fsSL https://git.teletypegames.org/engines/warpstore/raw/branch/master/uninstall.sh | sh
/userdata/system/batocera-store/
├── example-store az alábbi áruház indítója
└── example/
├── store.py, warpstore.py a motor és a közös magja
├── config.json az áruház, amely kiválasztotta
└── state.json, catalog.json, store.log
/userdata/system/configs/emulationstation/es_systems_example.cfg a menübejegyzés
/userdata/roms/example/ az áruház saját ROM-mappája
├── c64/
│ ├── blessingofra-2.0.0.prg, rabbit-1.0.0.prg, …
│ ├── images/blessingofra.png, …
│ └── gamelist.xml
├── ports/
│ ├── <name>.sh, images/<name>.png, .data/<name>/
│ └── gamelist.xml
└── store/ maga a menübejegyzés
├── update.sh a szinkron, ahogy a menü indítja
└── gamelist.xml
A doboz saját rendszermappáihoz egyáltalán nem nyúl. merge módban — a régi viselkedés, amely továbbra is elérhető — ugyanez a tartalom a <roms_root>/<system>/<subfolder>/ alá kerül, az elsőként megérintett gamelist.xml fájlról gamelist.xml.<áruházazonosító>-backup másolat készül, és minden összeolvasztás csak az adott áruház almappája alatti csomópontokat írja újra, így két áruház osztozhat egy gamelisten.
config.json az áruház könyvtárában; a sablon a repóban lévő config.example.json.
| Kulcs | Alapérték | Jelentés |
|---|---|---|
store.id |
warp |
slug: ez adja az áruház könyvtárának nevét, a naplóelőtagot és a gamelist-mentés nevét |
store.name |
WarpEngine Store |
a menübejegyzés megjelenített neve |
store.base_url |
— | a WarpEngine-hoszt |
store.api.catalog |
/api/software |
katalógusvégpont, ha a motor máshova van beillesztve |
store.api.download |
/api/download |
letöltési végpont |
paths.roms_root |
/userdata/roms |
hol élnek a rendszerek |
paths.subfolder |
warp |
az áruház saját mappája a roms_root alatt (merge módban: az almappája minden rendszeren belül) |
emulationstation.menu.mode |
system |
system: az áruház saját menübejegyzést kap. merge: a doboz saját rendszereibe telepít |
emulationstation.menu.name |
null → store.name |
a menübejegyzés felirata |
emulationstation.menu.theme |
null → logóval az áruházazonosító, anélkül a ports |
az a témamappa, ahonnan a bejegyzés a logóját veszi, és a logófájl neve |
emulationstation.menu.logo |
null |
a bejegyzés saját logója: útvonal vagy URL, illetve ezek listája; minden téma logómappájába települ |
emulationstation.menu.updater |
true |
egy Update … bejegyzés a menüben, amely lefuttatja a szinkront |
emulationstation.menu.labels |
{} |
rendszerenkénti mappafeliratok; a fel nem sorolt rendszer megtartja a doboz saját <fullname> értékét |
emulationstation.ports_entry |
null → false system módban |
írjon ki egy Ports-bejegyzést is, amely a szinkront futtatja |
emulationstation.folder_name |
WarpEngine Store |
csak merge módban: a mappánk megjelenített neve a doboz gamelistjében |
emulationstation.restart |
true |
változás után indítsa újra az EmulationStationt |
emulationstation.config_dir |
null → /userdata/system/configs/emulationstation |
hol tartja az ES a felhasználói konfigurációját |
catalog.statuses |
["released", "archived"] |
mely katalógus-status értékeket telepítse |
catalog.owner_id |
null |
szűkítés egyetlen kiadóra (/api/software?owner_id=) |
catalog.only / catalog.exclude |
[] |
szoftvernév-engedélyező / -tiltó lista |
platforms |
c64, tic80 | platform → rendszer, asset típusa, kiterjesztés, engedélyezve |
behavior.prune |
true |
távolítsa el azokat a játékokat, amelyek kikerültek a katalógusból vagy a szűrőkből |
behavior.timeout |
30 |
HTTP-időkorlát másodpercben |
behavior.insecure |
false |
TLS-ellenőrzés kihagyása (saját üzemeltetésű tesztpéldányokhoz) |
Egy platform akkor telepíthető, ha a kiadási assetje olyan fájl, amelyet a dobozon meglévő valamelyik rendszer közvetlenül el tud indítani — a „meglévő" itt azt jelenti, hogy szerepel a doboz saját es_systems.cfg fájljában, tehát ebbe az image-be beleépült az emulátor (ROM-mappa attól még lehet ott, hogy nincs mögötte semmi):
| Katalógusplatform | Asset típusa | Batocera-rendszer | Kiterjesztés |
|---|---|---|---|
c64 |
cartridge |
c64 (VICE) |
.prg |
tic80 |
cartridge |
tic80 |
.tic |
A többi (ebitengine, love, godot, bevy, phaser) html fájlt és operációs rendszerenkénti zipeket szállít, nem olyan ROM-ot, amit egy rendszer elindít, ezért alapból nincsenek leképezve. Egy hozzáadása konfigurációs szerkesztés:
"platforms": { "godot": { "system": "godot", "kind": "linux_x64", "ext": ".zip", "enabled": true } }
Az EmulationStation memóriában tartja a gamelisteket, és kilépéskor visszaírja őket, tehát egy futása közben végzett összeolvasztás felülíródhat. A menüből indítva (sync-from-es) a motor előbb letölt, majd az összeolvasztást egy leválasztott apply-gamelists --wait-pid <es-pid> gyerekfolyamatra bízza, amely megvárja a régi ES-folyamat halálát. SSH-ról, futó ES nélkül az összeolvasztás soron belül történik. Az es_systems overlaynek mindebből semmi nem kell: az ES beolvassa, és soha nem írja.
Az újrafuttatás idempotens: a meglévő fájlokhoz nem nyúl, egy kiadásemelés (1.1 → 2.0.0) törli a régi assetet, mielőtt lehúzná az újat, és azt a gamelist- vagy es_systems-fájlt, amelynek a tartalma nem változott, egyáltalán nem írja újra. A letöltések a GET /api/download végponton mennek, nem a /file/ útvonalon, így beleszámítanak a katalógusstatisztikába.
A szinkron megszakad, ha nem tudja megállapítani, mely rendszerek vannak a dobozon — egy olvashatatlan es_systems.cfg egy felcsatolatlan /userdata/roms mögött különben úgy nézne ki, mintha „már semmi nem kompatibilis", és letakarítana minden telepített játékot.
A state.json rögzíti, mit telepített az áruház, <rendszer>:<név> kulccsal, így ugyanaz a szoftvernév két rendszeren két külön bejegyzés marad. A layout megmondja, melyik könyvtárelrendezéshez tartoznak ezek a rekordok: ebből tudja az áruház saját mappájára való átállás, hogy egyszer kell lefutnia — eltávolítja a régi telepítést, a szinkron pedig újra letölti az új helyre. A szétválasztás előtti ttg-store-ból származó version: 1 fájlt az első futáskor migrálja.