mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 20:29:06 -05:00
612 lines
38 KiB
Markdown
612 lines
38 KiB
Markdown
# Installer
|
|
|
|
Reference for `install.sh`, `install.ps1` and `install.py`: what the one-liner
|
|
runs, what it detects, where the bytes come from and what it is allowed to
|
|
write. For BIOS directory paths per platform, see
|
|
[Getting started](getting-started.md).
|
|
|
|
## One line
|
|
|
|
```bash
|
|
# Linux / macOS / Steam Deck
|
|
curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh
|
|
```
|
|
|
|
```powershell
|
|
# Windows
|
|
irm https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1 | iex
|
|
```
|
|
|
|
From a clone, run the installer itself. Nothing beyond the standard library is
|
|
needed, and Python 3.8 is the floor both bootstraps enforce before handing over:
|
|
|
|
```bash
|
|
python install.py
|
|
```
|
|
|
|
## Passing options
|
|
|
|
The shell bootstrap forwards everything after `-s --`:
|
|
|
|
```bash
|
|
curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh \
|
|
| sh -s -- --platform retroarch --dest /path/to/sdcard/retroarch/cores/system
|
|
```
|
|
|
|
`--dest` is the BIOS folder itself: on a card, `retroarch/cores/system` for
|
|
RetroArch on Switch, `RetroArch/system` on Android.
|
|
|
|
PowerShell needs another form. `iex` takes the script text as its own argument,
|
|
so anything appended to `irm ... | iex` is read as an argument to `iex` and
|
|
rejected with `A positional parameter cannot be found`. Building a script block
|
|
from the text and calling it passes the arguments through:
|
|
|
|
```powershell
|
|
&([scriptblock]::Create((irm https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1))) `
|
|
--platform retroarch --dest D:\bios
|
|
```
|
|
|
|
## What the bootstrap does
|
|
|
|
Both wrappers do the same four things before any BIOS file is considered.
|
|
|
|
They fetch `install.py` over HTTPS only, from `RETROBIOS_INSTALL_URL` or the
|
|
pinned raw URL. A URL with any other scheme is refused outright.
|
|
|
|
They cap the download at 2 MB and require the SHA-256 to equal the 64 hex
|
|
characters embedded in the wrapper, `RETROBIOS_INSTALL_SHA256` when set. A
|
|
mismatch aborts with "the downloaded installer does not match its expected
|
|
fingerprint" and nothing runs. Right after an update of this project the
|
|
wrapper and `install.py` can come from caches a few minutes apart, which reads
|
|
the same way: waiting five minutes settles it. Any change to `install.py`
|
|
therefore means recomputing that pin in both wrappers.
|
|
|
|
They locate a Python 3.8 or newer interpreter (`python3` then `python` on
|
|
POSIX, `py -3` then `python3` then `python` on Windows). Without one,
|
|
`install.sh` names the command that installs it on the system it runs on
|
|
(`pkg install python` in Termux, which it offers to run since Termux installs
|
|
without root, `xcode-select --install` on macOS, apt, dnf, pacman, zypper or apk
|
|
from `/etc/os-release`), and `install.ps1` at a console offers to run `winget
|
|
install Python.Python.3.12`, then reads the new PATH and carries on.
|
|
`install.sh` needs `curl` or `wget` (GNU or BusyBox) and `sha256sum` or `shasum`.
|
|
Windows PowerShell 5.1 still negotiates SSL 3.0 and TLS 1.0 by default and
|
|
GitHub refuses both, so `install.ps1` raises the floor to TLS 1.2 before
|
|
downloading, and `install.sh` pins the same floor with `curl --tlsv1.2`.
|
|
|
|
They delete the temporary installer on the way out, including on interrupt.
|
|
Run from a clone, `install.sh` and `install.ps1` reuse the `install.py` sitting
|
|
next to them and skip the download entirely. Piped from stdin, `install.sh`
|
|
never treats the working directory as a trusted location for it.
|
|
|
|
Piped from `curl`, the shell's standard input is the script itself, so
|
|
`install.sh` hands the installer the terminal (`/dev/tty`) when there is one.
|
|
Without that, every question the installer can ask was skipped on Linux and
|
|
macOS.
|
|
|
|
## What the installer does
|
|
|
|
1. Detects the host OS and the platforms installed on it.
|
|
2. Fetches `install/<platform>.json` from the same revision the bootstrap
|
|
verified the installer against, so the file list and the code reading it
|
|
always come from one commit.
|
|
3. Prints the file count and total size, then a safety notice counting the
|
|
entries the collection cannot serve, how many of those the platform marks
|
|
required, and why. At a terminal, it then offers to install everything or
|
|
to choose systems, cores and regions (see below).
|
|
4. Hashes what is already in the destination and reports how many entries are
|
|
present, verified, or present with the wrong contents.
|
|
5. Downloads what is missing or wrong, up to 8 files at a time. At a
|
|
terminal one line shows the progress, the speed and the time left, while in a
|
|
log each finished file gets its own line. In an interactive run, a
|
|
download of 1 GB or more is confirmed first (`--yes` skips the question).
|
|
6. Verifies each download against the declared size, SHA-256 and SHA-1 before
|
|
it touches the destination, then moves it into place with `os.replace` so
|
|
a partial file is never visible. A failed attempt is retried up to three
|
|
times, after 1, 2 then 4 seconds or the delay a `Retry-After` header asks.
|
|
A file of 16 MB or more keeps its partial copy (`.<name>.part`) when a
|
|
transfer stops, and the next attempt or the next run requests only the
|
|
rest.
|
|
7. Copies into standalone emulator directories, only with `--standalone-copies`.
|
|
|
|
A RetroArch run into an empty folder printed this on 2026-10-21:
|
|
|
|
```
|
|
Fetching file index for retroarch...
|
|
8069 files (6.5 GB)
|
|
Not installable: 11 files (1 required, not found: 11).
|
|
|
|
Checking existing files...
|
|
0/8069 present (0 verified, 0 wrong hash)
|
|
```
|
|
|
|
When the manifest leaves entries out (a file the collection cannot supply, or
|
|
one that failed its hash check), a `Not installable:` line under the index count
|
|
says how many and why. `--check` and the closing report also name the required
|
|
ones, grouped by system with the emulators that read them, and point at the
|
|
emulator pages. They do not change the exit code of `--check`, since no run of
|
|
the installer can fetch them.
|
|
|
|
## Choosing what to install
|
|
|
|
By default the installer fetches everything the platform runs. At a terminal,
|
|
after detection, it asks:
|
|
|
|
```
|
|
RetroArch: 8069 files, 6.5 GB.
|
|
Enter) install everything
|
|
i) only what the installed cores read: 100 files, 44.2 MB
|
|
c) choose systems, cores and regions
|
|
q) quit
|
|
```
|
|
|
|
`i` appears for RetroArch, Lakka and RetroPie when cores are found installed,
|
|
and the line above is a RetroArch with Beetle PSX HW, Snes9x, mGBA, Genesis Plus GX
|
|
and FCEUmm.
|
|
It keeps what those cores read and drops the systems none of them emulates,
|
|
the rule `--installed-cores` applies.
|
|
|
|
`c` walks through three numbered lists, each answered with numbers such as
|
|
`1,4,7-9`, or Enter to keep everything on that axis.
|
|
|
|
- **Systems.** The consoles and computers the platform declares. A last
|
|
entry, `other`, holds what cores load for systems the platform does not
|
|
list, such as game engines and extra computers. Choosing systems leaves it
|
|
out unless it is chosen too, as `generate_pack.py --system` does.
|
|
- **Cores.** The platform's own BIOS list for the chosen systems is always
|
|
kept. This narrows only the extra files each emulator core loads beyond it.
|
|
Asked only when the chosen systems have such files.
|
|
- **Regions.** Asked only when a chosen system has one BIOS per region. The
|
|
numbers give an order of preference: `2,1` keeps the second region where a
|
|
file for it exists, and the first one elsewhere.
|
|
|
|
The run then prints the options that repeat the same choice without
|
|
questions, for instance
|
|
`--platform retroarch --system sony-playstation --region north-america`.
|
|
|
|
The same choices on the command line:
|
|
|
|
```bash
|
|
python install.py --platform retroarch --list-systems
|
|
python install.py --platform retroarch --system sony-playstation,nintendo-gba
|
|
python install.py --platform retroarch --system sony-playstation --core pcsx_rearmed
|
|
python install.py --platform recalbox --region us,eu,jp
|
|
```
|
|
|
|
### Installed cores
|
|
|
|
`--installed-cores` reads the cores a RetroArch already has and installs what
|
|
they need. The cores are the `<name>_libretro.so` (`.dll`, `.dylib`,
|
|
`_libretro_android.so`) files in the `libretro_directory` of the
|
|
`retroarch.cfg` detection found, `cores/` beside RetroArch when the setting is
|
|
unset, a `retroarch.cfg` next to the system directory for a portable install
|
|
given with `--dest`, and the folders RetroPie (`/opt/retropie/libretrocores`)
|
|
and Lakka (`/usr/lib/libretro`, `/storage/cores`) install into. A core file
|
|
is named the buildbot's way (`mednafen_psx`), and the manifest maps each name to
|
|
the profile its files are filed under (`beetle_psx`).
|
|
|
|
What stays is what a pack built for exactly those cores carries, the rule
|
|
`--target` follows too: a platform system stays when an installed core
|
|
emulates it or reads its files, or when no core of the platform does. A
|
|
file a core loads stays when that core is installed. Where two cores read
|
|
one path and want different bytes, the copy of the first installed core in
|
|
the builder's order is the one installed. The same holds for a file of the
|
|
platform's own list: where a core's source contradicts the platform and the
|
|
frontend only checks the path, the core's copy is installed while that core
|
|
is, and otherwise the platform's answer, read from the first declaring
|
|
system that stays.
|
|
|
|
With `--dest` and no `retroarch.cfg` beside the folder, nothing names the cores
|
|
folder: `--cores-dir` names it, and the refusal prints the command with it.
|
|
|
|
Batocera, Recalbox and the other distributions ship their cores with the
|
|
system, so the option is refused there, as it is when no core is found or
|
|
when the file list predates the mapping: carrying on would install
|
|
everything while the option asked for less.
|
|
|
|
A name the platform does not have is refused before anything is downloaded,
|
|
since carrying on would install something other than what was asked. The
|
|
refusal suggests the closest names (`playstation` gives `sony-playstation`) and
|
|
the option that lists them all. Names are matched on letters and digits, so
|
|
`--platform "MiSTer FPGA"` and `--system "Sony PlayStation"` are read as the
|
|
site writes them. A system name that itself contains commas, as Recalbox's
|
|
`msx1,msx2,msxturbor` does, is read as one name. The name a platform gives a
|
|
system itself is accepted too: on Batocera `--system psx` is
|
|
`sony-playstation` and `--system gba` is `nintendo-gba`, the names its
|
|
`roms/` folders carry. They come from the platform's own file, and
|
|
`--list-systems` prints them beside each system (`nintendo-gba (gba)`).
|
|
|
|
The region rule is the one `generate_pack.py --region` applies, and a test
|
|
holds the two to the same file list. In each group of regional alternatives
|
|
the best-ranked match stays. A file declared for every region beats regional
|
|
files that match nothing asked. When nothing matches, every regional file
|
|
stays, so no system is left without its BIOS. A file the emulator does not
|
|
select by region always stays. The manifest records, for each file, the
|
|
systems that declare it, and for a regional file, its regions and the groups
|
|
it competes in. The installer reads nothing else.
|
|
|
|
The questions are asked only when standard input is a terminal. Piped,
|
|
scripted or run with `--no-input`, the installer asks nothing and installs
|
|
what the options name, everything by default.
|
|
|
|
## Options
|
|
|
|
| Option | Effect |
|
|
|--------|--------|
|
|
| `--platform NAME` | Install for this platform instead of the detected one. Unknown names are refused with the available list |
|
|
| `--dest PATH` | Destination directory, overriding detection. Without `--platform`, the file list is the platform detected here: at a terminal the installer asks, Enter taking the one found, and otherwise it uses the one found, RetroArch's when nothing is found, and refuses when several are. The run prints whose list it is. A folder that does not exist is named with the nearest one that does, then created. When more than its last level is missing (a mistyped path), a run at a terminal asks first, and the answer defaults to no |
|
|
| `--target NAME` | Keep only the files the cores of that hardware target need, and the systems they emulate. An unknown target is refused rather than ignored, since carrying on would install everything. On Batocera, Recalbox, ROCKNIX and Lakka the board is read from the system (`batocera.arch`, `recalbox.arch`, `HW_DEVICE`, `LIBREELEC_DEVICE` or `LIBREELEC_PROJECT`) and applied when no `--target` is given |
|
|
| `--every-board` | Install the files of every board instead of the one the system names, for a card that moves between devices |
|
|
| `--installed-cores` | Keep only what the cores already installed read (RetroArch, Lakka, RetroPie). See [Installed cores](#installed-cores) |
|
|
| `--cores-dir PATH` | The folder RetroArch loads its cores from, for `--installed-cores` when it is not found. Implies `--installed-cores` |
|
|
| `--system NAMES` | Install only these systems. Comma-separated or repeated. `--list-systems` prints the names, `other` included |
|
|
| `--core NAMES` | Keep the platform's own list and only the extra files these cores load. `--list-cores` prints the names |
|
|
| `--emulator NAMES` | Install what these emulators read on the detected platform, and nothing else: their own files and the platform's files of the systems they run. Each emulator page on the site gives this command. `--list-emulators` prints the names, buildbot names such as `mednafen_psx` included |
|
|
| `--region LIST` | Preferred regions, best first (`us,eu,jp`): keep one BIOS per region where a system has several. Accepts the names and aliases of `generate_pack.py --region` |
|
|
| `--list-systems` | Print a platform's systems with the files and size each holds |
|
|
| `--list-cores` | Print the cores whose extra files a platform installs |
|
|
| `--list-emulators` | Print every name `--emulator` accepts, each buildbot name beside the emulator it stands for |
|
|
| `--romm-firmware TEMPLATE` | RomM: where each platform's firmware goes in the library, as `filesystem.structure.firmware` in RomM's `config.yml` says (`{platform}/bios` for a library laid out as `{platform}/roms`). Read from `config.yml` beside the library when it is there. See [RomM firmware layout](#romm-firmware-layout) |
|
|
| `--no-input` | Never ask a question, even at a terminal |
|
|
| `--check` | Report and exit without writing |
|
|
| `--dry-run` | Show what would be downloaded, by system and with sizes, without writing anything |
|
|
| `--yes`, `-y` | Do not ask before a download of 1 GB or more |
|
|
| `--quiet`, `-q` | Print only failures and the final summary. Implies `--no-input` |
|
|
| `--version` | Print the installer's identity, the first 12 hex digits of the SHA-256 the bootstraps pin |
|
|
| `--list-platforms` | Print the supported platforms and what was detected here |
|
|
| `--list-targets` | Print the hardware targets a platform publishes, with the core count each carries |
|
|
| `--jobs N`, `-j N` | Parallel downloads, 1 to 32, default 8 |
|
|
| `--verbose`, `-v` | Print per-attempt failures and hash mismatches |
|
|
| `--standalone-copies` | Also copy into detected standalone emulator directories |
|
|
| `--help`, `-h` | Print the option list, a few examples in the form the run was started with, and the address of this page |
|
|
|
|
`--check` reads the same file list, hashes the destination and exits, with
|
|
code 1 when a file is missing or does not match and 0 when every one does. Files
|
|
with a wrong hash are listed first and in full, then the first ten missing ones
|
|
(`--verbose` lists them all). `--dry-run` counts the data trees in its total. Without
|
|
it, a file already present with the expected hash is left untouched, and one
|
|
whose contents do not match is re-downloaded and replaced by the verified copy.
|
|
Files the list does not name are never read, moved or deleted.
|
|
|
|
## Environment overrides
|
|
|
|
| Variable | Effect |
|
|
|----------|--------|
|
|
| `RETROBIOS_REF` | Branch or tag the file list and the files committed to the repository are read from. Defaults to `main`. Files the manifest marks `release_asset` always come from the `large-files` release, whatever the ref, and that release replaces an asset in place when it is rebuilt. Each release deletes the previous release tags ([release process](release-process.md#cutting-a-release)), so a tag holds a file list until the next release |
|
|
| `RETROBIOS_BASE_URL` | Base URL for the manifest and the files it declares. HTTPS only, plain HTTP allowed to loopback for end-to-end tests. Whoever controls this base controls the expected hashes, hence the check. A file list that cannot be fetched names the address, and this variable when it is set |
|
|
| `RETROBIOS_OS` | Names the host outright (`linux`, `wsl`, `windows`, `darwin`, `android`) instead of detecting it |
|
|
| `EXTERNAL_STORAGE` | Android only, the shared storage root RetroArch builds its default directories from. Defaults to `/storage/emulated/0` |
|
|
| `RETROBIOS_INSTALL_URL` | Where the bootstrap fetches `install.py`. HTTPS only |
|
|
| `RETROBIOS_INSTALL_SHA256` | The SHA-256 the bootstrap requires of that installer, 64 hex characters. When either of these two is set and the fingerprint does not match, the error names the variable instead of suggesting a retry |
|
|
| `RETROBIOS_BOOTSTRAP` | Set by `install.sh` (`sh`) and `install.ps1` (`ps1`) for the temporary installer they run, so that its hints give back the one-line command rather than `python3 install.py` |
|
|
| `NO_COLOR` | Plain text, no colour, even at a terminal. `TERM=dumb` also turns the live progress line off |
|
|
| `HTTPS_PROXY`, `NO_PROXY` | Honoured for every download through the standard library's default proxy handling: one names the proxy, the other the hosts to reach directly |
|
|
|
|
## Platform detection
|
|
|
|
Detection reads what the system itself writes, never a guess from a directory
|
|
name. A machine runs one embedded system, so every rule below is tested. A
|
|
system built on another one (KOLOS on JELOS, dArkOS on ArkOS, spruceOS on a
|
|
TrimUI) matches both and is the one meant. Two unrelated systems matching
|
|
are both named: at a terminal the run asks which one, otherwise it stops and
|
|
asks for `--dest`. The run names the system as it names itself, with the file
|
|
list it gets: `Found AmberELEC at /storage/roms/bios (RetroArch files)`.
|
|
`--list-platforms` lists every system below.
|
|
|
|
Where a system's RetroArch config is named, its `system_directory` is the
|
|
folder, `~` being that system's home for RetroArch. The folder in the last
|
|
column is used when no config sets one. ArkOS and dArkOS keep two configs
|
|
(64-bit and 32-bit RetroArch), and their "Switch to SD2 for Roms" script
|
|
rewrites both to `/roms2/bios`: a second card is followed through them.
|
|
|
|
| Evidence | System | File list | BIOS directory |
|
|
|----------|--------|-----------|----------------|
|
|
| `/etc/os-release` `OS_NAME=ROCKNIX` (or `ID=rocknix` on images before 2025) | ROCKNIX | ROCKNIX | `/storage/roms/bios` |
|
|
| `/etc/os-release` `OS_NAME=knulli` or `/usr/share/knulli/knulli.version` | KNULLI | Batocera | `/userdata/bios` |
|
|
| `/etc/os-release` `ID=lakka` | Lakka | Lakka | `/storage/system` |
|
|
| `/usr/share/batocera/batocera.version` | Batocera | Batocera | `/userdata/bios` |
|
|
| `/recalbox/recalbox.version` or `/usr/bin/recalbox-settings` | Recalbox | Recalbox | `/recalbox/share/bios` |
|
|
| `/opt/muos` | muOS | RetroArch | `system_directory` of `/opt/muos/share/info/config/retroarch.cfg`, else of the default it is copied from: `/run/muos/storage/bios`, which muOS binds to `MUOS/bios` on the second card when that card holds one, else the first. Cores are read from that config's `libretro_directory` (`/opt/muos/share/core`) |
|
|
| `/mnt/mmc/MUOS/` | muOS | RetroArch | `/mnt/mmc/MUOS/bios` |
|
|
| `/etc/os-release` `NAME=AmberELEC` (CHAos writes the same) | AmberELEC | RetroArch | `/storage/.config/retroarch/retroarch.cfg`, else `/storage/roms/bios` |
|
|
| `/usr/bin/batocera-info` naming PAN4ELEC, on AmberELEC | PAN4ELEC | RetroArch | as AmberELEC |
|
|
| `/etc/os-release` `NAME=EmuELEC` | EmuELEC | RetroArch | as AmberELEC |
|
|
| `/etc/os-release` `OS_NAME=JELOS` | JELOS | RetroArch | as AmberELEC |
|
|
| `/usr/lib/systemd/system/jelos-ready.service`, on JELOS | KOLOS | RetroArch | as AmberELEC |
|
|
| `/etc/os-release` `OS_NAME=UnofficialOS` | UnofficialOS | RetroArch | as AmberELEC |
|
|
| `/etc/os-release` `OS_NAME=ArchR` | ArchR | RetroArch | as AmberELEC |
|
|
| `/home/ark` and `/opt/system`, or `title=ArkOS` in `/usr/share/plymouth/themes/text.plymouth` | ArkOS | RetroArch | `/home/ark/.config/retroarch/retroarch.cfg` then `retroarch32/retroarch.cfg`, else `/roms/bios` |
|
|
| `title=dArkOS` in the same file (the dArkOSRE, dArkOSen and ArkOS4Clone images keep it) | dArkOS | RetroArch | as ArkOS |
|
|
| `title=RetroOZ` in the same file, `/opt/.retrooz` or `/etc/retrooz_version` | RetroOZ | RetroArch | `/home/odroid/.config/retroarch/retroarch.cfg` then `retroarch32/retroarch.cfg`, else `/roms/bios` |
|
|
| `/mnt/SDCARD/spruce` | spruceOS | RetroArch | `/mnt/SDCARD/Saves/ra-configs/retroarch-*.cfg`, else `/mnt/SDCARD/BIOS` |
|
|
| `/mnt/SDCARD/System/usr/trimui/crossmix-version.txt` | CrossMix-OS | RetroArch | `/mnt/SDCARD/RetroArch/retroarch.cfg`, else `/mnt/SDCARD/BIOS` |
|
|
| `/mnt/SDCARD/.tmp_update/onionVersion/version.txt` | Onion | RetroArch | `/mnt/SDCARD/RetroArch/.retroarch/retroarch.cfg`, else `/mnt/SDCARD/BIOS` |
|
|
| `/mnt/SDCARD/.allium` | Allium | RetroArch | as Onion |
|
|
| `/mnt/SDCARD/Koriki/version.txt` | Koriki | RetroArch | as Onion |
|
|
| `/etc/main` and `/usr/bin/miyooctl` | MiyooCFW | RetroArch | `/mnt/.retroarch/retroarch.cfg`, else `/mnt/.retroarch/system` |
|
|
| `/mnt/plumos-user` | plumOS | RetroArch | `/mnt/plumos-user/bios`, which its launcher appends to every RetroArch run |
|
|
| `/etc/os-release` `ID=plumos` without the folder above | plumOS | none | its builds choose a folder per launch: named, not installed to |
|
|
| `/usr/trimui` | TrimUI | none | no published source names the stock firmware's folder: named, not installed to |
|
|
| `/mnt/SDCARD/.system/res` or `/mnt/sdcard/.system/res` | MinUI | none | MinUI, MyMinUI and NextUI give each core its own `Bios/<tag>/`, a layout not written yet: named, not installed to |
|
|
| `/mnt/vendor/bin/dmenu.bin` | Anbernic stock firmware | RetroArch | `/mnt/mmc/bios` |
|
|
|
|
AmberELEC, EmuELEC and KNULLI get the file list of the platform whose cores
|
|
they ship until a list of their own is published. TheRA is not detected: no
|
|
image or source of its Odroid Go builds is published, so neither its marker
|
|
files nor its folder could be read.
|
|
|
|
Batocera, Recalbox, ROCKNIX and Lakka also name the board they were built for, and
|
|
that board narrows the install unless `--target` or `--every-board` says
|
|
otherwise (see the options above). A board with no core list of its own is
|
|
named and gets the files of every board. Knulli names its board in
|
|
`/usr/share/knulli/knulli.arch` but builds its own set of cores, so Batocera's
|
|
per-board lists are not applied to it and the run says so.
|
|
|
|
Some platforms keep their BIOS in a fixed directory, and that directory is the
|
|
evidence. They are looked for after the configurations below, for a platform
|
|
not already found that way.
|
|
|
|
| Evidence | Platform | BIOS directory |
|
|
|----------|----------|----------------|
|
|
| `/media/fat/MiSTer` | MiSTer FPGA | `/media/fat/games` |
|
|
| `C:\RetroBat\bios` or `%USERPROFILE%\RetroBat\bios` | RetroBat | the same |
|
|
| `~/Emulation/bios` (Linux and macOS) | EmuDeck | the same |
|
|
| `~/.var/app/net.retrodeck.retrodeck` | RetroDECK | `~/retrodeck` |
|
|
| `/romm/library/bios` | RomM | the same |
|
|
| `/romm/config/config.yml` | RomM | `/romm/library/bios`, laid out by its firmware template |
|
|
| `~/RetroPie/BIOS` | RetroPie | the same |
|
|
| `%USERPROFILE%\BizHawk\Firmware` | BizHawk | the same |
|
|
| `EmuHawk.exe` or `EmuHawkMono.sh` in a `BizHawk*` folder of the home folder or one level down (`~/Games/BizHawk-2.11.1`), the newest | BizHawk | `Firmware` beside it |
|
|
|
|
Desktop installs are read from their own configuration, and several can be
|
|
found on one machine.
|
|
|
|
Under WSL both sides are searched. The Windows one is reached through its
|
|
`/mnt/<drive>` mount: a `C:\` path from a rule or a config becomes
|
|
`/mnt/c/...`, and the Windows variables the rules use (`USERPROFILE`,
|
|
`APPDATA`, `LOCALAPPDATA`, `ProgramFiles`) are read through `cmd.exe` when
|
|
WSL does not pass them. A forced `--platform retrobat` falls back to
|
|
`/mnt/c/RetroBat/bios`.
|
|
|
|
| Evidence | Platform | BIOS directory |
|
|
|----------|----------|----------------|
|
|
| `HKCU\SOFTWARE\RetroBat` `LatestKnownInstallPath`, which `RetroBat.exe` writes on every start (read through `reg.exe` under WSL) | RetroBat | that folder + `\bios`, before the fixed folders above |
|
|
| `~/.config/EmuDeck/settings.sh` (Linux and macOS) | EmuDeck | `emulationPath` + `/bios` |
|
|
| `%APPDATA%\EmuDeck\settings.ps1`, else `%USERPROFILE%\EmuDeck\settings.ps1` | EmuDeck | `$emulationPath` + `\bios`, `C:\Emulation\bios` when it names none (a network install: the `$VolName` share + `\Emulation\bios`) |
|
|
| `~/.var/app/net.retrodeck.retrodeck/config/retrodeck/retrodeck.json` | RetroDECK | `paths.rd_home_path`, falling back to the pre-migration `retrodeck.cfg` and its `rdhome`. A folder RetroDECK moved on its own is followed: files for `bios/` go to `paths.bios_path`, files for `roms/` to `paths.roms_path`, and the run names the new place |
|
|
| `~/.var/app/org.libretro.RetroArch/config/retroarch/retroarch.cfg` | RetroArch (Flatpak) | `system_directory` |
|
|
| `~/snap/retroarch/current/.config/retroarch/retroarch.cfg` | RetroArch (Snap) | `system_directory` |
|
|
| `~/.config/retroarch/retroarch.cfg` | RetroArch (native) | `system_directory` |
|
|
| `~/Library/Application Support/RetroArch/config/retroarch.cfg` | RetroArch (macOS) | `system_directory`. At default, `~/Documents/RetroArch/system` (portable, Steam build included: `Application Support/RetroArch/system`) |
|
|
| `retroarch.cfg` beside `retroarch.exe` (`C:\RetroArch-Win64`, `C:\RetroArch`, `Program Files\RetroArch`) | RetroArch (Windows) | `system_directory`. At default or without a config, `system` beside the exe |
|
|
| `%APPDATA%\RetroArch\retroarch.cfg` | RetroArch (Windows) | `system_directory`, read when the exe folder has no config |
|
|
| `%ProgramFiles(x86)%\Steam\steamapps\common\RetroArch\retroarch.cfg` | RetroArch (Steam) | `system_directory` |
|
|
| `%LOCALAPPDATA%\Packages\1e4cf179-f3c2-404f-b9f3-cb2070a5aad8_*\LocalState` | RetroArch (UWP/MSIX package) | `system_directory` of the `retroarch.cfg` there, `~` meaning that folder. By default `LocalState\system` |
|
|
| LaunchBox `Data\Emulators.xml` | RetroArch (portable) | the system directory that entry points at |
|
|
| LaunchBox `Data\Emulators.xml` naming `EmuHawk.exe` | BizHawk (portable) | `Firmware` beside `EmuHawk.exe` |
|
|
| `$BIZHAWK_DATA_HOME`, or `$XDG_DATA_HOME/emuhawk-monort-<version>` (Nix package) | BizHawk (Unix) | `Firmware` under it |
|
|
|
|
`system_directory` is read the way RetroArch expands it: a leading `~` is the
|
|
home directory, a leading `:` is the application directory, and `default` means
|
|
`system` next to the application. The LaunchBox installation is located through
|
|
its Start menu shortcut, the only record of where its installer was pointed.
|
|
|
|
Android has one frontend with a shared BIOS directory, and the app-private
|
|
directories of the standalone emulators are unreachable from outside the app
|
|
under scoped storage.
|
|
|
|
| Evidence | Platform | BIOS directory |
|
|
|----------|----------|----------------|
|
|
| `<shared storage>/Android/data/<package>/files/retroarch.cfg` | RetroArch (Android) | `system_directory` |
|
|
| `/data/data/<package>/files/retroarch.cfg` | RetroArch (Android) | `system_directory` |
|
|
| `<shared storage>/RetroArch/` | RetroArch (Android) | `<shared storage>/RetroArch/system` |
|
|
|
|
The packages are `com.retroarch.aarch64`, `com.retroarch` and
|
|
`com.retroarch.ra32`, the flavours the frontend publishes. Both config paths
|
|
are unreadable from Termux on Android 11 and later, so the directory itself is
|
|
what answers on current devices.
|
|
|
|
ES-DE and LaunchBox are reported when present and nothing is written to them:
|
|
they reference emulators rather than owning a BIOS directory. ES-DE is looked
|
|
up at `$ESDE_APPDATA_DIR` or `~/ES-DE`, the two locations its own
|
|
`getAppDataDirectory` resolves.
|
|
|
|
When no platform is found but standalone emulators are (an ES-DE setup with
|
|
DuckStation and PCSX2, for one), the installer fills their own BIOS folders:
|
|
the folders of the [standalone copies](#standalone-copies) list that exist on
|
|
the machine, with the files that list names, taken from the EmuDeck file list
|
|
and then RetroArch's. At a terminal it asks first. `--check` and `--dry-run`
|
|
work as for a platform. With `--system`, `--core`, `--emulator`, `--region` or
|
|
`--target` the run asks for a platform as before, since those name a
|
|
platform's lists.
|
|
|
|
When a platform named with `--platform` is not found on the machine, the
|
|
destination falls back to the directory its detection looks for:
|
|
`/userdata/bios` for Batocera, `/recalbox/share/bios` for Recalbox,
|
|
`/storage/system` for Lakka, `~/retrodeck` for RetroDECK, `~/Emulation/bios`
|
|
for EmuDeck (`C:\Emulation\bios` on Windows), `/storage/roms/bios` for ROCKNIX, `/media/fat/games` for MiSTer,
|
|
`~/RetroPie/BIOS` for RetroPie, `/romm/library/bios` for RomM,
|
|
`C:\RetroBat\bios` for RetroBat, and RetroArch's own default system
|
|
directory for RetroArch. BizHawk has no such folder: it reads `Firmware`
|
|
beside its executable wherever the archive was extracted, so a forced
|
|
`--platform bizhawk` that finds no install stops and asks for `--dest`. On Android a forced `--platform retroarch` falls back to
|
|
`<shared storage>/RetroArch/system` instead, a home directory there belonging
|
|
to Termux rather than to the frontend. Pass `--dest` to say where instead of
|
|
relying on that.
|
|
|
|
With nothing detected, the installer lists the platforms and asks. With several
|
|
detected, it asks which one. Both prompts need a terminal: piped into a script
|
|
with no platform to install for, it prints the manual invocation, in the form
|
|
the run was started with (the one-line command for a one-liner user), and
|
|
exits 1.
|
|
|
|
## Standalone copies
|
|
|
|
Some files are read by a standalone emulator from its own data directory
|
|
rather than from the platform's BIOS tree. `--standalone-copies` copies them
|
|
there after the install, and only then: without the flag nothing is written
|
|
outside the selected tree.
|
|
|
|
The manifest carries the list, fifteen entries on the six platforms that
|
|
declare them (Batocera, EmuDeck, Recalbox, RetroArch, RetroBat, RetroDECK):
|
|
|
|
| Files | Copied to |
|
|
|-------|-----------|
|
|
| `prod.keys`, `title.keys` | yuzu, eden, citron, suyu and Ryujinx key directories |
|
|
| `Citra/sysdata/aes_keys.txt`, `Citra/sysdata/boot9.bin` | Azahar `sysdata` |
|
|
| `scph*.bin` | DuckStation `bios` |
|
|
| `ps2-*.bin` | PCSX2 `bios`, native and Flatpak |
|
|
| `GC/USA/IPL.bin`, `GC/EUR/IPL.bin`, `GC/JAP/IPL.bin`, `dsp_rom.bin`, `dsp_coef.bin` | Dolphin, per region for the IPL |
|
|
| `PPSSPP/ppge_atlas.zim` | PPSSPP `PSP/SYSTEM` |
|
|
| `dc/dc_boot.bin`, `dc/dc_nvmem.bin` | Flycast `data`, native and Flatpak |
|
|
|
|
An entry names one file or a glob, and one entry carries no file at all: when
|
|
an RPCS3 configuration directory is present it prints that `PS3UPDAT.PUP` is
|
|
installed through RPCS3's own File menu, since that firmware is an installer to
|
|
run rather than a file to copy.
|
|
|
|
Targets are per OS, WSL falling back to the Linux ones. A directory that does
|
|
not exist is skipped rather than created, so nothing is copied for an emulator
|
|
that is not installed. A destination that is already a symbolic link is
|
|
replaced by the file rather than written through: a write through the link
|
|
would land outside the folder the user opted into. The summary counts each
|
|
copy as copied, skipped (folder absent) or failed, and names every failure.
|
|
A run without the flag lists the emulator folders of this list that exist on
|
|
the machine, and says nothing when there are none.
|
|
|
|
## RomM firmware layout
|
|
|
|
RomM 5.4 places each platform's firmware where `filesystem.structure.firmware`
|
|
in its `config.yml` says, relative to the library: `bios/{platform}` by
|
|
default, the layout of the pack. A library laid out as `{platform}/roms` makes
|
|
RomM stop until `config.yml` declares its templates, and RomM suggests
|
|
`firmware: "{platform}/bios"` for it.
|
|
|
|
The installer reads that value from `config.yml` in the `config` folder beside
|
|
the library, `/romm/config/config.yml` inside the container, and places each
|
|
file accordingly: with `{platform}/bios`, `psx/scph5501.bin` goes to
|
|
`library/psx/bios/scph5501.bin`. `--dest` still names the library's `bios`
|
|
folder, whose parent is the library. Run from the host of a container, where
|
|
`config.yml` sits elsewhere, `--romm-firmware` gives the same value. A
|
|
library laid out as `{platform}/roms` with no template found is refused
|
|
rather than filled in the wrong place, and the message gives the command.
|
|
|
|
## Android
|
|
|
|
Android reports itself as Linux, so the runtime is named from its own
|
|
variables instead: `ANDROID_ROOT` and `ANDROID_DATA`, which Termux inherits,
|
|
with `/system/build.prop` covering a shell started without them.
|
|
|
|
Detection then looks for RetroArch, the only Android frontend keeping a shared
|
|
BIOS directory. `retroarch.cfg` is read from the app external files directory
|
|
and then from the internal one, the order the frontend itself follows
|
|
(RetroArch v1.22.2, `UserPreferences.java:36-89`), and a `system_directory`
|
|
left at `default` resolves against `<shared storage>/RetroArch`, where the
|
|
directory also sits when no config has been written yet
|
|
(`platform_unix.c:1670-1697`).
|
|
|
|
Under scoped storage those two config paths are unreadable from Termux on
|
|
Android 11 and later, so on current devices the shared directory is what
|
|
answers. Running `termux-setup-storage` once is what makes it reachable:
|
|
|
|
```bash
|
|
pkg install python
|
|
termux-setup-storage
|
|
curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh
|
|
```
|
|
|
|
`EXTERNAL_STORAGE` overrides the storage root for a device that mounts it
|
|
elsewhere. A custom BIOS directory is still named directly:
|
|
|
|
```bash
|
|
python install.py --platform retroarch --dest /storage/emulated/0/RetroArch/system
|
|
```
|
|
|
|
## Where the files come from
|
|
|
|
Each manifest entry names either `repo_path`, served from the repository at the
|
|
pinned revision, or `release_asset`, served from the `large-files` release for
|
|
the files above the size GitHub carries in a tree. Everything else in the entry
|
|
(`dest`, `size`, `sha1`, `sha256`, `cores`) describes what must land where and
|
|
what it must hash to.
|
|
|
|
Entries the collection cannot satisfy are not silently dropped: they travel in
|
|
`omitted_files` with a reason, and the run prints the count before downloading
|
|
anything. `not_found` means no file in the collection matches the entry, which
|
|
is what the [gap analysis](../gaps.md) tracks.
|
|
|
|
Some cores read a whole tree of data rather than a few files: Dolphin's `Sys/`,
|
|
PPSSPP's assets, blueMSX's machines. These are not files of the collection, so
|
|
`data_directories` names each tree's source archive (the libretro buildbot, an
|
|
upstream release) and the sha1 and size of every file the pack carries. The
|
|
installer downloads the archive once, maps its members the way the pack was
|
|
built (`strip_components` for a ZIP, `source_path` for a tarball, `exclude`),
|
|
and writes a file only when its hash matches the list. A file the source has
|
|
changed since is left out and counted, and the pack still holds it. `--check`
|
|
counts the data files missing, `--dry-run` names each tree it would fetch, and
|
|
narrowing with `--system`, `--core` or `--target` keeps only the trees of the
|
|
systems that stay.
|
|
|
|
## Trust boundary
|
|
|
|
The manifest is fetched over the network and treated as untrusted input. Every
|
|
path it carries goes through one validator that refuses an absolute path, a
|
|
drive letter, a backslash, a null byte, a doubled separator, a trailing slash
|
|
and any `..` component, so a destination cannot climb out of the BIOS
|
|
directory. Manifest, target list and payloads each have a size cap, the file
|
|
list a count cap, and the sum of the declared sizes is checked against the
|
|
total the manifest claims.
|
|
|
|
Symbolic links met under the BIOS root are followed on purpose: they are the
|
|
user's own layout, and EmuDeck links `bios/shadps4/sys_modules` into shadPS4's
|
|
data directory where the emulator actually reads it. The boundary is lexical,
|
|
not a resolved-path comparison, which is what keeps that case working.
|
|
Standalone copies are stricter, since they write outside the tree the user
|
|
selected: a symlink already sitting at the destination is replaced by the
|
|
file rather than followed.
|
|
|
|
## Changing the installer
|
|
|
|
`install.py` runs on the user's machine, often an older handheld, so it stays
|
|
on Python 3.8 and the standard library alone. The RetroPie 4.8 images
|
|
(`retropie-buster-4.8-*.img.gz`) are Debian Buster, whose `python3` is 3.7.3:
|
|
there the bootstrap stops before running anything, and the RetroPie pack from
|
|
the releases page is the way to install
|
|
([first install](first-install.md#error-python-38-or-newer-is-required)).
|
|
Tests refuse any 3.9 syntax or API, and one runs the installer end to end under
|
|
a real 3.8 interpreter, which CI installs beside 3.12 so the check cannot skip.
|
|
The build scripts need 3.9 and are not bound by this.
|
|
|
|
Every change to `install.py` recomputes the SHA-256 pinned in `install.sh` and
|
|
`install.ps1` in the same commit. The tests compare the pins with the file.
|
|
|
|
Narrowing by system, core, region or target replays the pack builder's rules
|
|
from fields the manifest carries, and a parity test compares an installer
|
|
selection with the pack built the same way. Given a manifest without those
|
|
fields, the installer refuses to narrow rather than install everything.
|
|
|
|
## Exit status
|
|
|
|
| Code | Meaning |
|
|
|------|---------|
|
|
| `0` | everything asked for is installed or already up to date |
|
|
| `1` | a failure: the one-liner could not download or verify `install.py`, or found no Python 3.8 (nothing was run), an unusable base URL, an unknown platform or target, `--installed-cores` with no core found, a manifest that fails validation, a fetch that never succeeded, no platform to install for in a non-interactive run, some files failed while others installed, or `--check` found files to fetch |
|
|
| `2` | nothing was installed: every file the run tried failed (a BIOS folder that cannot be written, for one), the drive has too little free space for the download (checked before any folder is created), or the question about a `--dest` folder that does not exist was answered no. Also an option the installer refused before any download: an unknown option, `--jobs` outside 1 to 32, `--target` with `--every-board`. The message then starts with `install.py: error:` |
|
|
| `130` | stopped with Ctrl+C, or with Ctrl+C or Ctrl+D at a question. Run the same command again to carry on |
|
|
| `141` | the output was piped to a reader that closed early (`--list-platforms \| head`) |
|
|
|
|
`install.ps1` passes the installer's code on unchanged: run as a file it exits
|
|
with it, run through `irm \| iex` it sets `$LASTEXITCODE` (an `exit` there
|
|
would close the PowerShell window). It declares no parameter of its own, so
|
|
every argument reaches `install.py` as typed, short forms such as `-v`
|
|
included. Its body runs in a script block of its own,
|
|
so `irm \| iex` leaves neither its variables nor its `$ErrorActionPreference`
|
|
in the session.
|
|
|
|
Failures that are per-file, such as a download exhausting its three attempts,
|
|
are counted and reported at the end.
|
|
|
|
## When something goes wrong
|
|
|
|
See [Troubleshooting](troubleshooting.md#installation-script-fails) for network,
|
|
permission and detection failures.
|