RetroBat's on-disk layout and integration seams - folder tree, es_systems.cfg, es_savestates.cfg, es_settings.cfg precedence, ES event hooks, and the ES menu entry. Use when reading or writing anything inside a RetroBat install.
Installs into .claude/skills of the current project.
Are you the author of Retrobat Layout?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/spinnich-retrobat-layout)
---
name: retrobat-layout
description: RetroBat's on-disk layout and integration seams - folder tree, es_systems.cfg, es_savestates.cfg, es_settings.cfg precedence, ES event hooks, and the ES menu entry. Use when reading or writing anything inside a RetroBat install.
---
# RetroBat layout
RomMBat integrates purely through seams RetroBat already has. **Do not fork RetroBat.**
## Map
This file holds the tree, BIOS and systems. The rest is in topic files beside it, by section:
| Section | File |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| [es_savestates.cfg is the authority on save states](savestates.md#es_savestatescfg-is-the-authority-on-save-states) | [savestates.md](savestates.md) |
| [es_settings.cfg is how you configure emulators](settings.md#es_settingscfg-is-how-you-configure-emulators) | [settings.md](settings.md) |
| [The window in which `es_settings.cfg` can be written](settings.md#the-window-in-which-es_settingscfg-can-be-written) | [settings.md](settings.md) |
| [Controller input: read `es_input.cfg`, never detect a layout](input.md#controller-input-read-es_inputcfg-never-detect-a-layout) | [input.md](input.md) |
| [The ES menu entry is two files, and one of them is somebody else's](emulationstation.md#the-es-menu-entry-is-two-files-and-one-of-them-is-somebody-elses) | [emulationstation.md](emulationstation.md) |
| [Event hooks](emulationstation.md#event-hooks) | [emulationstation.md](emulationstation.md) |
| [The EmulationStation HTTP API](emulationstation.md#the-emulationstation-http-api) | [emulationstation.md](emulationstation.md) |
| [RetroBat's scraper settings are settings RomMBat should read](media.md#retrobats-scraper-settings-are-settings-rommbat-should-read) | [media.md](media.md) |
| [gamelist.xml](media.md#gamelistxml) | [media.md](media.md) |
## Tree
| Path | Use |
| ------------------------------------------- | ----------------------------------------------------- |
| `roms/<system>/` | ROMs. Folder names come from `es_systems.cfg` |
| `roms/<system>/gamelist.xml` | Metadata ES reads directly |
| `roms/<system>/images`, `videos`, `manuals` | Media siblings, named after the ROM file |
| `saves/` | Emulator save output |
| `bios/` | BIOS and firmware, and much else. See below |
| `emulationstation/.emulationstation/` | ES home: `es_settings.cfg`, `scripts/`, themes |
| `system/es_menu/*.menu` | How RetroBat registers launchable apps in the ES menu |
| `system/version.info` | The version, used for the compatibility gate |
Locate the root by walking up from `AppContext.BaseDirectory` to a marker
(`retrobat.ini`, `emulationstation/`, `roms/`). Registry lookups are a fallback for fixed
installs only, never the primary path.
## bios/ is a shared tree, and RomMBat owns almost none of it
**The requirements manifest is not a file in the install.** `batocera-systems.json` ships as a
.NET string resource inside `emulationstation/batocera-systems.exe`, byte-identical to
`reference/batocera-systems.json` apart from a trailing newline (RB-379). So the "read the live copy"
rule below has no live copy to read: the manifest is bundled at `data/retrobat/bios.json`,
generated by `tools/build-bios-manifest.py`. An entry carrying `supplement` is one RetroBat lists
under a sibling system, copied onto a system whose row was measured reading it (`gb` takes
`sgb`'s four and `gbc`'s boot ROM, RB-293).
Measured on a real install, `bios/` holds **4,683 files and 373 MB before RomMBat writes
anything** (RB-380): `dolphin-emu` 2,508 files, `mame` 858 (776 of them software-list XML), `nxengine`
436, blueMSX's `Machines` 296, plus openMSX's whole user directory **including its save
states**. Exactly 3 files sat at a path the manifest names carrying the md5 it names.
So the rules here are stricter than under `roms/`:
- **Never overwrite.** A file at a required path whose md5 disagrees is reported and left
exactly as it is. A BIOS the user plays with beats the manifest's idea of the right one.
- **Never delete.** A file with the right md5 that RomMBat did not download is adopted as a
fact, not as something it may later remove. Firmware rows carry no `rom_id`, which is what
keeps eviction away from them at the schema level.
- **Everything lands under `bios/`.** Six manifest entries name `emulators/` paths; all six
are hashless and therefore unreachable through an md5 join, and the prefix is enforced anyway.
- **Destination paths go six segments deep** and one md5 can owe three of them
(`coleco.rom`, `colecovision.rom`, `openMSX/share/systemroms/coleco.rom`). The path is the
key; the md5 is not.
- **180 of the 354 requirements carry no md5 at all**, and 30 systems have nothing else. Those
are unverifiable, which is a third state beside matched and missing, and reporting them as
missing tells a user to hunt for a file RomMBat could not recognize if they already had it.
One absent from disk, where the library holds a file under its exact name, stays
`Unverifiable` with `BiosStep.SameName` set: a lead the report prints, never a fetch.
## es_systems.cfg is the authority on systems and extensions
Read the **live** copy, not the vendored template: it reflects that machine's actual
configuration. Each `<system>` carries `<name>`, `<fullname>`, `<manufacturer>`,
`<hardware>`, `<release>`, `<path>`, `<extension>` and `<command>`.
**The folder is `<path>`, not `<name>`.** They are different vocabularies and five systems
in the shipped 8.3.0 file disagree: `gw` writes to `gameandwatch`, `powerbomberman` to `pb`,
`casloopy` to `loopy`, `Windows` to `windows`, and `starship` is used **twice**, for
`ghostship` and `starship`, so `<name>` is not even unique. Three entries own no folder under
`roms/` (`library`, `screenshots`, and `retrobat` at `system/es_menu`) and `mess` declares no
path; none is a sync target (RB-69). `~` expands to `<root>/emulationstation`, so the
ubiquitous `~\..\roms\<folder>` resolves to `<root>/roms/<folder>`. Match
case-insensitively, and parse it as XML: `arcade` and `kodi` sit inside comments, which a
regex over `<system>` would wrongly pick up.
`<extension>` is **a note, never a sync filter**. It is a union across every emulator the
system offers (`nes` lists `.wad`; one `psx` emulator reads `.chd` and another does not), so it
cannot say whether the running emulator opens a file, only whether ES lists it. RomMBat syncs
every member and reports the ones the list omits as unlisted in ES. It also moves between patch
releases: 8.2.1 added `.decomp` to eleven systems and `.zar` to
`ps4`, which is the concrete reason the list is read live rather than bundled. Never cache a
per-system extension set across runs.
`<manufacturer>`, `<hardware>` and `<release>` let the platform rollout order be derived
rather than hand-maintained.