Choose the asset that matches the machine
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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.statuses` | `["released", "archived"]` | catalog `status` values to install |
|
||||||
| `catalog.owner_id` | `null` | restrict to one publisher (`/api/software?owner_id=`) |
|
| `catalog.owner_id` | `null` | restrict to one publisher (`/api/software?owner_id=`) |
|
||||||
| `catalog.only` / `catalog.exclude` | `[]` | software-name allow / deny lists |
|
| `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.prune` | `true` | remove games that left the catalog or the filters |
|
||||||
| `behavior.timeout` | `30` | HTTP timeout, seconds |
|
| `behavior.timeout` | `30` | HTTP timeout, seconds |
|
||||||
| `behavior.insecure` | `false` | skip TLS verification (self-hosted test instances) |
|
| `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 } }
|
"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 —
|
A platform is also skipped when the box has no ROM folder for its system —
|
||||||
`list` reports that as `skipped <name>: no '<system>' ROM folder`.
|
`list` reports that as `skipped <name>: no '<system>' ROM folder`.
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ Standard library only — Batocera ships python3 but no pip packages.
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -281,6 +282,37 @@ def fetch_catalog(cfg, use_cache=False):
|
|||||||
die(f"cannot fetch catalog from {url}: {exc}")
|
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):
|
def asset_basename(path):
|
||||||
"""`/file/blessingofra-2.0.0.prg` -> `blessingofra-2.0.0.prg`."""
|
"""`/file/blessingofra-2.0.0.prg` -> `blessingofra-2.0.0.prg`."""
|
||||||
return os.path.basename(path.rstrip("/"))
|
return os.path.basename(path.rstrip("/"))
|
||||||
@@ -317,8 +349,9 @@ def pick_release(entry, kind, ext):
|
|||||||
return None, None
|
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."""
|
"""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 []}
|
statuses = {s.lower() for s in cfg["catalog"].get("statuses") or []}
|
||||||
only = {n.lower() for n in cfg["catalog"].get("only") or []}
|
only = {n.lower() for n in cfg["catalog"].get("only") or []}
|
||||||
exclude = {n.lower() for n in cfg["catalog"].get("exclude") 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:
|
if name.lower() in exclude:
|
||||||
continue
|
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:
|
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
|
continue
|
||||||
|
|
||||||
games.append(
|
games.append(
|
||||||
@@ -793,7 +832,9 @@ def cmd_apply_gamelists(cfg, args):
|
|||||||
def cmd_list(cfg, args):
|
def cmd_list(cfg, args):
|
||||||
systems = detect_systems(cfg)
|
systems = detect_systems(cfg)
|
||||||
catalog = fetch_catalog(cfg, use_cache=True)
|
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"]
|
installed = load_state()["installed"]
|
||||||
|
|
||||||
if not games:
|
if not games:
|
||||||
@@ -844,7 +885,9 @@ def cmd_config(cfg, args):
|
|||||||
write_json(args.config, DEFAULT_CONFIG)
|
write_json(args.config, DEFAULT_CONFIG)
|
||||||
log(f"wrote default config to {args.config}")
|
log(f"wrote default config to {args.config}")
|
||||||
return 0
|
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
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user