EN /HU | Belépés

WarpEngine Batocera Store

A warp-engine-batocera-store egy 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.json fájljából jön. Az a fele, amely nem a Batoceráról szól, a warpstore, amelyen a RetroArch-motorral osztozik.

TXT
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

Amit kapsz

  • Saját menübejegyzés — egy körhinta-bejegyzés az áruház nevével, benne platformonként egy mappával. 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.
  • Oldalfüggetlen — bármelyik hoszt, amely beilleszti a WarpEngine-t, kaphat áruházat; csak a konfiguráció változik.
  • Dobozonként több áruház — mindegyiké egy ROM-mappa, egy menübejegyzés és a saját állapota, így az egyik áruház takarítása nem érheti el a másik játékait.
  • A dobozhoz nem nyúl — a játékok az áruház mappájában élnek, nem a doboz rendszereiben. Ezen kívül semmihez nem lehet hozzányúlni, ezen belül pedig csak az törlődik, amit a state.json nyilvántart.
  • Biztonságos az EmulationStation mellett — az ES-ből indított szinkron a gamelist írását az ES kilépéséig halasztja, és minden újraíráskor átviszi az ES tulajdonában lévő címkéket (kedvencek, játékszámlálók).
  • Nulla függőség — csak a Python 3 szabványkönyvtára. A Batocera python3-at szállít, pip-et nem.

Hogyan működik

TXT
"<á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

Az áruház saját 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:

TXT
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:

  • A menübejegyzés deklarált, nem következtetett. Az ES magától kitalálná a csoportszülőt, de akkor a témamappája a csoport neve lenne, amivel egyetlen téma sem rendelkezik — a 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.
  • Azt a rendszert, amelynek nincs saját játéka, eldobja, nem csak elrejti: a 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.
  • A bejegyzés nem lehet a rendszerek feletti könyvtár. Az ES egy bejárt könyvtárat <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.
  • Az üres rendszer nem jelenik meg, így a bejegyzésben soha nincs zsákutca.
  • Egy új rendszerhez ES-újraindítás kell, amit a motor elvégez az olyan szinkron után, amely hozzáadott egyet.

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á.

A logója

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;
  • ahová nem ér el, ott a téma az áruház nevét mutatja szövegként, ami az EmulationStation saját tartalékmegoldása hiányzó logókép esetén.

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.


Menübejegyzés előtt telepített áruház frissítése

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.


Egy áruház telepítése

A telepítő paraméterként kapja az áruházat, így bármilyen konfigurációval működik:

SH
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:

SH
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

Áruházrepó írása

Három fájl:

TXT
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:

SH
#!/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.


Használat

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:

SH
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.


Eltávolítás

SH
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:

  • a felhasználó által az áruház almappájába tett ROM-ok — egy könyvtár csak akkor tűnik el, ha üres, tehát egyetlen kósza fájl megtartja. A ports/.data szintén marad: egy másik áruház is tarthat ott tartalmat.
  • a gamelistek — ezek a dobozéi. A mi <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é.
  • a 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:

SH
curl -fsSL https://git.teletypegames.org/engines/warpstore/raw/branch/master/uninstall.sh | sh

Hova kerülnek a dolgok

TXT
/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.


Konfiguráció

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 nullstore.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 nullfalse 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)

Platform-leképezés

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:

JSON
"platforms": { "godot": { "system": "godot", "kind": "linux_x64", "ext": ".zip", "enabled": true } }

Hogyan marad biztonságos a szinkron az EmulationStation mellett

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.12.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.

Állapot

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.