The games were scattered across the box's own systems — a folder inside
c64, another inside tic80, the launchers inside Ports, and the sync a
fourth entry somewhere else. There was no one place where the store
appeared, and living in the box's folders is what forced the gamelist
merging, the backups and every "never step outside your subfolder" check.
Now the store gets a folder of its own, /userdata/roms/<subfolder>, with a
directory per platform, and an es_systems_<id>.cfg overlay declares one ES
system per platform under a shared <group> — which EmulationStation draws
as a single carousel entry holding a folder per system. The gamelists are
ours, the box's systems are untouched, and the sync is startable from
inside the entry: Store -> "Update <store>".
Nothing about emulators is hardcoded. The launch command, the extensions,
the platform, the theme and the emulator list are copied off the box's own
es_systems.cfg — a file Batocera generated for this image, so it lists
exactly what got built into it. %SYSTEM% is resolved to the source system
name on the way, since ES would otherwise substitute ttg-c64, which
configgen has never heard of.
The old behaviour is emulationstation.menu.mode: "merge", and switching
either way migrates: the layout the files are in is recorded in
state.json, and a mismatch removes the old one and downloads again rather
than shuffling files half-way. purge follows that record too, so an
uninstall after an upgrade whose first sync never ran still finds the
games where they actually are.
Every path now goes through a Layout, so the two modes cannot drift apart.
Also: a gamelist or es_systems file whose content has not changed is not
rewritten at all, and the tags ES owns — favourite, playcount, lastplayed
— are carried over onto the nodes we rewrite, which merge mode used to
lose.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The store could only offer what an emulator boots, which meant cartridges
and nothing else — a little under half the catalog. Batocera runs Linux
and its Ports system takes .sh launchers, so a native build can be
installed too; it just is not a ROM.
`install: "port"` unpacks the zip, finds the executable and writes a
launcher that cd's into its directory first, because a game loads assets
relative to the working directory. Python's extraction drops the
executable bit, so it is restored from the mode the archive records.
The payload lives under ports/.data/<subfolder>/, whose leading dot is
what hides it from EmulationStation, while the launchers sit in a normal
subfolder that gets scanned like any other system's. Both carry the store
subfolder, so an uninstall or prune cannot reach another store's files —
verified by pointing a state entry at a foreign namespace and watching it
be refused.
Verified end to end on aarch64 against the live catalog: bevydemo
installs as a port with an executable launcher and binary, re-syncs
without re-downloading 29 MB, and uninstalls taking its 106 MB payload
with it. `install` defaults to rom, so existing configs are unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>