chore: merge round 2 fixes (installer)

This commit is contained in:
Abdessamad Derraz committed 2026-10-21 10:17:44 +02:00
commit c8c4f3f486
17 files changed
+1154 -286

No files matched your search

+10 -7
View File
@@ -74,7 +74,7 @@ from common import (
import packresolve
from refresh_data_dirs import cache_lock, hold_data_dirs
import region as region_mod
from manifestselect import SelectionIndex
from manifestselect import SelectionIndex, native_system_names
import slot as slot_mod
import slots
import split_pack
@@ -1229,12 +1229,12 @@ def generate_pack(
undecidable_slots=slot_undecidable,
narrowings=narrowings,
system_filter=system_filter,
bios_paths=[
str(detect.get("bios_path", ""))
for detect in (
_pack_registry.get(platform_name, {}).get("install", {}) or {}
).get("detect", [])
],
detect=(
_pack_registry.get(platform_name, {}).get("install", {}) or {}
).get("detect", []),
standalone_copies=(
_pack_registry.get(platform_name, {}).get("install", {}) or {}
).get("standalone_copies", []),
)
_write_generated_member(zf, "README.txt", readme_text)
@@ -4465,6 +4465,9 @@ def generate_manifest(
),
"core_names": core_names,
"system_cores": system_cores,
# The names the platform itself gives its systems (Batocera's psx),
# which --system accepts beside the ids.
"system_names": native_system_names(config, list(pack_systems)),
"files": manifest_files,
}
if data_directories:
+3 -1
View File
@@ -4735,6 +4735,7 @@ ZIP: extract them all into the same folder.
| [RetroBat](https://www.retrobat.org/) | Windows frontend with EmulationStation, includes RetroArch and standalone emulators | [RetroBat]({rel}) | The `bios` folder inside the RetroBat installation |
| [BizHawk](https://tasvideos.org/BizHawk) | Accuracy-focused multi-system emulator, popular for speedruns and TAS | [BizHawk]({rel}) | The `Firmware` folder inside the BizHawk installation |
| [LaunchBox](https://www.launchbox-app.com/) | Game library manager and launcher, uses RetroArch or standalone emulators behind the scenes | [RetroArch]({rel}) | Open RetroArch (via LaunchBox) > Settings > Directory > System |
| [EmuDeck](https://www.emudeck.com/) | Installs and configures multiple emulators with ES-DE | [EmuDeck]({rel}) | `bios` in the Emulation folder chosen in EmuDeck, `C:\\Emulation\\bios\\` by default |
### Linux
@@ -4750,6 +4751,7 @@ ZIP: extract them all into the same folder.
| Setup | What it is | Pack | Extract to |
|-------|-----------|------|-----------|
| [RetroArch](https://www.retroarch.com/) | Multi-system emulator | [RetroArch]({rel}) | `~/Library/Application Support/RetroArch/system/` |
| [EmuDeck](https://www.emudeck.com/) | Installs and configures multiple emulators with ES-DE | [EmuDeck]({rel}) | `~/Emulation/bios/` |
### Raspberry Pi and single-board computers
@@ -4778,7 +4780,7 @@ ZIP: extract them all into the same folder.
| Setup | What it is | Pack | Extract to |
|-------|-----------|------|-----------|
| [RomM](https://github.com/rommapp/romm) | Web-based ROM manager, plays games in the browser via EmulatorJS | [RomM]({rel}) | The `bios` folder in the RomM library, one subfolder per system |
| [RomM](https://github.com/rommapp/romm) | Web-based ROM manager, plays games in the browser via EmulatorJS | [RomM]({rel}) | `bios/` in the RomM library, one subfolder per platform. With `firmware: "{{platform}}/bios"` in `config.yml`, each subfolder goes to `<platform>/bios/` instead |
---
+36 -1
View File
@@ -17,8 +17,10 @@ from __future__ import annotations
from dataclasses import dataclass
import re
import region as region_mod
from common import _norm_system_id, sanitize_pack_path
from common import _norm_system_id, native_layer, sanitize_pack_path
from packextras import _extra_system_ids, _kept, extra_region_groups, system_spellings
@@ -118,3 +120,36 @@ class SelectionIndex:
if priority is not None:
out["priority"] = priority
return out
def _name_key(name: str) -> str:
"""Letters and digits only, as the installer compares names."""
return re.sub(r"[^a-z0-9]", "", name.lower())
def native_system_names(config: dict, systems: list[str]) -> dict[str, list[str]]:
"""Per platform system, the names the platform's own file gives it.
A Batocera user knows PlayStation as `psx`, the folder under roms/, and
the platform YAML records that name as `native_system` on each file it
transcribes. Only the platform's own entries count (`native_layer`), a
name spelled like the system id adds nothing, and a name the platform
gives to two systems, or that reads as another system's id, names
neither.
"""
native = native_layer(config) or {}
by_name: dict[str, set[str]] = {}
for sys_id, system in native.get("systems", {}).items():
if sys_id not in systems:
continue
for entry in system.get("files", []):
name = entry.get("native_system")
if isinstance(name, str) and name.strip():
by_name.setdefault(name.strip(), set()).add(sys_id)
ids = {_name_key(sys_id) for sys_id in systems}
out: dict[str, list[str]] = {}
for name, owners in by_name.items():
if len(owners) != 1 or _name_key(name) in ids:
continue
out.setdefault(next(iter(owners)), []).append(name)
return {sys_id: sorted(names) for sys_id, names in sorted(out.items())}
+221 -190
View File
@@ -5,6 +5,219 @@ goes, and which ones the user has to rename."""
from __future__ import annotations
INSTALL_SH = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh"
INSTALL_PS1 = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1"
def _detect_systems(detect: list[dict]) -> list[str]:
"""The operating systems the registry's detection rules cover, in order."""
order = ("linux", "darwin", "windows")
seen = {str(rule.get("os", "")) for rule in detect}
return [name for name in order if name in seen]
def _automatic_section(
display: str, detect: list[dict], standalone_copies: list[dict] | None,
) -> str:
"""The one-line install, for every system the registry detects the
platform on. Written per platform by hand, it left ROCKNIX and MiSTer
without it and told EmuDeck's Windows edition to open Konsole."""
systems = _detect_systems(detect)
if not systems:
return ""
# A platform detected on Linux only is a system of its own (Batocera,
# ROCKNIX, MiSTer) or runs on one machine (RetroDECK, a RomM server):
# the command runs there.
on_device = systems == ["linux"]
lines = [
" Option A: Automatic (recommended)",
" ---------------------------------",
]
posix = [name for name in systems if name != "windows"]
if posix:
if on_device:
where = f"On the machine running {display} (its terminal, or SSH), run:"
else:
labels = {"linux": "Linux", "darwin": "macOS"}
where = f"{' and '.join(labels[name] for name in posix)}: run this in a terminal:"
lines += [f" {where}", "", f" curl -fsSL {INSTALL_SH} | sh", ""]
if "windows" in systems:
lines += [" Windows: run this in PowerShell:", "", f" irm {INSTALL_PS1} | iex", ""]
lines += [
f" The installer finds the {display} BIOS folder on this machine and",
" installs there, checking every file. --dest names another folder.",
"",
]
# On a desktop: an emulator folder under the home of a console's own
# system is not one its frontend reads.
if standalone_copies and not on_device:
lines += [
" Standalone emulators read some files from folders of their own",
" (Switch and 3DS keys, PlayStation and GameCube BIOS). To copy them",
" into the emulator folders the installer finds, add --standalone-copies:",
"",
]
if posix:
lines.append(f" curl -fsSL {INSTALL_SH} | sh -s -- --standalone-copies")
if "windows" in systems:
lines.append(f" &([scriptblock]::Create((irm {INSTALL_PS1}))) --standalone-copies")
lines.append("")
return "\n".join(lines) + "\n"
def _generic_manual(bios_paths: list[str]) -> str:
"""Where the files go, from the registry's paths, for a platform with no
manual guide of its own."""
targets = [path for path in bios_paths if path]
where = " or ".join(f"{path}/" for path in dict.fromkeys(targets)) or "your BIOS directory"
return (
" Option B: Manual\n"
" ----------------\n"
f" 1. Extract all files from this archive directly into {where}\n"
" 2. Overwrite if asked\n\n"
)
# What a manual install needs on each platform: its shares, cards and folders.
MANUAL_GUIDES: dict[str, str] = {
"retroarch": (
" Option B: Manual (PC)\n"
" ---------------------\n"
" 1. Find your RetroArch system directory:\n"
" - RetroArch > Settings > Directory > System/BIOS\n"
" - Default: retroarch/system/\n"
" 2. Extract all files from this archive directly into your system directory\n"
" 3. Overwrite if asked\n\n"
" Option C: Manual (handheld / SD card)\n"
" -------------------------------------\n"
" Anbernic, Retroid, Miyoo, Trimui, etc.:\n"
" 1. Connect your SD card to your PC\n"
" 2. Find the BIOS folder (usually BIOS/ or system/)\n"
" 3. Extract all files from this archive directly into that folder\n"
" 4. Eject SD card and reboot your device\n\n"
" Common paths by device:\n"
" Anbernic (ArkOS/JELOS): BIOS/\n"
" Retroid (RetroArch): RetroArch/system/\n"
" Miyoo Mini (Onion OS): BIOS/\n"
" Steam Deck (RetroArch): ~/.config/retroarch/system/\n\n"
),
"batocera": (
" The terminal is F1 from the Batocera menu, or SSH.\n\n"
" Option B: Manual (network share)\n"
" --------------------------------\n"
" 1. On your PC, open the Batocera network share:\n"
" - Windows: \\\\BATOCERA\\share\\bios\\\n"
" - Mac/Linux: smb://batocera/share/bios/\n"
" 2. Extract all files from this archive directly into the share\n"
" 3. Overwrite if asked\n\n"
" Option C: Manual (SD card)\n"
" --------------------------\n"
" 1. Put the SD card in your PC\n"
" 2. Navigate to /userdata/bios/ on the SHARE partition\n"
" 3. Extract all files from this archive directly into that folder\n\n"
" NOTE: Dreamcast flash memory is named dc_nvmem.bin\n"
" (if your setup asks for dc_flash.bin, same file).\n\n"
),
"recalbox": (
" Option B: Manual (network share)\n"
" --------------------------------\n"
" 1. On your PC, open the Recalbox network share:\n"
" - Windows: \\\\RECALBOX\\share\\bios\\\n"
" - Mac/Linux: smb://recalbox/share/bios/\n"
" 2. Extract all files from this archive directly into the share\n\n"
" Option C: Manual (SD card)\n"
" --------------------------\n"
" 1. Put the SD card in your PC\n"
" 2. Navigate to /recalbox/share/bios/\n"
" 3. Extract all files from this archive directly into that folder\n\n"
),
"emudeck": (
" Option B: Manual\n"
" ----------------\n"
" Extract all files from this archive directly into EmuDeck's bios\n"
" folder, inside the Emulation folder chosen in EmuDeck:\n"
" Steam Deck, Linux and macOS: ~/Emulation/bios/ by default\n"
" Windows: C:\\Emulation\\bios\\ by default\n\n"
),
"retrodeck": (
" Option B: Manual\n"
" ----------------\n"
" 1. Open Dolphin file manager\n"
" 2. Show hidden files (Ctrl+H)\n"
" 3. Navigate to ~/retrodeck/\n"
" 4. Extract all files from this archive directly into ~/retrodeck/\n"
" (the archive already holds the bios/ and roms/ folders)\n\n"
" NOTE: RetroDECK uses its own BIOS checker. After\n"
" copying, open RetroDECK > Tools > BIOS Checker to\n"
" verify everything is detected.\n\n"
),
"retrobat": (
" Option B: Manual\n"
" ----------------\n"
" 1. Open your RetroBat installation folder\n"
" 2. Navigate to the bios\\ subfolder\n"
" (default: C:\\RetroBat\\bios\\)\n"
" 3. Extract all files from this archive directly into your bios\\ folder\n"
" 4. Overwrite if asked\n\n"
),
"bizhawk": (
" Option B: Manual\n"
" ----------------\n"
" 1. Open the folder you extracted BizHawk to, the one holding\n"
" EmuHawk.exe (Windows) or EmuHawkMono.sh (Linux)\n"
" 2. Navigate to its Firmware subfolder\n"
" 3. Extract all files from this archive directly into your Firmware folder\n"
" 4. In BizHawk: Config > Paths > Firmware should\n"
" point to this folder\n\n"
),
# RomM 5.4 places firmware by filesystem.structure.firmware in config.yml,
# bios/{platform} by default, and suggests {platform}/bios for a library
# laid out as {platform}/roms (rommapp/romm 68f71259,
# backend/config/config_manager.py:58,101-109,1105-1124).
"romm": (
" Option B: Manual\n"
" ----------------\n"
" The archive holds one folder per RomM platform (psx/, gba/, ...).\n"
" Where they go depends on filesystem.structure.firmware in RomM's\n"
" config.yml, read relative to the library folder:\n\n"
" - Not set, or \"bios/{platform}\" (the default): extract all files\n"
" from this archive directly into library/bios/\n"
" - \"{platform}/bios\" (the layout RomM suggests for a library kept as\n"
" {platform}/roms): each folder goes under its platform, psx/ into\n"
" library/psx/bios/, gba/ into library/gba/bios/, and so on.\n"
" The installer reads config.yml and does this itself. Run from\n"
" the host of a container, --romm-firmware \"{platform}/bios\" says it.\n\n"
" Then restart the RomM service to detect new files.\n\n"
),
"retropie": (
" Option B: Via network share\n"
" --------------------------\n"
" 1. On your PC, open: \\\\RETROPIE\\bios\\\n"
" 2. Extract all files from this archive directly into that folder\n\n"
" Option C: Via SSH\n"
" -----------------\n"
" 1. SSH into your Pi: ssh pi@retropie\n"
" 2. Copy files to ~/RetroPie/BIOS/\n\n"
" NOTE: the BIOS folder is on the card's Linux partition,\n"
" which Windows and macOS cannot open: copying through the\n"
" card works from a Linux computer only\n"
" (<rootfs>/home/pi/RetroPie/BIOS/).\n\n"
),
"lakka": (
" Lakka reads its BIOS files from /storage/system/.\n\n"
" Option B: Via network share\n"
" --------------------------\n"
" 1. On Lakka: Settings > Services > Samba Enable\n"
" 2. On your PC, open the System share: \\\\LAKKA\\System\\\n"
" (macOS and Linux: smb://lakka/System)\n"
" 3. Extract all files from this archive directly into that share\n\n"
" Option C: Via SSH\n"
" -----------------\n"
" 1. On Lakka: Settings > Services > SSH Enable\n"
" 2. Copy the files to /storage/system/ (scp -r * root@lakka:/storage/system/)\n\n"
),
}
def _build_readme(
platform_name: str,
platform_display: str,
@@ -19,7 +232,8 @@ def _build_readme(
undecidable_slots: list[str] | None = None,
narrowings: list[tuple[str, str]] | None = None,
system_filter: list[str] | None = None,
bios_paths: list[str] | None = None,
detect: list[dict] | None = None,
standalone_copies: list[dict] | None = None,
) -> str:
"""Build a personalized step-by-step README for each platform pack."""
narrowings = narrowings or []
@@ -31,195 +245,12 @@ def _build_readme(
f"{sep}\n\n"
)
guides: dict[str, str] = {
"retroarch": (
"INSTALLATION GUIDE\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Linux, macOS, Steam Deck: run this in a terminal:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" Windows: run this in PowerShell:\n\n"
" irm https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1 | iex\n\n"
" The script auto-detects your RetroArch install and copies\n"
" files to the correct location.\n\n"
" Option B: Manual (PC)\n"
" ---------------------\n"
" 1. Find your RetroArch system directory:\n"
" - RetroArch > Settings > Directory > System/BIOS\n"
" - Default: retroarch/system/\n"
" 2. Extract all files from this archive directly into your system directory\n"
" 3. Overwrite if asked\n\n"
" Option C: Manual (handheld / SD card)\n"
" -------------------------------------\n"
" Anbernic, Retroid, Miyoo, Trimui, etc.:\n"
" 1. Connect your SD card to your PC\n"
" 2. Find the BIOS folder (usually BIOS/ or system/)\n"
" 3. Extract all files from this archive directly into that folder\n"
" 4. Eject SD card and reboot your device\n\n"
" Common paths by device:\n"
" Anbernic (ArkOS/JELOS): BIOS/\n"
" Retroid (RetroArch): RetroArch/system/\n"
" Miyoo Mini (Onion OS): BIOS/\n"
" Steam Deck (RetroArch): ~/.config/retroarch/system/\n\n"
),
"batocera": (
"INSTALLATION GUIDE\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Open a terminal (F1 from Batocera menu) and run:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" Option B: Manual (network share)\n"
" --------------------------------\n"
" 1. On your PC, open the Batocera network share:\n"
" - Windows: \\\\BATOCERA\\share\\bios\\\n"
" - Mac/Linux: smb://batocera/share/bios/\n"
" 2. Extract all files from this archive directly into the share\n"
" 3. Overwrite if asked\n\n"
" Option C: Manual (SD card)\n"
" --------------------------\n"
" 1. Put the SD card in your PC\n"
" 2. Navigate to /userdata/bios/ on the SHARE partition\n"
" 3. Extract all files from this archive directly into that folder\n\n"
" NOTE: Dreamcast flash memory is named dc_nvmem.bin\n"
" (if your setup asks for dc_flash.bin, same file).\n\n"
),
"recalbox": (
"INSTALLATION GUIDE\n\n"
" Option A: Automatic\n"
" -------------------\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" Option B: Manual (network share)\n"
" --------------------------------\n"
" 1. On your PC, open the Recalbox network share:\n"
" - Windows: \\\\RECALBOX\\share\\bios\\\n"
" - Mac/Linux: smb://recalbox/share/bios/\n"
" 2. Extract all files from this archive directly into the share\n\n"
" Option C: Manual (SD card)\n"
" --------------------------\n"
" 1. Put the SD card in your PC\n"
" 2. Navigate to /recalbox/share/bios/\n"
" 3. Extract all files from this archive directly into that folder\n\n"
),
"emudeck": (
"INSTALLATION GUIDE (Steam Deck / Linux)\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Open Konsole (or any terminal) and run:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" The script places the BIOS files. To also copy the keys\n"
" into the standalone emulators it finds, run:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh -s -- --standalone-copies\n\n"
" Option B: Manual\n"
" ----------------\n"
" 1. Open Dolphin file manager\n"
" 2. Navigate to ~/Emulation/bios/\n"
" 3. Extract all files from this archive directly into ~/Emulation/bios/\n\n"
" STANDALONE EMULATORS (extra step)\n"
" Switch and 3DS emulators need keys in their own folders:\n"
" prod.keys -> ~/.local/share/yuzu/keys/\n"
" prod.keys -> ~/.local/share/eden/keys/\n"
" prod.keys -> ~/.config/Ryujinx/system/\n"
" aes_keys.txt -> ~/.local/share/azahar-emu/sysdata/\n"
" The installer copies them with --standalone-copies.\n\n"
),
"retrodeck": (
"INSTALLATION GUIDE (Steam Deck / Linux)\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Open Konsole (or any terminal) and run:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" Option B: Manual\n"
" ----------------\n"
" 1. Open Dolphin file manager\n"
" 2. Show hidden files (Ctrl+H)\n"
" 3. Navigate to ~/retrodeck/\n"
" 4. Extract all files from this archive directly into ~/retrodeck/\n"
" (the archive already holds the bios/ and roms/ folders)\n\n"
" NOTE: RetroDECK uses its own BIOS checker. After\n"
" copying, open RetroDECK > Tools > BIOS Checker to\n"
" verify everything is detected.\n\n"
),
"retrobat": (
"INSTALLATION GUIDE (Windows)\n\n"
" Option A: Automatic (recommended)\n"
" ---------------------------------\n"
" Open PowerShell and run:\n\n"
" irm https://raw.githubusercontent.com/Abdess/retrobios/main/install.ps1 | iex\n\n"
" Option B: Manual\n"
" ----------------\n"
" 1. Open your RetroBat installation folder\n"
" 2. Navigate to the bios\\ subfolder\n"
" (default: C:\\RetroBat\\bios\\)\n"
" 3. Extract all files from this archive directly into your bios\\ folder\n"
" 4. Overwrite if asked\n\n"
),
"bizhawk": (
"INSTALLATION GUIDE\n\n"
" 1. Open the folder you extracted BizHawk to, the one holding\n"
" EmuHawk.exe (Windows) or EmuHawkMono.sh (Linux)\n"
" 2. Navigate to its Firmware subfolder\n"
" 3. Extract all files from this archive directly into your Firmware folder\n"
" 4. In BizHawk: Config > Paths > Firmware should\n"
" point to this folder\n\n"
),
"romm": (
"INSTALLATION GUIDE (RomM server)\n\n"
" 1. Locate your RomM library folder\n"
" 2. Navigate to its bios/ folder (library/bios/)\n"
" 3. Extract all files from this archive directly into that folder\n"
" 4. Restart the RomM service to detect new files\n\n"
),
"retropie": (
"INSTALLATION GUIDE (Raspberry Pi)\n\n"
" Option A: Automatic\n"
" -------------------\n"
" On the Pi (F4 from EmulationStation, or over SSH), run:\n\n"
" curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh\n\n"
" Option B: Via network share\n"
" --------------------------\n"
" 1. On your PC, open: \\\\RETROPIE\\bios\\\n"
" 2. Extract all files from this archive directly into that folder\n\n"
" Option C: Via SSH\n"
" -----------------\n"
" 1. SSH into your Pi: ssh pi@retropie\n"
" 2. Copy files to ~/RetroPie/BIOS/\n\n"
" NOTE: the BIOS folder is on the card's Linux partition,\n"
" which Windows and macOS cannot open: copying through the\n"
" card works from a Linux computer only\n"
" (<rootfs>/home/pi/RetroPie/BIOS/).\n\n"
),
}
guides["lakka"] = (
"INSTALLATION GUIDE (Lakka)\n\n"
" Lakka reads its BIOS files from /storage/system/.\n\n"
" Option A: Via network share\n"
" --------------------------\n"
" 1. On Lakka: Settings > Services > Samba Enable\n"
" 2. On your PC, open the System share: \\\\LAKKA\\System\\\n"
" (macOS and Linux: smb://lakka/System)\n"
" 3. Extract all files from this archive directly into that share\n\n"
" Option B: Via SSH\n"
" -----------------\n"
" 1. On Lakka: Settings > Services > SSH Enable\n"
" 2. Copy the files to /storage/system/ (scp -r * root@lakka:/storage/system/)\n\n"
)
# A platform without a written guide is told where its files go by the
# registry, which the installer reads too: "your BIOS directory" sent
# MiSTer users looking for a folder MiSTer does not have.
targets = [path for path in bios_paths or [] if path]
where = (
" or ".join(f"{path}/" for path in targets)
if targets else "your BIOS directory"
)
guide = guides.get(
platform_name,
(
f"INSTALLATION\n\n"
f" 1. Extract all files from this archive directly into {where}\n"
f" 2. Overwrite if asked\n\n"
),
detect = list(detect or [])
bios_paths = [str(rule.get("bios_path", "")) for rule in detect]
guide = (
"INSTALLATION GUIDE\n\n"
+ _automatic_section(platform_display, detect, standalone_copies)
+ MANUAL_GUIDES.get(platform_name, _generic_manual(bios_paths))
)
if regions: