From b6d71aea1cf2cb68ebb6f0e93a9c730c879c8611 Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Mon, 17 Aug 2026 07:55:17 +0200 Subject: [PATCH] Choose the asset that matches the machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batocera runs on x86_64 mini-PCs, on the Raspberry Pi and on the ARM handhelds alike. A cartridge is data for an emulator and runs anywhere, which is why this store has been portable so far — but a native build only starts on the architecture it was built for, and an x86_64 binary installed on a Pi is worse than one never offered. `kind` and `ext` may now be a map from architecture to value, with `*` as a fallback; a plain string still means "the same everywhere", so existing configs are untouched. The detected architecture is reported at the top of `list` and as `detected_arch` in `config`, and a title with no asset for the running machine is skipped with a reason that names it. Verified on aarch64 against the live catalog: the cartridge config behaves exactly as before, a bevy entry mapped per architecture pulls bevydemo-1.0.0-linux-arm64.zip, and the binary inside is an AArch64 ELF. A map without an entry for the machine skips and says so. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 28 +++++++++++++++++++++++++++- store.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2feaafa..a67c6e8 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ counts, favourites and scraped media, and two stores can share one gamelist. | `catalog.statuses` | `["released", "archived"]` | catalog `status` values to install | | `catalog.owner_id` | `null` | restrict to one publisher (`/api/software?owner_id=`) | | `catalog.only` / `catalog.exclude` | `[]` | software-name allow / deny lists | -| `platforms` | c64, tic80 | platform → system, asset kind, extension, enabled | +| `platforms` | c64, tic80 | platform → system, asset kind, extension, enabled; the kind may be per-architecture | | `behavior.prune` | `true` | remove games that left the catalog or the filters | | `behavior.timeout` | `30` | HTTP timeout, seconds | | `behavior.insecure` | `false` | skip TLS verification (self-hosted test instances) | @@ -172,6 +172,32 @@ launches, so they are not mapped by default. Adding one is a config edit: "platforms": { "godot": { "system": "godot", "kind": "linux_x64", "ext": ".zip", "enabled": true } } ``` +### Per-architecture assets + +Batocera runs on x86_64 mini-PCs, on the Raspberry Pi and on the ARM handhelds +alike, and a native binary only starts on the architecture it was built for — +an x86_64 build installs on a Pi and then does nothing. A cartridge is data for +an emulator, so it is architecture-independent; a native build is not. + +Where the right asset depends on the machine, `kind` (and `ext`, if it differs) +can be a map instead of a string. `*` is the fallback: + +```json +"platforms": { + "bevy": { + "system": "bevy", + "kind": { "x86_64": "linux_x64", "aarch64": "linux_arm64" }, + "ext": ".zip", + "enabled": true + } +} +``` + +The engine reports the architecture it detected — `x86_64`, `aarch64`, `armhf`, +`x86` — at the top of `list`, and `config` prints it as `detected_arch`. With no +entry for the running machine and no `*`, the title is skipped and says so: +`skipped bevydemo: bevy has no asset kind for aarch64`. + A platform is also skipped when the box has no ROM folder for its system — `list` reports that as `skipped : no '' ROM folder`. diff --git a/store.py b/store.py index eb70e28..9894127 100755 --- a/store.py +++ b/store.py @@ -16,6 +16,7 @@ Standard library only — Batocera ships python3 but no pip packages. import argparse import json import os +import platform import shutil import subprocess import sys @@ -281,6 +282,37 @@ def fetch_catalog(cfg, use_cache=False): die(f"cannot fetch catalog from {url}: {exc}") +def machine(): + """A gep architekturaja, `uname -m` szerint, normalizalt neven. + + A gazdaplatform ugyanaz (Batocera), a gep viszont nem: x86_64 mini-PC, + Raspberry Pi es kezikonzol egyaránt. Egy x86_64 binaris ARM-on feltelepul, + de nem indul el, ezért a kiadasvalasztasnak tudnia kell, min fut. + """ + m = (platform.machine() or "").lower() + if m in ("x86_64", "amd64"): + return "x86_64" + if m in ("aarch64", "arm64"): + return "aarch64" + if m.startswith("armv") or m == "arm": + return "armhf" + if m in ("i386", "i486", "i586", "i686"): + return "x86" + return m or "unknown" + + +def resolve_for_arch(value, arch): + """Egy config-erteket architekturara oldunk fel. + + A `kind` es az `ext` lehet egyszeru string (minden gepen ugyanaz — igy + mukodik a cartridge), vagy architektura -> ertek leképezes. Az utóbbinal + a `"*"` kulcs szolgal tartaleknak. + """ + if not isinstance(value, dict): + return value + return value.get(arch, value.get("*")) + + def asset_basename(path): """`/file/blessingofra-2.0.0.prg` -> `blessingofra-2.0.0.prg`.""" return os.path.basename(path.rstrip("/")) @@ -317,8 +349,9 @@ def pick_release(entry, kind, ext): return None, None -def select_games(cfg, catalog, available_systems): +def select_games(cfg, catalog, available_systems, arch=None): """Catalog -> list of installable game dicts, plus a list of skip reasons.""" + arch = arch or machine() statuses = {s.lower() for s in cfg["catalog"].get("statuses") or []} only = {n.lower() for n in cfg["catalog"].get("only") or []} exclude = {n.lower() for n in cfg["catalog"].get("exclude") or []} @@ -344,9 +377,15 @@ def select_games(cfg, catalog, available_systems): if name.lower() in exclude: continue - release, file_name = pick_release(entry, spec["kind"], spec.get("ext", "")) + kind = resolve_for_arch(spec["kind"], arch) + if not kind: + skipped.append(f"{name}: {sw.get('platform')} has no asset kind for {arch}") + continue + ext = resolve_for_arch(spec.get("ext", ""), arch) or "" + + release, file_name = pick_release(entry, kind, ext) if not release: - skipped.append(f"{name}: no '{spec['kind']}' asset in any release") + skipped.append(f"{name}: no '{kind}' asset in any release") continue games.append( @@ -793,7 +832,9 @@ def cmd_apply_gamelists(cfg, args): def cmd_list(cfg, args): systems = detect_systems(cfg) catalog = fetch_catalog(cfg, use_cache=True) - games, skipped = select_games(cfg, catalog, systems) + arch = machine() + log(f"architecture: {arch}") + games, skipped = select_games(cfg, catalog, systems, arch) installed = load_state()["installed"] if not games: @@ -844,7 +885,9 @@ def cmd_config(cfg, args): write_json(args.config, DEFAULT_CONFIG) log(f"wrote default config to {args.config}") return 0 - print(json.dumps(cfg, indent=2, ensure_ascii=False)) + # A felismert architektura nem a configbol jon, de az asset-valasztast + # eldonti, ezert itt is lathatonak kell lennie. + print(json.dumps({"detected_arch": machine(), **cfg}, indent=2, ensure_ascii=False)) return 0