---
title: warpstore
url: 'https://wiki.teletypegames.org/hu/engines/warpstore'
markdown: 'https://wiki.teletypegames.org/hu/engines/warpstore.md'
lang: hu
date: '2026-08-17'
description: 'Egy áruházmotor beolvas egy WarpEngine-katalógust, és kiírja valamelyik gazdaplatform saját játékkönyvtár-formátumát. Csak ez az utolsó lépés tér el platformonként. Az API-val való beszélgetés, a kiadás kiválasztása, a gép illesztése és annak nyilvántartása, hogy mi hova került, mindenhol ugyanaz …'
taxonomy:
  tag:
    - engine
---

# warpstore

> Egy **áruházmotor** beolvas egy [WarpEngine](https://wiki.teletypegames.org/hu/engines/warp-engine)-katalógust, és kiírja valamelyik gazdaplatform saját játékkönyvtár-formátumát. Csak ez az utolsó lépés tér el platformonként. Az API-val való beszélgetés, a kiadás kiválasztása, a gép illesztése és annak nyilvántartása, hogy mi hova került, mindenhol ugyanaz a munka — a `warpstore` pontosan ez a munka, egyetlen fájlban, minden áruházmotor közösen használja.

```
                    ┌──────────────────────────────┐
                    │  warpstore.py                │
                    │  catalog · releases · host   │
                    │  state · config · HTTP       │
                    └──────────────┬───────────────┘
                    ┌──────────────┴───────────────┐
                    ▼                              ▼
    warp-engine-batocera-store        warp-engine-retroarch-store
    ROM-mappák + gamelist.xml         .lpl lejátszási listák + bélyegképek
                    │                              │
                    ▼                              ▼
             EmulationStation                  RetroArch
```

**Repó:** <https://git.teletypegames.org/engines/warpstore>**Katalógusoldal:** [WarpEngine](https://wiki.teletypegames.org/hu/engines/warp-engine)**Használói:** [Batocera áruházmotor](https://wiki.teletypegames.org/hu/stores/warp-engine-batocera-store) · [RetroArch áruházmotor](https://wiki.teletypegames.org/hu/stores/warp-engine-retroarch-store)

# Miért létezik

A Batocera-áruház 1073 sor volt, és ennek csak nagyjából a harmada szólt a Batoceráról. A RetroArch-áruháznak a másik kétharmadra volt szüksége, ezért a közös rész kiköltözött — már egy második fogyasztóval a kézben, hogy a vágás ott legyen, ahol a két motor valóban eltér.

Egyetlen fájl, csak a Python 3 szabványkönyvtárával: a gazdagépek a Batocera-doboztól (`python3`, `pip` nélkül) az asztali gépig terjednek, és egyiket sem szabad rávenni bármi telepítésére. Egy motor telepítője a `warpstore.py` fájlt az adapterszkript mellé teszi, az adapter pedig `import warpstore`-t hív.

# Mit ad hozzá egy adapter

1. **`DEFAULT_CONFIG`** — hol vannak a dolgok ezen a gazdagépen, és melyik katalógusplatform mire képződik le.
2. **`accept(spec, sw)`** — mely katalógusbejegyzéseket tudja futtatni ez a gazdagép, és hogyan csoportosulnak.
3. **A kód, amely kiírja a gazdagép könyvtárformátumát.**

```python
import warpstore as ws

def accept(spec, sw):
    """Return (scope, extras), or raise ws.Skip(reason) to reject the title."""
    if spec["system"] not in available_systems:
        raise ws.Skip(f"no '{spec['system']}' ROM folder on this box")
    return spec["system"], {"system": spec["system"]}

cfg = ws.load_config(path, DEFAULT_CONFIG, required=("paths.subfolder",))
ws.init(path)
games, skipped = ws.select_games(cfg, ws.fetch_catalog(cfg, use_cache=True), accept)
```

A `scope` az, ahogyan a gazdagép csoportosítja a könyvtárát — egy Batocera-rendszer, egy RetroArch-lejátszási lista. A `state.json` kulcsa `<scope>:<name>`, így ugyanaz a szoftvernév két scope-ban két külön bejegyzés marad.

# Mi van benne

| Terület | Függvények |
|---|---|
| Naplózás | `set_tag`, `set_verbose`, `log`, `debug`, `die` |
| Fájlok | `load_json`, `write_json`, `write_atomic`, `within`, `prune_empty_dirs` |
| Konfiguráció | `deep_merge`, `load_config` |
| Áruház-könyvtár | `init`, majd `HOME`, `CONFIG_PATH`, `STATE_PATH`, `CATALOG_CACHE` |
| HTTP | `http_get`, `http_download`, `api_url`, `download_url`, `user_agent` |
| Gazdagép | `machine`, `host_os`, `host`, `resolve_for_host` |
| Katalógus | `fetch_catalog`, `select_games`, `survey_catalog`, `pick_release`, `asset_basename`, `download_image` |
| Állapot | `load_state`, `save_state`, `game_key`, `record_scope`, `records_by_scope`, `match_keys`, `limit_to_names` |

Öt közülük olyan döntést hordoz, amit érdemes ismerni.

**`select_games` kontra `survey_catalog`** — a `select_games` azt adja vissza, amit az áruház telepíteni tud. A `survey_catalog` ezen felül címenként egy rekordot ad azokról is, amelyeket ez a gép nem tud telepíteni, egy `reason` kóddal (`platform_off`, `host_asset`, `no_asset`, `vetoed`, az 1.5.0-tól `gated` is — olyan cím, amelyet a host árul, és amelyet egy névtelen áruház nem tud letölteni; ilyenkor a rekordban ott a `purchase_url`) és egy mondattal. Egy kliens ezeket kiszürkítheti elrejtés helyett — a WarpEngine Client a saját portjában pontosan ezt teszi. Azok a címek, amelyeket az áruház *maga dönt úgy*, hogy nem kínál, egyik listában sincsenek benne — egy rossz státusz vagy egy kizárólista szerkesztői döntés, nem a gép korlátja.

**`fetch_catalog`** — a hostot kéri meg, hogy maga szűrjön `owner_id`-re és a `catalog.statuses`-ra (`status=released,archived`), és ha a host mégis lapozva válaszol, követi a `nextPage`-et — így egy host oldali lapméret sosem vághatja meg csendben a szinkront. Mindkettő csak rövidebb letöltés: a `survey_catalog` ugyanezeket a szűrőket újra alkalmazza, így egy paramétert nem ismerő host semmin nem változtat.

**`download_url`** — a `GET /api/download` végpontra mutat, nem a `/file/`-ra, így a host minden letöltést lát. Hogy beszámolja-e, az a host döntése: a teletypegames.org szándékosan kihagyja a letöltésszámból a `warpstore/*` user agentet (`user_agent`, `ignored_user_agents`), így az áruházak telepítései nem jelennek meg a statisztikában.

**`resolve_for_host(value, host)`** — olyan konfigurációs érték, amely függhet a géptől. Egy sima sztring mindenhol ugyanaz — ilyen a `cartridge`: adat egy emulátornak. Egyébként az érték egy mapping, és a legspecifikusabb kulcs nyer:

```python
{"linux-aarch64": …, "aarch64": …, "linux": …, "*": …}
```

Ha nincs illeszkedő kulcs és nincs `*` sem, az eredmény `None`, és a hívó kihagyottként jelenti a címet, ahelyett hogy olyasmit telepítene, ami nem tud futni. Ez a Batocera-motor `resolve_for_arch` függvényének általánosítása: a Batocera mindig csak `linux` volt, de egy RetroArch-gazdagép lehet Windows, macOS vagy Android — és ez dönti el, hogy hogyan hívják a libretro core fájlját (`.dll`, `.dylib`, `_android.so`).

**`within(path, root)`** — minden törlést ez őriz. Egy áruház csak abból a részfából távolíthat el fájlokat, amely az övé, soha nem a felhasználó könyvtárából és soha nem egy másik áruházéból.

**`prune_empty_dirs(dirs, root)`** — ugyanennek a szabálynak az eltávolítási oldala. Csak üres könyvtárak tűnnek el, és csak a `root` belsejében: egyetlen kósza fájl megtartja a könyvtárat. Ez teszi lehetővé, hogy a `purge` (és így az `uninstall.sh`) úgy hagyja a gazdagépet, ahogy volt.

**`write_atomic`** — semmi nem íródik a helyén. Egy szinkron közben megszakított áruház különben félig megírt lejátszási listát vagy gamelistet hagyna maga után, ami rosszabb, mint egy régi.

# Minden áruház eltávolítása egy gépről

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

Ezzel az egész keretrendszer lehúzható anélkül, hogy fejben kellene tartani, mi van telepítve: megkeresi az összes áruház-könyvtárat az ismert gyökerek alatt, mindegyik áruház **saját motorjával** takaríttatja el, amit telepített, majd törli az áruházat, az indítóját és a motorfájlokat. Egyetlen áruházhoz inkább az adott motor saját `uninstall.sh` szkriptjét használd — az ismeri a gazdagép extráit is, például egy Batocera Ports-bejegyzést.

| Környezeti változó | Jelentés |
|---|---|
| `DRY_RUN` | `1` esetén kiírja, mi tűnne el, és semmit nem távolít el |
| `KEEP_GAMES` | `1` esetén csak a szkripteket törli, a játékokat megtartja |
| `FORCE` | `1` esetén futó RetroArch mellett is takarít |
| `BATOCERA_STORE_ROOT`, `BATOCERA_PORTS_DIR` | hol keressen egy Batocera-dobozon |
| `STORE_ROOT`, `BIN_DIR` | hol keressen egy asztali gépen |

Ha egy motor nem tud végezni (fut a RetroArch, nincs felcsatolva a ROM-gyökér), a futás ott megáll, ahelyett hogy törölné azt a motort, amely tudja, mit telepített.

A szkript POSIX `sh`, nem bash, így a `| sh` ott is működik, ahol a `/bin/sh` a dash. A motorok saját telepítői ugyanígy.

# Állapot

`state.json`, 2-es verzió, `<scope>:<name>` kulccsal:

```json
{
  "version": 2,
  "installed": {
    "c64:blessingofra": { "name": "blessingofra", "scope": "c64", "asset": "blessingofra-2.0.0.prg", "…": "…" }
  }
}
```

A `record_scope()` visszaesik a rekord `system` mezőjére, mert a Batocera-áruház ezt írta, mielőtt ez a modul létezett. Ott a kettő ugyanaz a sztring volt, így egy telepített doboz migráció nélkül működik tovább, és semmit nem tölt le újra. Egy `version: 1` fájlt, amelynek a kulcsa a csupasz szoftvernév, az első futáskor újrakulcsoz.

# Verziózás

A motorok onnan húzzák le a `warpstore.py` fájlt, ahová a telepítőjük `WARPSTORE_RAW_BASE`-e mutat — alapból a `master`-ről, így egy itteni változás a következő telepítéskor eljut az áruházhoz, és pont ezért kell a felületet kicsin tartani. Egy áruházrepó rögzítheti is: a [TTG Batocera Store](https://wiki.teletypegames.org/hu/stores/ttg-batocera-store) a motorját és a `warpstore`-t is rögzített commitról húzza, és kiadás után átírja őket. A `--version` mindkét motoron kiírja mindkét számot, például `3.1.0 (warpstore 1.2.0)`.

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Motorok](https://wiki.teletypegames.org/hu/engines.md)
- Previous: [WarpEngine](https://wiki.teletypegames.org/hu/engines/warp-engine.md)
