Let a store bring its own carousel logo

The menu entry borrowed the Ports icon, which is what "give it a theme
folder that already has artwork" buys you. EmulationStation offers nothing
better on its own: a system's logo is resolved through the theme
(SystemData::getProperty("image") -> theme's system/logo, path built from
the system's theme folder), and there is no user-level override anywhere —
not in the settings, not in the ROM folder, not in the binary.

So `menu.logo` installs the file where the themes look. The logo folder is
found rather than assumed: it is the folder holding the most logos of
systems every theme has (ports, snes, nes, …), because themes keep them in
art/logos, _inc/systems/logos, _art/Colorlogos and worse. On the box that
found 7 of 9 themes; the other two do not use ${system.theme} at all, and
there ES falls back to drawing the store's name as text, which is a fine
answer.

Two safeguards, since this writes outside the store's own folder for the
first time: the file is named after `menu.theme`, which now defaults to the
store id so no theme's own artwork can be overwritten, and every path
installed is recorded in state.json — an uninstall removes exactly those,
a pre-existing file of that name is reported and left alone.

Themes that ship with the system image are under /usr/share, and Batocera's
root is a RAM overlay: the copy works but does not survive a reboot. The
sync puts it back, and the log says so, naming batocera-save-overlay.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-23 21:46:08 +02:00
co-authored by Claude Opus 5
parent 032c4f0cfb
commit ce210edee1
3 changed files with 241 additions and 10 deletions
+39 -3
View File
@@ -39,7 +39,8 @@ The reference store is
├─ GET /api/download?path=<asset> .prg / .tic into the store's folder
├─ GET <software.imageUrl> box art
├─ write gamelist.xml title, desc, author, image
─ write es_systems_<id>.cfg the store's menu entry
─ write es_systems_<id>.cfg the store's menu entry
└─ install the logo into the themes so the entry is not borrowing one
```
Python 3 standard library only — Batocera ships python3 and no pip.
@@ -101,6 +102,39 @@ store called `nes`, say — the entry is called `<id>-store` instead, and the lo
says so: an overlay whose `<name>` matches an existing system would *modify that
system* rather than add one.
### Its logo
EmulationStation resolves a system's carousel logo through the **theme**:
`SystemData::getProperty("image")` asks the theme for `system/logo`, and the
theme builds the path from the system's theme folder — carbon, for instance,
tries `art/logos/${system.theme}.png`, then `.svg`. There is no user-level
override anywhere, so a custom system has exactly two options: borrow a theme
folder that already has artwork, or put a file where the themes look.
Both are supported. With no `menu.logo`, the entry borrows `ports` — every theme
has that one, so the entry has *an* icon, just not the store's. Set `menu.logo`
to an image (a path or a URL, or a list of them for several formats) and the
engine installs it instead:
- it finds each theme's logo folder by looking for the logos of systems every
theme has (`ports`, `snes`, `nes`, …) rather than by knowing the layouts —
themes keep them in `art/logos`, `_inc/systems/logos`, `_art/Colorlogos`, and
worse;
- the file is written as `<menu.theme>.<ext>`, and `menu.theme` then defaults to
the store id, so nothing of the theme's own is ever overwritten;
- **a file we did not write is never touched** — every path installed is recorded
in `state.json`, an uninstall takes exactly those back out, and a name clash is
reported and skipped;
- a theme with no logo folder we can find, or that resolves logos some other way,
shows the store's **name as text** instead — EmulationStation's own fallback
(`CarouselComponent`: no image, so a `TextComponent`).
One Batocera wrinkle: themes that ship *with the system image* live under
`/usr/share`, and Batocera's root is a RAM overlay. The copy works and shows up
immediately, but it is gone after a reboot — the next sync puts it back, and
`batocera-save-overlay` makes it permanent. Themes under `/userdata/themes` keep
it for good.
For the old behaviour — games inside the box's own systems, under a subfolder,
merged into the box's gamelists — set `emulationstation.menu.mode` to `merge`.
@@ -231,7 +265,8 @@ that knows about them.
| `BATOCERA_STORE_ROOT`, `BATOCERA_PORTS_DIR`, `BATOCERA_PORT_NAME` | as for the installer |
In the store's own layout an uninstall is total: the store's ROM folder, its
gamelists and its `es_systems_<id>.cfg` all go, and the box is left as it was.
gamelists, its `es_systems_<id>.cfg` and the logos it installed into the themes
all go, and the box is left as it was.
In `merge` mode there is more to be careful about, and these stay behind:
- **ROMs you put in the store's subfolder yourself.** Directories are removed
@@ -313,7 +348,8 @@ stays a favourite across a sync.
| `paths.subfolder` | `warp` | the store's own folder inside `roms_root` (in `merge` mode: its subfolder inside each system) |
| `emulationstation.menu.mode` | `system` | `system`: the store gets its own menu entry. `merge`: install into the box's own systems |
| `emulationstation.menu.name` | `null``store.name` | label of the menu entry |
| `emulationstation.menu.theme` | `ports` | theme folder the entry borrows its logo from |
| `emulationstation.menu.theme` | `null` → the store id with a logo, `ports` without | theme folder the entry takes its logo from, and the name of the logo file |
| `emulationstation.menu.logo` | `null` | the entry's own logo: a path or URL, or a list of them (one per format). Installed into every theme's logo folder |
| `emulationstation.menu.updater` | `true` | an *Update …* entry inside the menu that runs the sync |
| `emulationstation.menu.labels` | `{}` | per-system folder labels; a system not listed keeps the box's own `<fullname>` |
| `emulationstation.ports_entry` | `null``false` in `system` mode, `true` in `merge` mode | also write a Ports entry that runs the sync |