diff --git a/install.ps1 b/install.ps1 index c3a913b1..276fb492 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,6 +1,6 @@ # RetroBIOS installer for Windows (PowerShell 5+, no Python required) $ErrorActionPreference = "Stop" -$baseUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main" +$baseUrl = if ($env:RETROBIOS_BASE_URL) { $env:RETROBIOS_BASE_URL } else { "https://raw.githubusercontent.com/Abdess/retrobios/main" } $releaseUrl = "https://github.com/Abdess/retrobios/releases/download/large-files" $platform = $null @@ -17,22 +17,179 @@ if (Test-Path $emudeckSettings) { } } +# Expand the notations RetroArch writes into its config values: +# '~' is the home directory and ':' the application directory, both dropping +# two leading characters (libretro-common/file/file_path.c). +function Expand-RetroArchPath { + param([string]$Value, [string]$AppDir) + if ($Value.StartsWith('~')) { return Join-Path $env:USERPROFILE $Value.Substring(2) } + if ($Value.StartsWith(':')) { return Join-Path $AppDir $Value.Substring(2) } + return [Environment]::ExpandEnvironmentVariables($Value) +} + +function Get-RetroArchSystemDir { + param([string]$CfgPath, [string]$AppDir) + if (-not (Test-Path $CfgPath)) { return $null } + foreach ($line in Get-Content $CfgPath) { + if ($line -match '^\s*system_directory\s*=\s*"?([^"]*)"?') { + $val = $Matches[1].Trim() + if (-not $val -or $val -eq "default") { return Join-Path $AppDir "system" } + return Expand-RetroArchPath -Value $val -AppDir $AppDir + } + } + return $null +} + # Detect RetroArch if (-not $platform) { $raCfg = Join-Path $env:APPDATA "RetroArch\retroarch.cfg" if (Test-Path $raCfg) { $platform = "retroarch" - $biosPath = Join-Path $env:APPDATA "RetroArch\system" - foreach ($line in Get-Content $raCfg) { - if ($line -match '^\s*system_directory\s*=\s*"?([^"]+)"?') { - $val = $Matches[1].Trim() - if ($val -and $val -ne "default") { - $biosPath = $val + $raRoot = Join-Path $env:APPDATA "RetroArch" + $found = Get-RetroArchSystemDir -CfgPath $raCfg -AppDir $raRoot + $biosPath = if ($found) { $found } else { Join-Path $raRoot "system" } + Write-Host "Found RetroArch at $biosPath" + } +} + +# Locate LaunchBox. It installs wherever the user points its installer and +# writes no uninstall registry key, so the Start menu shortcut is the only +# record of that choice. +function Get-LaunchBoxRoot { + $candidates = @() + $lnk = Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs\LaunchBox\LaunchBox.lnk" + if (Test-Path $lnk) { + $bytes = [System.IO.File]::ReadAllBytes($lnk) + $text = [System.Text.Encoding]::ASCII.GetString($bytes) + if ($text -match '([A-Za-z]:\\[ -~]{0,260}?LaunchBox\.exe)') { + # The shortcut points at Core\LaunchBox.exe + $exeDir = Split-Path $Matches[1] -Parent + $candidates += (Split-Path $exeDir -Parent) + $candidates += $exeDir + } + } + $candidates += (Join-Path $env:USERPROFILE "LaunchBox") + foreach ($root in $candidates) { + if (Test-Path (Join-Path $root "Data\Emulators.xml")) { return $root } + } + return $null +} + +# Map each emulator LaunchBox knows about to its installation directory. +# ApplicationPath is either absolute or relative to the LaunchBox root. +function Get-LaunchBoxEmulators { + param([string]$Root) + $map = @{} + if (-not $Root) { return $map } + $xmlPath = Join-Path $Root "Data\Emulators.xml" + if (-not (Test-Path $xmlPath)) { return $map } + $doc = $null + try { $doc = [xml](Get-Content $xmlPath -Raw) } catch { return $map } + foreach ($emu in $doc.LaunchBox.Emulator) { + $app = "$($emu.ApplicationPath)" + if (-not $app) { continue } + $exe = if ($app -match '^[A-Za-z]:' -or $app.StartsWith('\')) { $app } else { Join-Path $Root $app } + $dir = Split-Path $exe -Parent + $name = (Split-Path $exe -Leaf).ToLower() + if ((Test-Path $dir) -and -not $map.ContainsKey($name)) { $map[$name] = $dir } + } + return $map +} + +# BIOS directories LaunchBox itself computes for the emulators it manages. +function Get-LaunchBoxBiosDirs { + $dirs = @{} + $root = Get-LaunchBoxRoot + if (-not $root) { return $dirs } + $emulators = Get-LaunchBoxEmulators -Root $root + $documents = Join-Path $env:USERPROFILE "Documents" + + if ($emulators.ContainsKey("pcsx2.exe")) { + # portable.ini or portable.txt next to the executable moves the data + # root there, portable.txt naming a subfolder when it holds one + # (EmuFolders in pcsx2/Pcsx2Config.cpp). Otherwise it is Documents. + $emuDir = $emulators["pcsx2.exe"] + $portableTxt = Join-Path $emuDir "portable.txt" + $portable = (Test-Path (Join-Path $emuDir "portable.ini")) -or (Test-Path $portableTxt) + if ($portable) { + $subpath = if (Test-Path $portableTxt) { (Get-Content $portableTxt -Raw).Trim() } else { "" } + $dataRoot = if ($subpath) { Join-Path $emuDir $subpath } else { $emuDir } + } else { + $dataRoot = Join-Path $documents "PCSX2" + } + $ini = Join-Path $dataRoot "inis\PCSX2.ini" + $bios = Join-Path $dataRoot "bios" + if (Test-Path $ini) { + foreach ($line in Get-Content $ini) { + if ($line.StartsWith("Bios = ")) { + $val = $line.Substring(7).Trim() + $bios = if ($val -match '^[A-Za-z]:' -or $val.StartsWith('\')) { $val } else { Join-Path $dataRoot $val } + break + } + } + } + $dirs["pcsx2"] = $bios + } + + if ($emulators.ContainsKey("xemu.exe")) { + # bootrom_path and flashrom_path name a file whose directory holds + # the images; both default to bios/ under the executable. + $emuDir = $emulators["xemu.exe"] + $toml = Join-Path $emuDir "xemu.toml" + if (-not (Test-Path $toml)) { $toml = Join-Path $env:APPDATA "xemu\xemu\xemu.toml" } + $bios = Join-Path $emuDir "bios" + if (Test-Path $toml) { + foreach ($line in Get-Content $toml) { + if ($line.StartsWith("bootrom_path") -or $line.StartsWith("flashrom_path")) { + $parts = $line.Split("'") + if ($parts.Count -gt 1 -and (Test-Path $parts[1])) { + $bios = Split-Path $parts[1] -Parent + break + } + } + } + } + $dirs["xemu"] = $bios + } + + if ($emulators.ContainsKey("dolphin.exe")) { + # portable.txt beside the executable moves the user directory to User + # (SetUserDirectory in Source/Core/UICommon/UICommon.cpp) + $emuDir = $emulators["dolphin.exe"] + if (Test-Path (Join-Path $emuDir "portable.txt")) { + $dirs["dolphin"] = Join-Path $emuDir "User" + } + } + return $dirs +} + +# Detect LaunchBox (portable RetroArch referenced in Data\Emulators.xml) +if (-not $platform) { + $lbRoot = Get-LaunchBoxRoot + $lbXml = if ($lbRoot) { Join-Path $lbRoot "Data\Emulators.xml" } else { $null } + if ($lbXml -and (Test-Path $lbXml)) { + $lb = $null + try { $lb = [xml](Get-Content $lbXml -Raw) } catch { Write-Host "Warning: could not parse $lbXml" } + if ($lb) { + foreach ($emu in $lb.LaunchBox.Emulator) { + $app = "$($emu.ApplicationPath)".Replace('\', '/') + if ($app -notmatch 'retroarch\.exe$') { continue } + $lbRoot = Split-Path (Split-Path $lbXml -Parent) -Parent + if ($app -match '^[A-Za-z]:' -or $app.StartsWith('/')) { + $exe = $app + } else { + $exe = Join-Path $lbRoot $app + } + $raDir = Split-Path $exe -Parent + if (Test-Path $raDir) { + $platform = "retroarch" + $found = Get-RetroArchSystemDir -CfgPath (Join-Path $raDir "retroarch.cfg") -AppDir $raDir + $biosPath = if ($found) { $found } else { Join-Path $raDir "system" } + Write-Host "Found LaunchBox with RetroArch at $biosPath" + break } - break } } - Write-Host "Found RetroArch at $biosPath" } } @@ -74,11 +231,6 @@ foreach ($f in $files) { Write-Host " $upToDate/$($files.Count) up to date, $($toDownload.Count) to download" -if ($toDownload.Count -eq 0) { - Write-Host "`nDone. 0 downloaded, $upToDate already up to date." - exit 0 -} - $downloaded = 0 $errors = 0 $total = $toDownload.Count @@ -129,6 +281,7 @@ foreach ($f in $toDownload) { # Standalone emulator copies if ($manifest.standalone_copies) { Write-Host "`nStandalone emulators:" + $extraDirs = Get-LaunchBoxBiosDirs foreach ($entry in $manifest.standalone_copies) { if ($entry.note) { $detectPaths = @() @@ -156,6 +309,12 @@ if ($manifest.standalone_copies) { if ($entry.targets -and $entry.targets.windows) { $targetDirs = $entry.targets.windows } + if ($entry.emulator -and $extraDirs.ContainsKey($entry.emulator)) { + $extra = $extraDirs[$entry.emulator] + $subdir = if ($entry.file) { Split-Path $entry.file -Parent } else { "" } + if ($subdir) { $extra = Join-Path $extra $subdir } + $targetDirs += $extra + } foreach ($td in $targetDirs) { $expanded = [Environment]::ExpandEnvironmentVariables($td) if (-not (Test-Path $expanded)) { continue } @@ -167,6 +326,7 @@ if ($manifest.standalone_copies) { } catch { Write-Host " $($s.Name) -> $expanded FAILED" -ForegroundColor Red } + } } } diff --git a/install.py b/install.py index 6841f9f7..2514bf74 100644 --- a/install.py +++ b/install.py @@ -19,13 +19,15 @@ import hashlib import json import os import platform +import re import shutil import sys import tempfile import urllib.error import urllib.parse import urllib.request -from pathlib import Path +import xml.etree.ElementTree as ET +from pathlib import Path, PurePosixPath BASE_URL = os.environ.get( "RETROBIOS_BASE_URL", @@ -98,10 +100,32 @@ def _parse_os_release() -> dict[str, str]: return result -def _parse_retroarch_system_dir(cfg_path: Path) -> Path | None: - """Parse system_directory from retroarch.cfg.""" +def _expand_retroarch_path(value: str, app_dir: Path) -> Path: + """Expand the notations RetroArch writes into its config values. + + fill_pathname_expand_special (libretro-common/file/file_path.c) maps a + leading '~' to the home directory and a leading ':' to the application + directory, dropping the two leading characters in both cases. + """ + if value[:1] == "~": + return Path(str(Path.home())) / value[2:] + if value[:1] == ":": + return app_dir / value[2:] + return Path(os.path.expandvars(os.path.expanduser(value))) + + +def _parse_retroarch_system_dir( + cfg_path: Path, app_dir: Path | None = None +) -> Path | None: + """Parse system_directory from retroarch.cfg. + + app_dir is RetroArch's application directory, used to expand ':' values. + It defaults to the directory holding the config, which is where a portable + install keeps both. + """ if not cfg_path.exists(): return None + app_dir = app_dir or cfg_path.parent try: for line in cfg_path.read_text(encoding="utf-8", errors="replace").splitlines(): line = line.strip() @@ -109,9 +133,8 @@ def _parse_retroarch_system_dir(cfg_path: Path) -> Path | None: _, _, value = line.partition("=") value = value.strip().strip('"').strip("'") if not value or value == "default": - return cfg_path.parent / "system" - value = os.path.expandvars(os.path.expanduser(value)) - return Path(value) + return app_dir / "system" + return _expand_retroarch_path(value, app_dir) except OSError: pass return None @@ -251,6 +274,206 @@ def _detect_embedded() -> list[tuple[str, Path]]: return found +def _lnk_target(lnk_path: Path, exe_name: str) -> Path | None: + """Read the target path of a Windows shortcut. + + Shortcuts embed the target as a plain local path; scanning for it avoids + parsing the whole .lnk structure for the one field that matters here. + """ + try: + if lnk_path.stat().st_size > 64 * 1024: + return None + blob = lnk_path.read_bytes() + except OSError: + return None + pattern = re.compile( + rb"[A-Za-z]:\\[ -~]{0,260}?" + re.escape(exe_name.encode()), re.IGNORECASE + ) + match = pattern.search(blob) + if not match: + return None + return Path(match.group().decode("ascii", "replace").replace("\\", "/")) + + +def launchbox_root(os_type: str) -> Path | None: + """Locate the LaunchBox installation directory. + + LaunchBox installs wherever the user points its installer and writes no + uninstall registry key, so the Start menu shortcut is the only record of + that choice. Everything else falls back to the default location. + """ + if os_type not in ("windows", "wsl"): + return None + candidates: list[Path] = [] + appdata = os.environ.get("APPDATA", "") + if appdata: + lnk = ( + Path(appdata) + / "Microsoft" + / "Windows" + / "Start Menu" + / "Programs" + / "LaunchBox" + / "LaunchBox.lnk" + ) + if lnk.exists(): + target = _lnk_target(lnk, "LaunchBox.exe") + if target: + # The shortcut points at Core/LaunchBox.exe + candidates.append(target.parent.parent) + candidates.append(target.parent) + userprofile = os.environ.get("USERPROFILE", "") + if userprofile: + candidates.append(Path(userprofile) / "LaunchBox") + for root in candidates: + if (root / "Data" / "Emulators.xml").exists(): + return root + return None + + +def launchbox_emulators(emulators_xml: Path) -> dict[str, Path]: + """Map each emulator LaunchBox knows about to its installation directory. + + LaunchBox stores its emulator list in Data/Emulators.xml; ApplicationPath + is either absolute or relative to the LaunchBox root directory. Keys are + the lowercase executable names. + """ + found: dict[str, Path] = {} + if not emulators_xml.exists(): + return found + try: + if emulators_xml.stat().st_size > 10 * 1024 * 1024: + return found + raw = emulators_xml.read_text(encoding="utf-8", errors="replace") + # ElementTree expands internal entities; LaunchBox writes no doctype + if " Path | None: + """Resolve the system dir of a RetroArch referenced by LaunchBox. + + Mirrors the plugin's UpdateSystemPath: read system_directory from the + retroarch.cfg sitting next to the executable, else assume system/. + """ + emu_dir = launchbox_emulators(emulators_xml).get("retroarch.exe") + if emu_dir is None: + return None + system_dir = _parse_retroarch_system_dir(emu_dir / "retroarch.cfg", emu_dir) + return system_dir or emu_dir / "system" + + +def _pcsx2_bios_dir(emu_dir: Path) -> Path: + """Resolve the BIOS folder of a PCSX2 installed under LaunchBox. + + EmuFolders (pcsx2/Pcsx2Config.cpp) enters portable mode when portable.ini + or portable.txt sits next to the executable, and then takes the data root + from the contents of portable.txt, relative to the executable. Otherwise + the data root is Documents/PCSX2. Bios defaults to "bios" below it and the + Bios key of PCSX2.ini overrides that. + """ + documents = Path(os.environ.get("USERPROFILE", str(Path.home()))) / "Documents" + portable_txt = emu_dir / "portable.txt" + portable = (emu_dir / "portable.ini").exists() or portable_txt.exists() + if portable: + subpath = "" + try: + subpath = portable_txt.read_text( + encoding="utf-8", errors="replace" + ).strip() + except OSError: + pass + data_root = emu_dir / subpath if subpath else emu_dir + else: + data_root = documents / "PCSX2" + ini = data_root / "inis" / "PCSX2.ini" + try: + for line in ini.read_text(encoding="utf-8", errors="replace").splitlines(): + if line.startswith("Bios = "): + value = line[len("Bios = "):].strip() + candidate = Path(value) + return candidate if candidate.is_absolute() else data_root / value + except OSError: + pass + return data_root / "bios" + + +def _xemu_bios_dir(emu_dir: Path) -> Path: + """Resolve the BIOS folder of a xemu managed by LaunchBox. + + Follows the plugin: xemu.toml next to the executable else the roaming + copy, with bootrom_path and flashrom_path naming a file whose directory + holds the images. Both default to bios/ under the executable. + """ + appdata = os.environ.get("APPDATA", "") + toml = emu_dir / "xemu.toml" + if not toml.exists() and appdata: + toml = Path(appdata) / "xemu" / "xemu" / "xemu.toml" + if toml.exists(): + try: + for line in toml.read_text(encoding="utf-8", errors="replace").splitlines(): + if line.startswith(("bootrom_path", "flashrom_path")): + parts = line.split("'") + if len(parts) > 1 and Path(parts[1]).exists(): + return Path(parts[1]).parent + except OSError: + pass + return emu_dir / "bios" + + +def launchbox_bios_dirs(root: Path) -> dict[str, Path]: + """Map emulator ids to the BIOS directory LaunchBox expects for them. + + Only emulators whose destination LaunchBox itself computes are listed; + the rest keep the default locations declared in the manifest. + """ + emulators = launchbox_emulators(root / "Data" / "Emulators.xml") + dirs: dict[str, Path] = {} + emu_dir = emulators.get("pcsx2.exe") + if emu_dir: + dirs["pcsx2"] = _pcsx2_bios_dir(emu_dir) + emu_dir = emulators.get("xemu.exe") + if emu_dir: + dirs["xemu"] = _xemu_bios_dir(emu_dir) + emu_dir = emulators.get("dolphin.exe") + if emu_dir and (emu_dir / "portable.txt").exists(): + # portable.txt beside the executable moves the user directory to + # User (SetUserDirectory in Source/Core/UICommon/UICommon.cpp, + # PORTABLE_USER_DIR in Source/Core/Common/CommonPaths.h) + dirs["dolphin"] = emu_dir / "User" + return dirs + + +def detect_frontends(os_type: str, home: Path | None = None) -> list[str]: + """Detect frontends that reference emulators without owning a BIOS dir. + + ES-DE resolves its application data directory to $ESDE_APPDATA_DIR or + /ES-DE (es-core FileSystemUtil.cpp getAppDataDirectory). + """ + home = home or Path.home() + frontends: list[str] = [] + esde_env = os.environ.get("ESDE_APPDATA_DIR", "") + if (esde_env and Path(esde_env).is_dir()) or (home / "ES-DE").is_dir(): + frontends.append("esde") + if launchbox_root(os_type) is not None: + frontends.append("launchbox") + return frontends + + def detect_platforms(os_type: str) -> list[tuple[str, Path]]: """Detect installed emulator platforms and their BIOS directories.""" found: list[tuple[str, Path]] = [] @@ -321,6 +544,15 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]: if ra_dir: found.append(("retroarch", ra_dir)) + # Portable RetroArch referenced by LaunchBox + lb_root = launchbox_root(os_type) + if lb_root and not any(name == "retroarch" for name, _ in found): + system_dir = _launchbox_retroarch_system_dir( + lb_root / "Data" / "Emulators.xml" + ) + if system_dir: + found.append(("retroarch", system_dir)) + return found @@ -480,7 +712,8 @@ def download_files( def do_standalone_copies( - manifest: dict, bios_path: Path, os_type: str + manifest: dict, bios_path: Path, os_type: str, + extra_dirs: dict[str, Path] | None = None, ) -> tuple[int, int]: """Copy BIOS files to standalone emulator directories. @@ -490,6 +723,10 @@ def do_standalone_copies( - note: informational message when detect path exists - WSL fallback to linux targets + extra_dirs maps the emulator id of an entry to a root that replaces the + default per-OS locations, for setups such as LaunchBox that keep their + emulators outside them. The layout below the root is the same. + Returns (copied_count, skipped_count). """ from fnmatch import fnmatch @@ -532,8 +769,17 @@ def do_standalone_copies( if not targets and os_type == "wsl": targets = entry.get("targets", {}).get("linux", []) - for target_dir_str in targets: - target_dir = Path(os.path.expandvars(os.path.expanduser(target_dir_str))) + target_dirs = [ + Path(os.path.expandvars(os.path.expanduser(t))) for t in targets + ] + extra_root = (extra_dirs or {}).get(entry.get("emulator", "")) + if extra_root is not None: + subdir = PurePosixPath(entry.get("file", "")).parent + extra = extra_root if str(subdir) == "." else extra_root / subdir + if extra not in target_dirs: + target_dirs.append(extra) + + for target_dir in target_dirs: if not target_dir.is_dir(): skipped += len(sources) continue @@ -675,6 +921,11 @@ def main() -> None: platforms = [("retroarch", args.dest)] else: print("Detecting platform...") + frontends = detect_frontends(os_type) + if "esde" in frontends: + print(" Found ES-DE (BIOS files go to each emulator, not to ES-DE itself)") + if "launchbox" in frontends: + print(" Found LaunchBox") platforms = detect_platforms(os_type) if not platforms: print(" No supported platform detected.") @@ -755,7 +1006,11 @@ def main() -> None: # Standalone copies if manifest.get("standalone_copies") and not args.check: print("\nStandalone emulators:") - copied, skipped = do_standalone_copies(manifest, bios_path, os_type) + lb_root = launchbox_root(os_type) + extra_dirs = launchbox_bios_dirs(lb_root) if lb_root else None + copied, skipped = do_standalone_copies( + manifest, bios_path, os_type, extra_dirs + ) if copied or skipped: print(f" {copied} copied, {skipped} skipped (dir not found)") diff --git a/install/batocera.json b/install/batocera.json index f0a007c5..4276cd5d 100644 --- a/install/batocera.json +++ b/install/batocera.json @@ -4,7 +4,7 @@ "platform": "batocera", "display_name": "Batocera", "version": "1.0", - "generated": "2026-08-07T16:30:30Z", + "generated": "2026-08-08T00:30:24Z", "base_destination": "bios", "detect": [ { @@ -97,6 +97,7 @@ }, { "pattern": "ps2-*.bin", + "emulator": "pcsx2", "targets": { "linux": [ "$HOME/.config/PCSX2/bios", @@ -123,6 +124,7 @@ }, { "file": "GC/USA/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/USA" @@ -137,6 +139,7 @@ }, { "file": "GC/EUR/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/EUR" @@ -151,6 +154,7 @@ }, { "file": "GC/JAP/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/JAP" @@ -165,6 +169,7 @@ }, { "file": "dsp_rom.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -179,6 +184,7 @@ }, { "file": "dsp_coef.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -230,8 +236,8 @@ } } ], - "total_files": 1600, - "total_size": 2868353323, + "total_files": 1601, + "total_size": 2869019853, "files": [ { "dest": "panafz1.bin", @@ -9571,6 +9577,15 @@ "PokeMini" ] }, + { + "dest": "PPSSPP/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": [ + "PPSSPP" + ] + }, { "dest": "prboom.wad", "sha1": "5f4aed208301449c2e9514edfd325fe9dead76fa", diff --git a/install/bizhawk.json b/install/bizhawk.json index c7d66d55..1ec5d81e 100644 --- a/install/bizhawk.json +++ b/install/bizhawk.json @@ -4,7 +4,7 @@ "platform": "bizhawk", "display_name": "BizHawk", "version": "1.0", - "generated": "2026-08-07T16:15:44Z", + "generated": "2026-08-07T18:49:15Z", "base_destination": "Firmware", "detect": [ { @@ -19,8 +19,8 @@ } ], "standalone_copies": [], - "total_files": 538, - "total_size": 2548313700, + "total_files": 539, + "total_size": 2548980230, "files": [ { "dest": "panafz1.bin", @@ -3242,6 +3242,15 @@ "PicoDrive" ] }, + { + "dest": "PPSSPP/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": [ + "PPSSPP" + ] + }, { "dest": "prboom.wad", "sha1": "5f4aed208301449c2e9514edfd325fe9dead76fa", diff --git a/install/emudeck.json b/install/emudeck.json index 8af6d91e..f4de8695 100644 --- a/install/emudeck.json +++ b/install/emudeck.json @@ -4,7 +4,7 @@ "platform": "emudeck", "display_name": "EmuDeck", "version": "1.0", - "generated": "2026-08-06T18:09:38Z", + "generated": "2026-08-08T00:30:33Z", "base_destination": "bios", "detect": [ { @@ -110,6 +110,7 @@ }, { "pattern": "ps2-*.bin", + "emulator": "pcsx2", "targets": { "linux": [ "$HOME/.config/PCSX2/bios", @@ -136,6 +137,7 @@ }, { "file": "GC/USA/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/USA" @@ -150,6 +152,7 @@ }, { "file": "GC/EUR/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/EUR" @@ -164,6 +167,7 @@ }, { "file": "GC/JAP/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/JAP" @@ -178,6 +182,7 @@ }, { "file": "dsp_rom.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -192,6 +197,7 @@ }, { "file": "dsp_coef.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -243,8 +249,8 @@ } } ], - "total_files": 524, - "total_size": 1769405209, + "total_files": 525, + "total_size": 1770071739, "files": [ { "dest": "colecovision.rom", @@ -3591,6 +3597,15 @@ "Mupen64Plus-Next" ] }, + { + "dest": "PPSSPP/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": [ + "PPSSPP" + ] + }, { "dest": "PS3UPDAT.PUP", "sha1": "093f8698b54b78dcb701de2043f82639de51d63b", diff --git a/install/lakka.json b/install/lakka.json index 9d845d40..735ea481 100644 --- a/install/lakka.json +++ b/install/lakka.json @@ -4,7 +4,7 @@ "platform": "lakka", "display_name": "Lakka", "version": "1.0", - "generated": "2026-08-07T16:16:14Z", + "generated": "2026-08-07T18:49:47Z", "base_destination": "system", "detect": [ { @@ -15,8 +15,8 @@ } ], "standalone_copies": [], - "total_files": 1630, - "total_size": 4194971786, + "total_files": 1631, + "total_size": 4195638316, "files": [ { "dest": "3do_arcade_saot.bin", @@ -3303,6 +3303,13 @@ "repo_path": "bios/Sony/PlayStation 2/.variants/GameIndex.yaml.57369df9", "cores": null }, + { + "dest": "PPSSPP/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": null + }, { "dest": "ti83se.rom", "sha1": "8177bc6d5489d575cbfa9a004d097fc08c6f8c86", diff --git a/install/recalbox.json b/install/recalbox.json index 1f371c20..c31a9de1 100644 --- a/install/recalbox.json +++ b/install/recalbox.json @@ -4,7 +4,7 @@ "platform": "recalbox", "display_name": "Recalbox", "version": "1.0", - "generated": "2026-08-07T16:16:21Z", + "generated": "2026-08-08T01:20:56Z", "base_destination": "bios", "detect": [ { @@ -97,6 +97,7 @@ }, { "pattern": "ps2-*.bin", + "emulator": "pcsx2", "targets": { "linux": [ "$HOME/.config/PCSX2/bios", @@ -123,6 +124,7 @@ }, { "file": "GC/USA/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/USA" @@ -137,6 +139,7 @@ }, { "file": "GC/EUR/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/EUR" @@ -151,6 +154,7 @@ }, { "file": "GC/JAP/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/JAP" @@ -165,6 +169,7 @@ }, { "file": "dsp_rom.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -179,6 +184,7 @@ }, { "file": "dsp_coef.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -230,8 +236,8 @@ } } ], - "total_files": 1181, - "total_size": 2485059730, + "total_files": 1184, + "total_size": 2494020078, "files": [ { "dest": "3do/panafz1.bin", @@ -2641,6 +2647,240 @@ "repo_path": "bios/Microsoft/Xbox/mcpx_1.0.bin", "cores": null }, + { + "dest": "hiscore.dat", + "sha1": "7381472bf046126257e51a0124e4553282f020e5", + "size": 322458, + "repo_path": "bios/Arcade/Arcade/hiscore.dat", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "cheat.dat", + "sha1": "32fc78415114a976af9c2ee53ea13de9f40b55ee", + "size": 7971360, + "repo_path": "bios/Arcade/MAME/cheat.dat", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "pgm.zip", + "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", + "size": 2094636, + "repo_path": "bios/Arcade/MAME/pgm.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "megaplay.zip", + "sha1": "c9ee16e26e03496195a7bff151efbdd89da01204", + "size": 49978, + "repo_path": "bios/Arcade/Arcade/megaplay.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "megatech.zip", + "sha1": "be1bcca32794d0eb36c86ae32a2a43c92834cdc6", + "size": 24831, + "repo_path": "bios/Arcade/Arcade/megatech.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "decocass.zip", + "sha1": "1b33cf0e730a6cde96479b0face108b6931d433e", + "size": 22379, + "repo_path": "bios/Arcade/Arcade/decocass.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "playch10.zip", + "sha1": "125e74975da3f7ac643232210ab37c33e4ece9ca", + "size": 23388, + "repo_path": "bios/Arcade/Arcade/playch10.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "nss.zip", + "sha1": "68b780f40623e51a421faf374ae5a84a53a81279", + "size": 50536, + "repo_path": "bios/Arcade/Arcade/nss.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "skns.zip", + "sha1": "4257bd14b541fafbd555cb98ba079a3416a45934", + "size": 924762, + "repo_path": "bios/Arcade/Arcade/skns.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "konamigx.zip", + "sha1": "8cf0aa7f9dca4d77485e605fb0e2173a734633bf", + "size": 8712, + "repo_path": "bios/Arcade/Arcade/konamigx.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "konamigv.zip", + "sha1": "b4ff14ed8c3e37a4b45ce2d80c692961ffc88270", + "size": 81391, + "repo_path": "bios/Arcade/Arcade/konamigv.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "cvs.zip", + "sha1": "649d8af20ba0048fa993585960bf0305d50fc560", + "size": 3113, + "repo_path": "bios/Arcade/Arcade/cvs.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "cpzn1.zip", + "sha1": "6515a4f2f3f5864c32346c2fecdc3595205f1d86", + "size": 125571, + "repo_path": "bios/Arcade/Arcade/cpzn1.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "cpzn2.zip", + "sha1": "b18e23b8908e3e920ce971b8be168fa8baf1b3f7", + "size": 137827, + "repo_path": "bios/Arcade/Arcade/cpzn2.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "tps.zip", + "sha1": "f18270ba6e747de86bc7bd4f0f2c9c6f35335871", + "size": 138474, + "repo_path": "bios/Arcade/Arcade/tps.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "taitofx1.zip", + "sha1": "2975c1ac34a18f6d262c5cc7dcc223f096742075", + "size": 126160, + "repo_path": "bios/Arcade/Arcade/taitofx1.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "psarc95.zip", + "sha1": "3c1a40887171d71915487cdb630d2b7fee2417cc", + "size": 128759, + "repo_path": "bios/Arcade/Arcade/psarc95.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "atluspsx.zip", + "sha1": "a6ff77ddecbd0888c2323a51199cadd3202e759a", + "size": 126346, + "repo_path": "bios/Arcade/Arcade/atluspsx.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "atpsx.zip", + "sha1": "86ff54e5d15d754768fe7fd1ac6871fe1b49dba5", + "size": 126298, + "repo_path": "bios/Arcade/Arcade/atpsx.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "acpsx.zip", + "sha1": "5426d52e17e0ff9195fabbb42f704342e556d08e", + "size": 126167, + "repo_path": "bios/Arcade/Arcade/acpsx.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "hng64.zip", + "sha1": "98b92ece93d3e160c446a56ac859a8311333e36c", + "size": 41210, + "repo_path": "bios/Arcade/Arcade/hng64.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "ar_bios.zip", + "sha1": "df93e5943023cc8f7ddf0ffb956fd969e04a171a", + "size": 681333, + "repo_path": "bios/Arcade/Arcade/ar_bios.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "maxaflex.zip", + "sha1": "697551fcf9557ae33e31096b118a0c6769700a2e", + "size": 13673, + "repo_path": "bios/Arcade/Arcade/maxaflex.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "crysbios.zip", + "sha1": "7f2d1c4e00ce1824ff91459d8ff6b59b2fb4b746", + "size": 43480, + "repo_path": "bios/Arcade/Arcade/crysbios.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "macsbios.zip", + "sha1": "4e0202f8430cb4842184df7b5418e32620156c7b", + "size": 305290, + "repo_path": "bios/Arcade/Arcade/macsbios.zip", + "cores": [ + "AdvanceMAME" + ] + }, + { + "dest": "bctvidbs.zip", + "sha1": "5024b3bfd04ccd2061eb60d2eca254e8faf44f5c", + "size": 3753, + "repo_path": "bios/Arcade/Arcade/bctvidbs.zip", + "cores": [ + "AdvanceMAME" + ] + }, { "dest": "kick13.rom", "sha1": "90933936cce43ca9bc6bf375662c076b27e3c458", @@ -4472,15 +4712,6 @@ "ECWolf" ] }, - { - "dest": "fbneo/hiscore.dat", - "sha1": "7381472bf046126257e51a0124e4553282f020e5", - "size": 322458, - "repo_path": "bios/Arcade/Arcade/hiscore.dat", - "cores": [ - "FinalBurn Neo" - ] - }, { "dest": "fbneo/samples/donpachi.zip", "sha1": "d380fb29287eb7fc9ff901a7653ad40785f7deb1", @@ -5009,15 +5240,6 @@ "Hatari" ] }, - { - "dest": "pgm.zip", - "sha1": "c0c001ec80fa860857000f4cfc9844a28498a355", - "size": 2094636, - "repo_path": "bios/Arcade/MAME/pgm.zip", - "cores": [ - "MAME" - ] - }, { "dest": "lindbios.zip", "sha1": "65a2f2cee74c316d5f40b68deda66787609df353", @@ -5081,24 +5303,6 @@ "MAME" ] }, - { - "dest": "megaplay.zip", - "sha1": "c9ee16e26e03496195a7bff151efbdd89da01204", - "size": 49978, - "repo_path": "bios/Arcade/Arcade/megaplay.zip", - "cores": [ - "MAME" - ] - }, - { - "dest": "megatech.zip", - "sha1": "be1bcca32794d0eb36c86ae32a2a43c92834cdc6", - "size": 24831, - "repo_path": "bios/Arcade/Arcade/megatech.zip", - "cores": [ - "MAME" - ] - }, { "dest": "isgsm.zip", "sha1": "f590ccf688b4c05fa1da5c5dd92c224545170c3b", @@ -5117,33 +5321,6 @@ "MAME" ] }, - { - "dest": "decocass.zip", - "sha1": "1b33cf0e730a6cde96479b0face108b6931d433e", - "size": 22379, - "repo_path": "bios/Arcade/Arcade/decocass.zip", - "cores": [ - "MAME" - ] - }, - { - "dest": "playch10.zip", - "sha1": "125e74975da3f7ac643232210ab37c33e4ece9ca", - "size": 23388, - "repo_path": "bios/Arcade/Arcade/playch10.zip", - "cores": [ - "MAME" - ] - }, - { - "dest": "nss.zip", - "sha1": "68b780f40623e51a421faf374ae5a84a53a81279", - "size": 50536, - "repo_path": "bios/Arcade/Arcade/nss.zip", - "cores": [ - "MAME" - ] - }, { "dest": "sfcbox.zip", "sha1": "14311945b06a5d013d1df5ce961ab47fbd46fb2a", @@ -5198,24 +5375,6 @@ "MAME" ] }, - { - "dest": "konamigx.zip", - "sha1": "8cf0aa7f9dca4d77485e605fb0e2173a734633bf", - "size": 8712, - "repo_path": "bios/Arcade/Arcade/konamigx.zip", - "cores": [ - "MAME" - ] - }, - { - "dest": "konamigv.zip", - "sha1": "b4ff14ed8c3e37a4b45ce2d80c692961ffc88270", - "size": 81391, - "repo_path": "bios/Arcade/Arcade/konamigv.zip", - "cores": [ - "MAME" - ] - }, { "dest": "kviper.zip", "sha1": "78c8e1c3c033b65758b7e53a9346b44d037fea7f", @@ -5378,15 +5537,6 @@ "MAME" ] }, - { - "dest": "skns.zip", - "sha1": "4257bd14b541fafbd555cb98ba079a3416a45934", - "size": 924762, - "repo_path": "bios/Arcade/Arcade/skns.zip", - "cores": [ - "MAME" - ] - }, { "dest": "mac2bios.zip", "sha1": "829b2e10aff75a0857feb2b601f893db29877994", @@ -5396,15 +5546,6 @@ "MAME" ] }, - { - "dest": "macsbios.zip", - "sha1": "4e0202f8430cb4842184df7b5418e32620156c7b", - "size": 305290, - "repo_path": "bios/Arcade/Arcade/macsbios.zip", - "cores": [ - "MAME" - ] - }, { "dest": "3dobios.zip", "sha1": "de463b0577dfd1027bf7de523ff67a0fff861cdb", @@ -5423,24 +5564,6 @@ "MAME" ] }, - { - "dest": "hng64.zip", - "sha1": "98b92ece93d3e160c446a56ac859a8311333e36c", - "size": 41210, - "repo_path": "bios/Arcade/Arcade/hng64.zip", - "cores": [ - "MAME" - ] - }, - { - "dest": "ar_bios.zip", - "sha1": "df93e5943023cc8f7ddf0ffb956fd969e04a171a", - "size": 681333, - "repo_path": "bios/Arcade/Arcade/ar_bios.zip", - "cores": [ - "MAME" - ] - }, { "dest": "alg_bios.zip", "sha1": "901e1927ebe87946e3e4bb6d0a825088a8a60490", @@ -5459,24 +5582,6 @@ "MAME" ] }, - { - "dest": "maxaflex.zip", - "sha1": "697551fcf9557ae33e31096b118a0c6769700a2e", - "size": 13673, - "repo_path": "bios/Arcade/Arcade/maxaflex.zip", - "cores": [ - "MAME" - ] - }, - { - "dest": "crysbios.zip", - "sha1": "7f2d1c4e00ce1824ff91459d8ff6b59b2fb4b746", - "size": 43480, - "repo_path": "bios/Arcade/Arcade/crysbios.zip", - "cores": [ - "MAME" - ] - }, { "dest": "galgbios.zip", "sha1": "9ec50f79c5fd6eaddd88542aa18b6c5bb81a9ed4", @@ -6368,15 +6473,6 @@ "MAME" ] }, - { - "dest": "mame2000/cheat/cheat.dat", - "sha1": "32fc78415114a976af9c2ee53ea13de9f40b55ee", - "size": 7971360, - "repo_path": "bios/Arcade/MAME/cheat.dat", - "cores": [ - "MAME 2000" - ] - }, { "dest": "history.dat", "sha1": "42d6d9ac933a7819adfd686f629a8e43aea4e880", @@ -6395,51 +6491,6 @@ "MAME 2000" ] }, - { - "dest": "cvs.zip", - "sha1": "649d8af20ba0048fa993585960bf0305d50fc560", - "size": 3113, - "repo_path": "bios/Arcade/Arcade/cvs.zip", - "cores": [ - "MAME 2003" - ] - }, - { - "dest": "cpzn1.zip", - "sha1": "6515a4f2f3f5864c32346c2fecdc3595205f1d86", - "size": 125571, - "repo_path": "bios/Arcade/Arcade/cpzn1.zip", - "cores": [ - "MAME 2003" - ] - }, - { - "dest": "cpzn2.zip", - "sha1": "b18e23b8908e3e920ce971b8be168fa8baf1b3f7", - "size": 137827, - "repo_path": "bios/Arcade/Arcade/cpzn2.zip", - "cores": [ - "MAME 2003" - ] - }, - { - "dest": "taitofx1.zip", - "sha1": "2975c1ac34a18f6d262c5cc7dcc223f096742075", - "size": 126160, - "repo_path": "bios/Arcade/Arcade/taitofx1.zip", - "cores": [ - "MAME 2003" - ] - }, - { - "dest": "tps.zip", - "sha1": "f18270ba6e747de86bc7bd4f0f2c9c6f35335871", - "size": 138474, - "repo_path": "bios/Arcade/Arcade/tps.zip", - "cores": [ - "MAME 2003" - ] - }, { "dest": "sncwgltd.zip", "sha1": "9d0104d44d0d1cb9bbe801d21f2fddb834efa758", @@ -6449,15 +6500,6 @@ "MAME 2003" ] }, - { - "dest": "acpsx.zip", - "sha1": "5426d52e17e0ff9195fabbb42f704342e556d08e", - "size": 126167, - "repo_path": "bios/Arcade/Arcade/acpsx.zip", - "cores": [ - "MAME 2003" - ] - }, { "dest": "mame2010/cheat.zip", "sha1": "a737e925e3b86374da57496e3261d0303889977f", @@ -6494,33 +6536,6 @@ "MAME 2010" ] }, - { - "dest": "atluspsx.zip", - "sha1": "a6ff77ddecbd0888c2323a51199cadd3202e759a", - "size": 126346, - "repo_path": "bios/Arcade/Arcade/atluspsx.zip", - "cores": [ - "MAME 2010" - ] - }, - { - "dest": "atpsx.zip", - "sha1": "86ff54e5d15d754768fe7fd1ac6871fe1b49dba5", - "size": 126298, - "repo_path": "bios/Arcade/Arcade/atpsx.zip", - "cores": [ - "MAME 2010" - ] - }, - { - "dest": "psarc95.zip", - "sha1": "3c1a40887171d71915487cdb630d2b7fee2417cc", - "size": 128759, - "repo_path": "bios/Arcade/Arcade/psarc95.zip", - "cores": [ - "MAME 2010" - ] - }, { "dest": "vspsx.zip", "sha1": "241fa39e4b321b4b7b8fc8f82b411df8a206fec5", @@ -6530,15 +6545,6 @@ "MAME 2010" ] }, - { - "dest": "bctvidbs.zip", - "sha1": "5024b3bfd04ccd2061eb60d2eca254e8faf44f5c", - "size": 3753, - "repo_path": "bios/Arcade/Arcade/bctvidbs.zip", - "cores": [ - "MAME 2010" - ] - }, { "dest": "mk6nsw11.zip", "sha1": "b62c501e3858d6a1db6f8450ed96a6b37b1b5d58", @@ -7340,6 +7346,15 @@ "PicoDrive" ] }, + { + "dest": "PPSSPP/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": [ + "PPSSPP" + ] + }, { "dest": "prboom.wad", "sha1": "5f4aed208301449c2e9514edfd325fe9dead76fa", @@ -8411,6 +8426,15 @@ "Dolphin" ] }, + { + "dest": "fbneo/hiscore.dat", + "sha1": "7381472bf046126257e51a0124e4553282f020e5", + "size": 322458, + "repo_path": "bios/Arcade/Arcade/hiscore.dat", + "cores": [ + "FinalBurn Neo" + ] + }, { "dest": "kronos/saturn_bios.bin", "sha1": "2b8cb4f87580683eb4d760e4ed210813d667f0a2", @@ -8420,6 +8444,15 @@ "Kronos" ] }, + { + "dest": "mame2000/cheat/cheat.dat", + "sha1": "32fc78415114a976af9c2ee53ea13de9f40b55ee", + "size": 7971360, + "repo_path": "bios/Arcade/MAME/cheat.dat", + "cores": [ + "MAME 2000" + ] + }, { "dest": "mame2003/hiscore.dat", "sha1": "7381472bf046126257e51a0124e4553282f020e5", diff --git a/install/retroarch.json b/install/retroarch.json index 64d62804..d1792940 100644 --- a/install/retroarch.json +++ b/install/retroarch.json @@ -4,7 +4,7 @@ "platform": "retroarch", "display_name": "RetroArch", "version": "1.0", - "generated": "2026-08-07T16:16:14Z", + "generated": "2026-08-08T00:30:59Z", "base_destination": "system", "detect": [ { @@ -115,6 +115,7 @@ }, { "pattern": "ps2-*.bin", + "emulator": "pcsx2", "targets": { "linux": [ "$HOME/.config/PCSX2/bios", @@ -141,6 +142,7 @@ }, { "file": "GC/USA/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/USA" @@ -155,6 +157,7 @@ }, { "file": "GC/EUR/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/EUR" @@ -169,6 +172,7 @@ }, { "file": "GC/JAP/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/JAP" @@ -183,6 +187,7 @@ }, { "file": "dsp_rom.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -197,6 +202,7 @@ }, { "file": "dsp_coef.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -248,8 +254,8 @@ } } ], - "total_files": 1630, - "total_size": 4194971786, + "total_files": 1631, + "total_size": 4195638316, "files": [ { "dest": "3do_arcade_saot.bin", @@ -3536,6 +3542,13 @@ "repo_path": "bios/Sony/PlayStation 2/.variants/GameIndex.yaml.57369df9", "cores": null }, + { + "dest": "PPSSPP/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": null + }, { "dest": "ti83se.rom", "sha1": "8177bc6d5489d575cbfa9a004d097fc08c6f8c86", diff --git a/install/retrobat.json b/install/retrobat.json index 51b2972a..0b25e569 100644 --- a/install/retrobat.json +++ b/install/retrobat.json @@ -4,7 +4,7 @@ "platform": "retrobat", "display_name": "RetroBat", "version": "1.0", - "generated": "2026-08-07T12:30:14Z", + "generated": "2026-08-08T00:31:14Z", "base_destination": "bios", "detect": [ { @@ -96,6 +96,7 @@ }, { "pattern": "ps2-*.bin", + "emulator": "pcsx2", "targets": { "linux": [ "$HOME/.config/PCSX2/bios", @@ -122,6 +123,7 @@ }, { "file": "GC/USA/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/USA" @@ -136,6 +138,7 @@ }, { "file": "GC/EUR/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/EUR" @@ -150,6 +153,7 @@ }, { "file": "GC/JAP/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/JAP" @@ -164,6 +168,7 @@ }, { "file": "dsp_rom.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -178,6 +183,7 @@ }, { "file": "dsp_coef.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -229,8 +235,8 @@ } } ], - "total_files": 1234, - "total_size": 3283257651, + "total_files": 1235, + "total_size": 3283924181, "files": [ { "dest": "panafz1.bin", @@ -7236,6 +7242,15 @@ "PicoDrive" ] }, + { + "dest": "PPSSPP/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": [ + "PPSSPP" + ] + }, { "dest": "prboom.wad", "sha1": "5f4aed208301449c2e9514edfd325fe9dead76fa", diff --git a/install/retrodeck.json b/install/retrodeck.json index 63be52e8..2a49a77b 100644 --- a/install/retrodeck.json +++ b/install/retrodeck.json @@ -4,7 +4,7 @@ "platform": "retrodeck", "display_name": "RetroDECK", "version": "1.0", - "generated": "2026-08-07T16:16:39Z", + "generated": "2026-08-08T00:31:25Z", "base_destination": "", "detect": [ { @@ -97,6 +97,7 @@ }, { "pattern": "ps2-*.bin", + "emulator": "pcsx2", "targets": { "linux": [ "$HOME/.config/PCSX2/bios", @@ -123,6 +124,7 @@ }, { "file": "GC/USA/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/USA" @@ -137,6 +139,7 @@ }, { "file": "GC/EUR/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/EUR" @@ -151,6 +154,7 @@ }, { "file": "GC/JAP/IPL.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu/GC/JAP" @@ -165,6 +169,7 @@ }, { "file": "dsp_rom.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -179,6 +184,7 @@ }, { "file": "dsp_coef.bin", + "emulator": "dolphin", "targets": { "linux": [ "$HOME/.local/share/dolphin-emu" @@ -230,8 +236,8 @@ } } ], - "total_files": 3264, - "total_size": 4967595069, + "total_files": 3266, + "total_size": 4968928129, "files": [ { "dest": "bios/panafz1.bin", @@ -14011,6 +14017,13 @@ "repo_path": "bios/Sony/PlayStation 2/ps2-0190r-20030623.bin", "cores": null }, + { + "dest": "bios/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": null + }, { "dest": "bios/supracan.zip", "sha1": "1af645230c3f1741fb1f7efef05e9efe768e9dcc", @@ -24173,6 +24186,15 @@ "NP2kai" ] }, + { + "dest": "bios/PPSSPP/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": [ + "PPSSPP" + ] + }, { "dest": "bios/keropi/iplrom.dat", "sha1": "0ed038ed2133b9f78c6e37256807424e0d927560", diff --git a/install/rocknix.json b/install/rocknix.json index 5a52c956..b842cfda 100644 --- a/install/rocknix.json +++ b/install/rocknix.json @@ -4,7 +4,7 @@ "platform": "rocknix", "display_name": "ROCKNIX", "version": "1.0", - "generated": "2026-08-07T16:58:58Z", + "generated": "2026-08-07T18:50:23Z", "base_destination": "bios", "detect": [ { @@ -16,8 +16,8 @@ } ], "standalone_copies": [], - "total_files": 1495, - "total_size": 4058321299, + "total_files": 1496, + "total_size": 4058987829, "files": [ { "dest": "xemu/bios/mcpx_1.0.bin", @@ -8589,6 +8589,15 @@ "PokeMini" ] }, + { + "dest": "PPSSPP/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": [ + "PPSSPP" + ] + }, { "dest": "prboom.wad", "sha1": "5f4aed208301449c2e9514edfd325fe9dead76fa", diff --git a/install/romm.json b/install/romm.json index 06e345cf..4c5b4b76 100644 --- a/install/romm.json +++ b/install/romm.json @@ -4,7 +4,7 @@ "platform": "romm", "display_name": "RomM", "version": "1.0", - "generated": "2026-08-06T17:49:51Z", + "generated": "2026-08-07T18:50:25Z", "base_destination": "bios", "detect": [ { @@ -14,8 +14,8 @@ } ], "standalone_copies": [], - "total_files": 542, - "total_size": 1486691402, + "total_files": 543, + "total_size": 1487357932, "files": [ { "dest": "3do/3do_arcade_saot.bin", @@ -2593,6 +2593,13 @@ "repo_path": "bios/Sony/PlayStation 2/ps2-0250j-20100415.bin", "cores": null }, + { + "dest": "psp/ppge_atlas.zim", + "sha1": "30a33f3651f8e1b0967a5dfe6ff714baebcce41a", + "size": 666530, + "repo_path": "bios/Sony/PlayStation Portable/ppge_atlas.zim", + "cores": null + }, { "dest": "tvc/tvcfileio.rom", "sha1": "98889c3a56b11dedf077f866ed2e12d51b604113", diff --git a/platforms/_registry.yml b/platforms/_registry.yml index 7635f6fc..fd4b3f3e 100644 --- a/platforms/_registry.yml +++ b/platforms/_registry.yml @@ -1,3 +1,149 @@ +# Shared by every platform below: same files, same destinations. +_standalone_copies: &standalone_copies + # Switch emulators (keys) + - file: prod.keys + targets: + linux: + - $HOME/.local/share/yuzu/keys + - $HOME/.local/share/eden/keys + - $HOME/.local/share/citron/keys + - $HOME/.local/share/suyu/keys + - $HOME/.config/Ryujinx/system + windows: + - '%APPDATA%\yuzu\keys' + - '%APPDATA%\eden\keys' + - '%APPDATA%\citron\keys' + - '%APPDATA%\suyu\keys' + - '%APPDATA%\Ryujinx\system' + darwin: + - $HOME/Library/Application Support/yuzu/keys + - $HOME/Library/Application Support/Ryujinx/system + - file: title.keys + targets: + linux: + - $HOME/.local/share/yuzu/keys + - $HOME/.local/share/eden/keys + - $HOME/.local/share/citron/keys + - $HOME/.local/share/suyu/keys + windows: + - '%APPDATA%\yuzu\keys' + - '%APPDATA%\eden\keys' + - '%APPDATA%\citron\keys' + - '%APPDATA%\suyu\keys' + darwin: + - $HOME/Library/Application Support/yuzu/keys + # 3DS emulators + - file: Citra/sysdata/aes_keys.txt + targets: + linux: + - $HOME/.local/share/azahar/sysdata + windows: + - '%APPDATA%\Azahar\sysdata' + - file: Citra/sysdata/boot9.bin + targets: + linux: + - $HOME/.local/share/azahar/sysdata + windows: + - '%APPDATA%\Azahar\sysdata' + # DuckStation (PS1) + - pattern: 'scph*.bin' + targets: + linux: + - $HOME/.local/share/duckstation/bios + windows: + - '%LOCALAPPDATA%\DuckStation\bios' + - '%USERPROFILE%\Documents\DuckStation\bios' + darwin: + - $HOME/Library/Application Support/DuckStation/bios + # PCSX2 (PS2) + - pattern: 'ps2-*.bin' + emulator: pcsx2 + targets: + linux: + - $HOME/.config/PCSX2/bios + - $HOME/.var/app/net.pcsx2.PCSX2/config/PCSX2/bios + windows: + - '%USERPROFILE%\Documents\PCSX2\bios' + darwin: + - $HOME/Library/Application Support/PCSX2/bios + # RPCS3 (PS3) - needs menu install + - note: 'PS3 firmware (PS3UPDAT.PUP) found. Install via RPCS3 > File > Install Firmware.' + detect: + linux: + - $HOME/.config/rpcs3 + windows: + - '%APPDATA%\rpcs3' + # Dolphin (GameCube) + - file: GC/USA/IPL.bin + emulator: dolphin + targets: + linux: + - $HOME/.local/share/dolphin-emu/GC/USA + windows: + - '%APPDATA%\Dolphin Emulator\GC\USA' + darwin: + - $HOME/Library/Application Support/Dolphin/GC/USA + - file: GC/EUR/IPL.bin + emulator: dolphin + targets: + linux: + - $HOME/.local/share/dolphin-emu/GC/EUR + windows: + - '%APPDATA%\Dolphin Emulator\GC\EUR' + darwin: + - $HOME/Library/Application Support/Dolphin/GC/EUR + - file: GC/JAP/IPL.bin + emulator: dolphin + targets: + linux: + - $HOME/.local/share/dolphin-emu/GC/JAP + windows: + - '%APPDATA%\Dolphin Emulator\GC\JAP' + darwin: + - $HOME/Library/Application Support/Dolphin/GC/JAP + - file: dsp_rom.bin + emulator: dolphin + targets: + linux: + - $HOME/.local/share/dolphin-emu + windows: + - '%APPDATA%\Dolphin Emulator' + darwin: + - $HOME/Library/Application Support/Dolphin + - file: dsp_coef.bin + emulator: dolphin + targets: + linux: + - $HOME/.local/share/dolphin-emu + windows: + - '%APPDATA%\Dolphin Emulator' + darwin: + - $HOME/Library/Application Support/Dolphin + # PPSSPP + - file: PPSSPP/ppge_atlas.zim + targets: + linux: + - $HOME/.config/ppsspp/PSP/SYSTEM + windows: + - '%USERPROFILE%\Documents\PPSSPP\PSP\SYSTEM' + darwin: + - $HOME/Library/Application Support/PPSSPP/PSP/SYSTEM + # Flycast (Dreamcast) + - file: dc/dc_boot.bin + targets: + linux: + - $HOME/.local/share/flycast/data + - $HOME/.var/app/org.flycast.Flycast/data/flycast + windows: + - '%APPDATA%\flycast\data' + - file: dc/dc_nvmem.bin + targets: + linux: + - $HOME/.local/share/flycast/data + - $HOME/.var/app/org.flycast.Flycast/data/flycast + windows: + - '%APPDATA%\flycast\data' + platforms: retroarch: config: retroarch.yml @@ -32,144 +178,7 @@ platforms: method: config_file config: '%APPDATA%\RetroArch\retroarch.cfg' parse_key: system_directory - standalone_copies: - # Switch emulators (keys) - - file: prod.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - - $HOME/.config/Ryujinx/system - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - - '%APPDATA%\Ryujinx\system' - darwin: - - $HOME/Library/Application Support/yuzu/keys - - $HOME/Library/Application Support/Ryujinx/system - - file: title.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - darwin: - - $HOME/Library/Application Support/yuzu/keys - # 3DS emulators - - file: Citra/sysdata/aes_keys.txt - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - - file: Citra/sysdata/boot9.bin - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - # DuckStation (PS1) - - pattern: 'scph*.bin' - targets: - linux: - - $HOME/.local/share/duckstation/bios - windows: - - '%LOCALAPPDATA%\DuckStation\bios' - - '%USERPROFILE%\Documents\DuckStation\bios' - darwin: - - $HOME/Library/Application Support/DuckStation/bios - # PCSX2 (PS2) - - pattern: 'ps2-*.bin' - targets: - linux: - - $HOME/.config/PCSX2/bios - - $HOME/.var/app/net.pcsx2.PCSX2/config/PCSX2/bios - windows: - - '%USERPROFILE%\Documents\PCSX2\bios' - darwin: - - $HOME/Library/Application Support/PCSX2/bios - # RPCS3 (PS3) - needs menu install - - note: 'PS3 firmware (PS3UPDAT.PUP) found. Install via RPCS3 > File > Install Firmware.' - detect: - linux: - - $HOME/.config/rpcs3 - windows: - - '%APPDATA%\rpcs3' - # Dolphin (GameCube) - - file: GC/USA/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/USA - windows: - - '%APPDATA%\Dolphin Emulator\GC\USA' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/USA - - file: GC/EUR/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/EUR - windows: - - '%APPDATA%\Dolphin Emulator\GC\EUR' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/EUR - - file: GC/JAP/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/JAP - windows: - - '%APPDATA%\Dolphin Emulator\GC\JAP' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/JAP - - file: dsp_rom.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - - file: dsp_coef.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - # PPSSPP - - file: PPSSPP/ppge_atlas.zim - targets: - linux: - - $HOME/.config/ppsspp/PSP/SYSTEM - windows: - - '%USERPROFILE%\Documents\PPSSPP\PSP\SYSTEM' - darwin: - - $HOME/Library/Application Support/PPSSPP/PSP/SYSTEM - # Flycast (Dreamcast) - - file: dc/dc_boot.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' - - file: dc/dc_nvmem.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' + standalone_copies: *standalone_copies batocera: config: batocera.yml status: active @@ -394,144 +403,7 @@ platforms: method: file_exists file: /etc/batocera-version bios_path: /userdata/bios - standalone_copies: - # Switch emulators (keys) - - file: prod.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - - $HOME/.config/Ryujinx/system - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - - '%APPDATA%\Ryujinx\system' - darwin: - - $HOME/Library/Application Support/yuzu/keys - - $HOME/Library/Application Support/Ryujinx/system - - file: title.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - darwin: - - $HOME/Library/Application Support/yuzu/keys - # 3DS emulators - - file: Citra/sysdata/aes_keys.txt - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - - file: Citra/sysdata/boot9.bin - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - # DuckStation (PS1) - - pattern: 'scph*.bin' - targets: - linux: - - $HOME/.local/share/duckstation/bios - windows: - - '%LOCALAPPDATA%\DuckStation\bios' - - '%USERPROFILE%\Documents\DuckStation\bios' - darwin: - - $HOME/Library/Application Support/DuckStation/bios - # PCSX2 (PS2) - - pattern: 'ps2-*.bin' - targets: - linux: - - $HOME/.config/PCSX2/bios - - $HOME/.var/app/net.pcsx2.PCSX2/config/PCSX2/bios - windows: - - '%USERPROFILE%\Documents\PCSX2\bios' - darwin: - - $HOME/Library/Application Support/PCSX2/bios - # RPCS3 (PS3) - needs menu install - - note: 'PS3 firmware (PS3UPDAT.PUP) found. Install via RPCS3 > File > Install Firmware.' - detect: - linux: - - $HOME/.config/rpcs3 - windows: - - '%APPDATA%\rpcs3' - # Dolphin (GameCube) - - file: GC/USA/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/USA - windows: - - '%APPDATA%\Dolphin Emulator\GC\USA' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/USA - - file: GC/EUR/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/EUR - windows: - - '%APPDATA%\Dolphin Emulator\GC\EUR' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/EUR - - file: GC/JAP/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/JAP - windows: - - '%APPDATA%\Dolphin Emulator\GC\JAP' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/JAP - - file: dsp_rom.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - - file: dsp_coef.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - # PPSSPP - - file: PPSSPP/ppge_atlas.zim - targets: - linux: - - $HOME/.config/ppsspp/PSP/SYSTEM - windows: - - '%USERPROFILE%\Documents\PPSSPP\PSP\SYSTEM' - darwin: - - $HOME/Library/Application Support/PPSSPP/PSP/SYSTEM - # Flycast (Dreamcast) - - file: dc/dc_boot.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' - - file: dc/dc_nvmem.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' + standalone_copies: *standalone_copies recalbox: config: recalbox.yml status: active @@ -744,144 +616,7 @@ platforms: method: file_exists file: /usr/bin/recalbox-settings bios_path: /recalbox/share/bios - standalone_copies: - # Switch emulators (keys) - - file: prod.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - - $HOME/.config/Ryujinx/system - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - - '%APPDATA%\Ryujinx\system' - darwin: - - $HOME/Library/Application Support/yuzu/keys - - $HOME/Library/Application Support/Ryujinx/system - - file: title.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - darwin: - - $HOME/Library/Application Support/yuzu/keys - # 3DS emulators - - file: Citra/sysdata/aes_keys.txt - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - - file: Citra/sysdata/boot9.bin - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - # DuckStation (PS1) - - pattern: 'scph*.bin' - targets: - linux: - - $HOME/.local/share/duckstation/bios - windows: - - '%LOCALAPPDATA%\DuckStation\bios' - - '%USERPROFILE%\Documents\DuckStation\bios' - darwin: - - $HOME/Library/Application Support/DuckStation/bios - # PCSX2 (PS2) - - pattern: 'ps2-*.bin' - targets: - linux: - - $HOME/.config/PCSX2/bios - - $HOME/.var/app/net.pcsx2.PCSX2/config/PCSX2/bios - windows: - - '%USERPROFILE%\Documents\PCSX2\bios' - darwin: - - $HOME/Library/Application Support/PCSX2/bios - # RPCS3 (PS3) - needs menu install - - note: 'PS3 firmware (PS3UPDAT.PUP) found. Install via RPCS3 > File > Install Firmware.' - detect: - linux: - - $HOME/.config/rpcs3 - windows: - - '%APPDATA%\rpcs3' - # Dolphin (GameCube) - - file: GC/USA/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/USA - windows: - - '%APPDATA%\Dolphin Emulator\GC\USA' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/USA - - file: GC/EUR/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/EUR - windows: - - '%APPDATA%\Dolphin Emulator\GC\EUR' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/EUR - - file: GC/JAP/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/JAP - windows: - - '%APPDATA%\Dolphin Emulator\GC\JAP' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/JAP - - file: dsp_rom.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - - file: dsp_coef.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - # PPSSPP - - file: PPSSPP/ppge_atlas.zim - targets: - linux: - - $HOME/.config/ppsspp/PSP/SYSTEM - windows: - - '%USERPROFILE%\Documents\PPSSPP\PSP\SYSTEM' - darwin: - - $HOME/Library/Application Support/PPSSPP/PSP/SYSTEM - # Flycast (Dreamcast) - - file: dc/dc_boot.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' - - file: dc/dc_nvmem.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' + standalone_copies: *standalone_copies retrobat: config: retrobat.yml status: active @@ -1067,144 +802,7 @@ platforms: - os: windows method: path_exists path: '%USERPROFILE%\RetroBat\bios' - standalone_copies: - # Switch emulators (keys) - - file: prod.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - - $HOME/.config/Ryujinx/system - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - - '%APPDATA%\Ryujinx\system' - darwin: - - $HOME/Library/Application Support/yuzu/keys - - $HOME/Library/Application Support/Ryujinx/system - - file: title.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - darwin: - - $HOME/Library/Application Support/yuzu/keys - # 3DS emulators - - file: Citra/sysdata/aes_keys.txt - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - - file: Citra/sysdata/boot9.bin - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - # DuckStation (PS1) - - pattern: 'scph*.bin' - targets: - linux: - - $HOME/.local/share/duckstation/bios - windows: - - '%LOCALAPPDATA%\DuckStation\bios' - - '%USERPROFILE%\Documents\DuckStation\bios' - darwin: - - $HOME/Library/Application Support/DuckStation/bios - # PCSX2 (PS2) - - pattern: 'ps2-*.bin' - targets: - linux: - - $HOME/.config/PCSX2/bios - - $HOME/.var/app/net.pcsx2.PCSX2/config/PCSX2/bios - windows: - - '%USERPROFILE%\Documents\PCSX2\bios' - darwin: - - $HOME/Library/Application Support/PCSX2/bios - # RPCS3 (PS3) - needs menu install - - note: 'PS3 firmware (PS3UPDAT.PUP) found. Install via RPCS3 > File > Install Firmware.' - detect: - linux: - - $HOME/.config/rpcs3 - windows: - - '%APPDATA%\rpcs3' - # Dolphin (GameCube) - - file: GC/USA/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/USA - windows: - - '%APPDATA%\Dolphin Emulator\GC\USA' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/USA - - file: GC/EUR/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/EUR - windows: - - '%APPDATA%\Dolphin Emulator\GC\EUR' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/EUR - - file: GC/JAP/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/JAP - windows: - - '%APPDATA%\Dolphin Emulator\GC\JAP' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/JAP - - file: dsp_rom.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - - file: dsp_coef.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - # PPSSPP - - file: PPSSPP/ppge_atlas.zim - targets: - linux: - - $HOME/.config/ppsspp/PSP/SYSTEM - windows: - - '%USERPROFILE%\Documents\PPSSPP\PSP\SYSTEM' - darwin: - - $HOME/Library/Application Support/PPSSPP/PSP/SYSTEM - # Flycast (Dreamcast) - - file: dc/dc_boot.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' - - file: dc/dc_nvmem.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' + standalone_copies: *standalone_copies emudeck: config: emudeck.yml status: active @@ -1232,144 +830,7 @@ platforms: config: '%APPDATA%\EmuDeck\settings.ps1' parse_key: $emulationPath bios_subdir: bios - standalone_copies: - # Switch emulators (keys) - - file: prod.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - - $HOME/.config/Ryujinx/system - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - - '%APPDATA%\Ryujinx\system' - darwin: - - $HOME/Library/Application Support/yuzu/keys - - $HOME/Library/Application Support/Ryujinx/system - - file: title.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - darwin: - - $HOME/Library/Application Support/yuzu/keys - # 3DS emulators - - file: Citra/sysdata/aes_keys.txt - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - - file: Citra/sysdata/boot9.bin - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - # DuckStation (PS1) - - pattern: 'scph*.bin' - targets: - linux: - - $HOME/.local/share/duckstation/bios - windows: - - '%LOCALAPPDATA%\DuckStation\bios' - - '%USERPROFILE%\Documents\DuckStation\bios' - darwin: - - $HOME/Library/Application Support/DuckStation/bios - # PCSX2 (PS2) - - pattern: 'ps2-*.bin' - targets: - linux: - - $HOME/.config/PCSX2/bios - - $HOME/.var/app/net.pcsx2.PCSX2/config/PCSX2/bios - windows: - - '%USERPROFILE%\Documents\PCSX2\bios' - darwin: - - $HOME/Library/Application Support/PCSX2/bios - # RPCS3 (PS3) - needs menu install - - note: 'PS3 firmware (PS3UPDAT.PUP) found. Install via RPCS3 > File > Install Firmware.' - detect: - linux: - - $HOME/.config/rpcs3 - windows: - - '%APPDATA%\rpcs3' - # Dolphin (GameCube) - - file: GC/USA/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/USA - windows: - - '%APPDATA%\Dolphin Emulator\GC\USA' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/USA - - file: GC/EUR/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/EUR - windows: - - '%APPDATA%\Dolphin Emulator\GC\EUR' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/EUR - - file: GC/JAP/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/JAP - windows: - - '%APPDATA%\Dolphin Emulator\GC\JAP' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/JAP - - file: dsp_rom.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - - file: dsp_coef.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - # PPSSPP - - file: PPSSPP/ppge_atlas.zim - targets: - linux: - - $HOME/.config/ppsspp/PSP/SYSTEM - windows: - - '%USERPROFILE%\Documents\PPSSPP\PSP\SYSTEM' - darwin: - - $HOME/Library/Application Support/PPSSPP/PSP/SYSTEM - # Flycast (Dreamcast) - - file: dc/dc_boot.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' - - file: dc/dc_nvmem.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' + standalone_copies: *standalone_copies lakka: config: lakka.yml status: active @@ -1429,144 +890,7 @@ platforms: method: path_exists path: $HOME/.var/app/net.retrodeck.retrodeck bios_path: $HOME/retrodeck - standalone_copies: - # Switch emulators (keys) - - file: prod.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - - $HOME/.config/Ryujinx/system - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - - '%APPDATA%\Ryujinx\system' - darwin: - - $HOME/Library/Application Support/yuzu/keys - - $HOME/Library/Application Support/Ryujinx/system - - file: title.keys - targets: - linux: - - $HOME/.local/share/yuzu/keys - - $HOME/.local/share/eden/keys - - $HOME/.local/share/citron/keys - - $HOME/.local/share/suyu/keys - windows: - - '%APPDATA%\yuzu\keys' - - '%APPDATA%\eden\keys' - - '%APPDATA%\citron\keys' - - '%APPDATA%\suyu\keys' - darwin: - - $HOME/Library/Application Support/yuzu/keys - # 3DS emulators - - file: Citra/sysdata/aes_keys.txt - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - - file: Citra/sysdata/boot9.bin - targets: - linux: - - $HOME/.local/share/azahar/sysdata - windows: - - '%APPDATA%\Azahar\sysdata' - # DuckStation (PS1) - - pattern: 'scph*.bin' - targets: - linux: - - $HOME/.local/share/duckstation/bios - windows: - - '%LOCALAPPDATA%\DuckStation\bios' - - '%USERPROFILE%\Documents\DuckStation\bios' - darwin: - - $HOME/Library/Application Support/DuckStation/bios - # PCSX2 (PS2) - - pattern: 'ps2-*.bin' - targets: - linux: - - $HOME/.config/PCSX2/bios - - $HOME/.var/app/net.pcsx2.PCSX2/config/PCSX2/bios - windows: - - '%USERPROFILE%\Documents\PCSX2\bios' - darwin: - - $HOME/Library/Application Support/PCSX2/bios - # RPCS3 (PS3) - needs menu install - - note: 'PS3 firmware (PS3UPDAT.PUP) found. Install via RPCS3 > File > Install Firmware.' - detect: - linux: - - $HOME/.config/rpcs3 - windows: - - '%APPDATA%\rpcs3' - # Dolphin (GameCube) - - file: GC/USA/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/USA - windows: - - '%APPDATA%\Dolphin Emulator\GC\USA' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/USA - - file: GC/EUR/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/EUR - windows: - - '%APPDATA%\Dolphin Emulator\GC\EUR' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/EUR - - file: GC/JAP/IPL.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu/GC/JAP - windows: - - '%APPDATA%\Dolphin Emulator\GC\JAP' - darwin: - - $HOME/Library/Application Support/Dolphin/GC/JAP - - file: dsp_rom.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - - file: dsp_coef.bin - targets: - linux: - - $HOME/.local/share/dolphin-emu - windows: - - '%APPDATA%\Dolphin Emulator' - darwin: - - $HOME/Library/Application Support/Dolphin - # PPSSPP - - file: PPSSPP/ppge_atlas.zim - targets: - linux: - - $HOME/.config/ppsspp/PSP/SYSTEM - windows: - - '%USERPROFILE%\Documents\PPSSPP\PSP\SYSTEM' - darwin: - - $HOME/Library/Application Support/PPSSPP/PSP/SYSTEM - # Flycast (Dreamcast) - - file: dc/dc_boot.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' - - file: dc/dc_nvmem.bin - targets: - linux: - - $HOME/.local/share/flycast/data - - $HOME/.var/app/org.flycast.Flycast/data/flycast - windows: - - '%APPDATA%\flycast\data' + standalone_copies: *standalone_copies romm: config: romm.yml status: active diff --git a/tests/test_install.py b/tests/test_install.py index e83f72ac..6664e1c1 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -1,12 +1,18 @@ """Tests for install.py platform detection and config parsing.""" from __future__ import annotations +import functools +import http.server import importlib.util import json import os import re +import shutil +import subprocess import tempfile +import threading import unittest +import unittest.mock from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent @@ -207,6 +213,509 @@ class TestEmbeddedDetection(unittest.TestCase): self.assertEqual(self._detect_with(set()), []) +class TestLaunchboxDetection(unittest.TestCase): + """LaunchBox references its emulators in Data/Emulators.xml.""" + + def _make_launchbox(self, app_path: str, title: str = "Retroarch"): + root = Path(tempfile.mkdtemp()) + data = root / "LaunchBox" / "Data" + data.mkdir(parents=True) + xml = data / "Emulators.xml" + xml.write_text( + "\n" + "\n" + " \n" + " x\n" + f" {title}\n" + f" {app_path}\n" + " \n" + "\n" + ) + return root, xml + + def test_relative_application_path(self): + root, xml = self._make_launchbox("Emulators\\RetroArch\\retroarch.exe") + ra_dir = root / "LaunchBox" / "Emulators" / "RetroArch" + ra_dir.mkdir(parents=True) + self.assertEqual( + install._launchbox_retroarch_system_dir(xml), ra_dir / "system" + ) + + def test_absolute_application_path(self): + emu_dir = Path(tempfile.mkdtemp()) / "RetroArch" + emu_dir.mkdir(parents=True) + root, xml = self._make_launchbox(str(emu_dir / "retroarch.exe")) + self.assertEqual( + install._launchbox_retroarch_system_dir(xml), emu_dir / "system" + ) + + def test_no_retroarch_entry(self): + root, xml = self._make_launchbox("Emulators\\PCSX2\\pcsx2.exe", "PCSX2") + self.assertIsNone(install._launchbox_retroarch_system_dir(xml)) + + def test_missing_emulator_directory(self): + root, xml = self._make_launchbox("Emulators\\RetroArch\\retroarch.exe") + self.assertIsNone(install._launchbox_retroarch_system_dir(xml)) + + def test_stale_entry_before_installed_one(self): + root = Path(tempfile.mkdtemp()) + data = root / "LaunchBox" / "Data" + data.mkdir(parents=True) + ra_dir = root / "LaunchBox" / "Emulators" / "RetroArch" + ra_dir.mkdir(parents=True) + xml = data / "Emulators.xml" + xml.write_text( + "\n" + "\n" + " \n" + " RetroArch (old)\n" + " Emulators\\RetroArch-old\\retroarch.exe\n" + " \n" + " \n" + " RetroArch\n" + " Emulators\\RetroArch\\retroarch.exe\n" + " \n" + "\n" + ) + self.assertEqual( + install._launchbox_retroarch_system_dir(xml), ra_dir / "system" + ) + + def test_missing_or_malformed_xml(self): + self.assertIsNone( + install._launchbox_retroarch_system_dir(Path("/nonexistent/Emulators.xml")) + ) + root = Path(tempfile.mkdtemp()) + bad = root / "Emulators.xml" + bad.write_text("not xml at all <<<") + self.assertIsNone(install._launchbox_retroarch_system_dir(bad)) + + +class TestRetroarchPathExpansion(unittest.TestCase): + """RetroArch abbreviates paths with '~' and ':' in its config.""" + + def _cfg(self, value: str) -> Path: + app_dir = Path(tempfile.mkdtemp()) + (app_dir / "retroarch.cfg").write_text(f'system_directory = "{value}"\n') + return app_dir + + def test_application_dir_prefix(self): + app_dir = self._cfg(":\\system") + self.assertEqual( + install._parse_retroarch_system_dir(app_dir / "retroarch.cfg"), + app_dir / "system", + ) + + def test_application_dir_prefix_forward_slash(self): + app_dir = self._cfg(":/system") + self.assertEqual( + install._parse_retroarch_system_dir(app_dir / "retroarch.cfg"), + app_dir / "system", + ) + + def test_home_prefix(self): + app_dir = self._cfg("~/bios") + self.assertEqual( + install._parse_retroarch_system_dir(app_dir / "retroarch.cfg"), + Path.home() / "bios", + ) + + def test_application_dir_overrides_config_dir(self): + app_dir = self._cfg(":\\system") + elsewhere = Path(tempfile.mkdtemp()) + self.assertEqual( + install._parse_retroarch_system_dir( + app_dir / "retroarch.cfg", elsewhere + ), + elsewhere / "system", + ) + + def test_absolute_path_untouched(self): + target = Path(tempfile.mkdtemp()) / "bios" + app_dir = self._cfg(str(target)) + self.assertEqual( + install._parse_retroarch_system_dir(app_dir / "retroarch.cfg"), target + ) + + +class TestLaunchboxRoot(unittest.TestCase): + """LaunchBox installs where the user chooses and keeps no registry key.""" + + def _make_root(self, root: Path) -> Path: + (root / "Data").mkdir(parents=True) + (root / "Data" / "Emulators.xml").write_text( + "\n\n" + ) + return root + + def test_shortcut_target_is_read(self): + # Byte layout of the shortcut a real install writes + lnk = Path(tempfile.mkdtemp()) / "LaunchBox.lnk" + lnk.write_bytes( + b"L\x00\x00\x00\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00" + b"\x00\x00\x00\x46" + b"\x00" * 8 + + b"C:\\Games\\LaunchBox\\Core\\LaunchBox.exe\x00" + b"\x1c\x00\x00\x00LaunchBox\x00" + ) + self.assertEqual( + install._lnk_target(lnk, "LaunchBox.exe"), + Path("C:/Games/LaunchBox/Core/LaunchBox.exe"), + ) + + def test_shortcut_without_target_is_ignored(self): + lnk = Path(tempfile.mkdtemp()) / "LaunchBox.lnk" + lnk.write_bytes(b"L\x00\x00\x00 no target here ") + self.assertIsNone(install._lnk_target(lnk, "LaunchBox.exe")) + + def test_shortcut_pointing_elsewhere_falls_back(self): + # A stale shortcut must not win over a real installation + userprofile = Path(tempfile.mkdtemp()) + root = self._make_root(userprofile / "LaunchBox") + appdata = Path(tempfile.mkdtemp()) + lnk_dir = ( + appdata / "Microsoft" / "Windows" / "Start Menu" / "Programs" / "LaunchBox" + ) + lnk_dir.mkdir(parents=True) + (lnk_dir / "LaunchBox.lnk").write_bytes( + b"L\x00\x00\x00D:\\Gone\\LaunchBox\\Core\\LaunchBox.exe\x00" + ) + env = {"APPDATA": str(appdata), "USERPROFILE": str(userprofile)} + with unittest.mock.patch.dict(os.environ, env): + self.assertEqual(install.launchbox_root("windows"), root) + + def test_falls_back_to_userprofile(self): + userprofile = Path(tempfile.mkdtemp()) + root = self._make_root(userprofile / "LaunchBox") + env = {"APPDATA": str(Path(tempfile.mkdtemp())), "USERPROFILE": str(userprofile)} + with unittest.mock.patch.dict(os.environ, env): + self.assertEqual(install.launchbox_root("windows"), root) + + def test_absent_on_linux(self): + self.assertIsNone(install.launchbox_root("linux")) + + def test_nothing_installed(self): + env = { + "APPDATA": str(Path(tempfile.mkdtemp())), + "USERPROFILE": str(Path(tempfile.mkdtemp())), + } + with unittest.mock.patch.dict(os.environ, env): + self.assertIsNone(install.launchbox_root("windows")) + + +class TestLaunchboxBiosDirs(unittest.TestCase): + """LaunchBox computes where each emulator it manages keeps its BIOS.""" + + def _make(self, entries: list[tuple[str, str]]) -> Path: + root = Path(tempfile.mkdtemp()) / "LaunchBox" + (root / "Data").mkdir(parents=True) + body = "".join( + " \n" + f" {title}\n" + f" {app}\n" + " \n" + for title, app in entries + ) + (root / "Data" / "Emulators.xml").write_text( + "\n" + f"\n{body}\n" + ) + for _, app in entries: + (root / Path(app.replace("\\", "/")).parent).mkdir(parents=True, exist_ok=True) + return root + + def test_pcsx2_portable_default_bios(self): + root = self._make([("PCSX2", "Emulators\\PCSX2\\pcsx2.exe")]) + emu = root / "Emulators" / "PCSX2" + (emu / "portable.ini").write_text("") + (emu / "inis").mkdir() + (emu / "inis" / "PCSX2.ini").write_text("[Folders]\nBios = bios\n") + self.assertEqual(install.launchbox_bios_dirs(root)["pcsx2"], emu / "bios") + + def test_pcsx2_portable_absolute_bios(self): + root = self._make([("PCSX2", "Emulators\\PCSX2\\pcsx2.exe")]) + emu = root / "Emulators" / "PCSX2" + elsewhere = Path(tempfile.mkdtemp()) / "ps2bios" + (emu / "portable.ini").write_text("") + (emu / "inis").mkdir() + (emu / "inis" / "PCSX2.ini").write_text(f"Bios = {elsewhere}\n") + self.assertEqual(install.launchbox_bios_dirs(root)["pcsx2"], elsewhere) + + def test_pcsx2_portable_txt_also_triggers_portable_mode(self): + root = self._make([("PCSX2", "Emulators\\PCSX2\\pcsx2.exe")]) + emu = root / "Emulators" / "PCSX2" + (emu / "portable.txt").write_text("") + self.assertEqual(install.launchbox_bios_dirs(root)["pcsx2"], emu / "bios") + + def test_pcsx2_portable_txt_names_a_data_root(self): + root = self._make([("PCSX2", "Emulators\\PCSX2\\pcsx2.exe")]) + emu = root / "Emulators" / "PCSX2" + (emu / "portable.txt").write_text("mydata\n") + self.assertEqual( + install.launchbox_bios_dirs(root)["pcsx2"], emu / "mydata" / "bios" + ) + + def test_pcsx2_without_config_uses_documents(self): + root = self._make([("PCSX2", "Emulators\\PCSX2\\pcsx2.exe")]) + userprofile = Path(tempfile.mkdtemp()) + with unittest.mock.patch.dict(os.environ, {"USERPROFILE": str(userprofile)}): + self.assertEqual( + install.launchbox_bios_dirs(root)["pcsx2"], + userprofile / "Documents" / "PCSX2" / "bios", + ) + + def test_xemu_defaults_to_bios_folder(self): + root = self._make([("Xemu", "Emulators\\Xemu\\xemu.exe")]) + emu = root / "Emulators" / "Xemu" + with unittest.mock.patch.dict(os.environ, {"APPDATA": str(Path(tempfile.mkdtemp()))}): + self.assertEqual(install.launchbox_bios_dirs(root)["xemu"], emu / "bios") + + def test_xemu_honours_configured_bootrom(self): + root = self._make([("Xemu", "Emulators\\Xemu\\xemu.exe")]) + emu = root / "Emulators" / "Xemu" + configured = Path(tempfile.mkdtemp()) / "xbox" + configured.mkdir() + (configured / "mcpx_1.0.bin").write_text("x") + (emu / "xemu.toml").write_text( + "[sys.files]\n" + f"bootrom_path = '{configured / 'mcpx_1.0.bin'}'\n" + ) + self.assertEqual(install.launchbox_bios_dirs(root)["xemu"], configured) + + def test_dolphin_portable_user_folder(self): + root = self._make([("Dolphin", "Emulators\\Dolphin\\Dolphin.exe")]) + emu = root / "Emulators" / "Dolphin" + (emu / "portable.txt").write_text("") + self.assertEqual(install.launchbox_bios_dirs(root)["dolphin"], emu / "User") + + def test_dolphin_without_portable_marker_is_absent(self): + root = self._make([("Dolphin", "Emulators\\Dolphin\\Dolphin.exe")]) + (root / "Emulators" / "Dolphin" / "User").mkdir() + self.assertNotIn("dolphin", install.launchbox_bios_dirs(root)) + + def test_unknown_emulators_are_ignored(self): + root = self._make([("Cemu", "Emulators\\Cemu\\Cemu.exe")]) + self.assertEqual(install.launchbox_bios_dirs(root), {}) + + def test_doctype_is_rejected(self): + root = Path(tempfile.mkdtemp()) / "LaunchBox" + (root / "Data").mkdir(parents=True) + (root / "Data" / "Emulators.xml").write_text( + "]>\n\n" + ) + self.assertEqual(install.launchbox_emulators(root / "Data" / "Emulators.xml"), {}) + + +class TestStandaloneCopiesExtraDirs(unittest.TestCase): + """Setups that keep emulators outside the default locations.""" + + def _manifest(self) -> dict: + return { + "standalone_copies": [ + { + "pattern": "ps2-*.bin", + "emulator": "pcsx2", + "targets": {"windows": []}, + }, + { + "file": "GC/USA/IPL.bin", + "emulator": "dolphin", + "targets": {"windows": []}, + }, + ] + } + + def test_pattern_entry_copies_into_extra_dir(self): + bios = Path(tempfile.mkdtemp()) + (bios / "ps2-0230a.bin").write_text("rom") + extra = Path(tempfile.mkdtemp()) / "bios" + extra.mkdir() + copied, _ = install.do_standalone_copies( + self._manifest(), bios, "windows", {"pcsx2": extra} + ) + self.assertEqual(copied, 1) + self.assertTrue((extra / "ps2-0230a.bin").exists()) + + def test_file_entry_keeps_its_subdirectory(self): + bios = Path(tempfile.mkdtemp()) + (bios / "GC" / "USA").mkdir(parents=True) + (bios / "GC" / "USA" / "IPL.bin").write_text("ipl") + user = Path(tempfile.mkdtemp()) / "User" + (user / "GC" / "USA").mkdir(parents=True) + copied, _ = install.do_standalone_copies( + self._manifest(), bios, "windows", {"dolphin": user} + ) + self.assertEqual(copied, 1) + self.assertTrue((user / "GC" / "USA" / "IPL.bin").exists()) + + def test_no_extra_dirs_is_unchanged(self): + bios = Path(tempfile.mkdtemp()) + (bios / "ps2-0230a.bin").write_text("rom") + self.assertEqual( + install.do_standalone_copies(self._manifest(), bios, "windows"), (0, 0) + ) + + +@unittest.skipUnless(shutil.which("pwsh"), "pwsh not available") +class TestLaunchboxDetectionPowershell(unittest.TestCase): + """install.ps1 must resolve LaunchBox's portable RetroArch on its own.""" + + def _run(self, entries: str, seed=None, manifest: dict | None = None): + userprofile = Path(tempfile.mkdtemp()) + ra_dir = userprofile / "LaunchBox" / "Emulators" / "RetroArch" + ra_dir.mkdir(parents=True) + data = userprofile / "LaunchBox" / "Data" + data.mkdir(parents=True) + (data / "Emulators.xml").write_text( + "\n" + "\n" + f"{entries}" + "\n" + ) + if seed is not None: + seed(userprofile) + serve_root = Path(tempfile.mkdtemp()) + (serve_root / "install").mkdir() + (serve_root / "install" / "retroarch.json").write_text( + json.dumps(manifest if manifest is not None else {"files": []}) + ) + handler = functools.partial( + http.server.SimpleHTTPRequestHandler, directory=str(serve_root) + ) + httpd = http.server.ThreadingHTTPServer(("127.0.0.1", 0), handler) + threading.Thread(target=httpd.serve_forever, daemon=True).start() + try: + env = dict(os.environ) + env.update( + USERPROFILE=str(userprofile), + APPDATA=str(tempfile.mkdtemp()), + RETROBIOS_BASE_URL=f"http://127.0.0.1:{httpd.server_address[1]}", + ) + proc = subprocess.run( + ["pwsh", "-NoProfile", "-File", str(REPO_ROOT / "install.ps1")], + env=env, + capture_output=True, + text=True, + timeout=60, + ) + finally: + httpd.shutdown() + return proc, ra_dir + + def _assert_resolved(self, proc, ra_dir): + self.assertIn("Found LaunchBox with RetroArch at", proc.stdout) + self.assertIn(str(ra_dir), proc.stdout) + self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) + self.assertIn("Done.", proc.stdout) + + def test_detects_launchbox_retroarch(self): + proc, ra_dir = self._run( + " \n" + " Retroarch\n" + " Emulators\\RetroArch\\retroarch.exe\n" + " \n" + ) + self._assert_resolved(proc, ra_dir) + + def test_stale_entry_before_installed_one(self): + proc, ra_dir = self._run( + " \n" + " RetroArch (old)\n" + " Emulators\\RetroArch-old\\retroarch.exe\n" + " \n" + " \n" + " RetroArch\n" + " Emulators\\RetroArch\\retroarch.exe\n" + " \n" + ) + self._assert_resolved(proc, ra_dir) + + def test_system_directory_from_config(self): + def seed(root: Path) -> None: + ra = root / "LaunchBox" / "Emulators" / "RetroArch" + (ra / "bios").mkdir(parents=True) + (ra / "retroarch.cfg").write_text('system_directory = ":\\bios"\n') + + proc, ra_dir = self._run( + " \n" + " RetroArch\n" + " Emulators\\RetroArch\\retroarch.exe\n" + " \n", + seed=seed, + ) + self.assertIn("Found LaunchBox with RetroArch at", proc.stdout) + self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) + self.assertIn(str(ra_dir / "bios"), proc.stdout) + + def test_standalone_copy_reaches_launchbox_emulator(self): + def seed(root: Path) -> None: + system = root / "LaunchBox" / "Emulators" / "RetroArch" / "system" + system.mkdir(parents=True) + (system / "ps2-0230a.bin").write_text("rom") + pcsx2 = root / "LaunchBox" / "Emulators" / "PCSX2" + (pcsx2 / "bios").mkdir(parents=True) + (pcsx2 / "portable.ini").write_text("") + (pcsx2 / "inis").mkdir() + (pcsx2 / "inis" / "PCSX2.ini").write_text("[Folders]\nBios = bios\n") + + manifest = { + "files": [], + "standalone_copies": [ + { + "pattern": "ps2-*.bin", + "emulator": "pcsx2", + "targets": {"windows": []}, + } + ], + } + proc, ra_dir = self._run( + " \n" + " RetroArch\n" + " Emulators\\RetroArch\\retroarch.exe\n" + " \n" + " \n" + " PCSX2\n" + " Emulators\\PCSX2\\pcsx2.exe\n" + " \n", + seed=seed, + manifest=manifest, + ) + self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) + landed = ra_dir.parent / "PCSX2" / "bios" / "ps2-0230a.bin" + self.assertTrue(landed.exists(), proc.stdout) + + +class TestDetectFrontends(unittest.TestCase): + """Frontends have no BIOS directory of their own but hint at the setup.""" + + def test_esde_from_home_directory(self): + home = Path(tempfile.mkdtemp()) + (home / "ES-DE").mkdir() + old = os.environ.pop("ESDE_APPDATA_DIR", None) + try: + self.assertIn("esde", install.detect_frontends("linux", home=home)) + finally: + if old is not None: + os.environ["ESDE_APPDATA_DIR"] = old + + def test_esde_from_env_dir(self): + home = Path(tempfile.mkdtemp()) + appdata = Path(tempfile.mkdtemp()) + os.environ["ESDE_APPDATA_DIR"] = str(appdata) + try: + self.assertIn("esde", install.detect_frontends("linux", home=home)) + finally: + del os.environ["ESDE_APPDATA_DIR"] + + def test_nothing_detected(self): + home = Path(tempfile.mkdtemp()) + old = os.environ.pop("ESDE_APPDATA_DIR", None) + try: + self.assertEqual(install.detect_frontends("linux", home=home), []) + finally: + if old is not None: + os.environ["ESDE_APPDATA_DIR"] = old + + class TestNormalizePlatform(unittest.TestCase): """Manifest URLs are case sensitive: user input must be normalized."""