From 2c61a909f746fbf54d7e21e9ed5fa912976e2005 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Sat, 17 Oct 2026 20:26:02 +0200 Subject: [PATCH] fix: find RetroArch where Windows and macOS keep it --- install.ps1 | 2 +- install.py | 62 ++++++++++++++++++++++++++++++++--------- install.sh | 2 +- platforms/_registry.yml | 10 ++++++- tests/test_install.py | 49 ++++++++++++++++++++++++++++++++ wiki/installer.md | 5 ++-- 6 files changed, 112 insertions(+), 18 deletions(-) diff --git a/install.ps1 b/install.ps1 index d4ce326d..ce742565 100644 --- a/install.ps1 +++ b/install.ps1 @@ -8,7 +8,7 @@ param( $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -$defaultInstallSha256 = "351450de7dc16195231c2065aef1d1ee72a2231d9a5d7af158dc199099b198ad" +$defaultInstallSha256 = "3eac76733e9f27965ef62da4f3ef8e793b02af289d7a3317b1626c99a64ddcff" $maximumInstallerBytes = 2MB $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } $temporary = $null diff --git a/install.py b/install.py index 0286ece4..470c05bf 100644 --- a/install.py +++ b/install.py @@ -174,15 +174,15 @@ def _os_default_dests(os_type: str) -> dict[str, Path]: profile = Path(os.environ.get("USERPROFILE", str(home))) appdata = Path(os.environ.get("APPDATA", str(home / "AppData" / "Roaming"))) return { - "retroarch": appdata / "RetroArch" / "system", + # RetroArch's system directory defaults to \system + # (platform_win32.c), and its installer to C:\RetroArch-Win64. + "retroarch": Path("C:/RetroArch-Win64/system"), # RetroBat's installer defaults to C:\RetroBat (installer.iss). "retrobat": Path("C:/RetroBat/bios"), "bizhawk": profile / "BizHawk" / "Firmware", } if os_type == "darwin": - return { - "retroarch": home / "Library" / "Application Support" / "RetroArch" / "system", - } + return {"retroarch": _macos_retroarch_base() / "system"} return { "retroarch": home / ".config" / "retroarch" / "system", "bizhawk": home / ".config" / "BizHawk" / "Firmware", @@ -455,6 +455,27 @@ def _retroarch_cfg_dir( return None +def _windows_retroarch_dirs() -> list[Path]: + """Folders holding retroarch.exe where the installer and the archive put + it: C:\RetroArch-Win64 by default, C:\RetroArch for 32-bit builds.""" + candidates = [Path("C:/RetroArch-Win64"), Path("C:/RetroArch")] + for var in ("ProgramFiles", "ProgramFiles(x86)"): + root = os.environ.get(var, "") + if root: + candidates.append(Path(root) / "RetroArch") + return [folder for folder in candidates if (folder / "retroarch.exe").is_file()] + + +def _macos_retroarch_base() -> Path: + """Where RetroArch on macOS keeps its system folder by default: + ~/Documents/RetroArch, or Application Support/RetroArch when that folder + holds portable.txt (platform_darwin.m).""" + support = Path.home() / "Library" / "Application Support" / "RetroArch" + if (support / "portable.txt").is_file(): + return support + return Path.home() / "Documents" / "RetroArch" + + # System directory -> the retroarch.cfg it was read from and the directory # ':' expands to there, for finding the cores of the same installation. _RETROARCH_CONFIGS: dict[Path, tuple[Path, Path]] = {} @@ -896,11 +917,17 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]: found.append(("retroarch", ra_dir)) if os_type == "darwin": - home = Path.home() - mac_cfg = home / "Library" / "Application Support" / "RetroArch" / "retroarch.cfg" - ra_dir = _parse_retroarch_system_dir(mac_cfg) - if ra_dir: - found.append(("retroarch", ra_dir)) + # open_default_config_file reads /RetroArch/config + # (configuration.c); a value left at default is the documents folder, + # or Application Support itself for a portable install + # (platform_darwin.m). The config beside it is where older builds + # kept it. + support = Path.home() / "Library" / "Application Support" / "RetroArch" + for mac_cfg in (support / "config" / "retroarch.cfg", support / "retroarch.cfg"): + ra_dir = _parse_retroarch_system_dir(mac_cfg, _macos_retroarch_base()) + if ra_dir: + found.append(("retroarch", ra_dir)) + break if os_type in ("windows", "wsl"): # EmuDeck Windows @@ -911,11 +938,20 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]: if emu_path: found.append(("emudeck", Path(emu_path) / "bios")) - # RetroArch Windows + # RetroArch Windows: open_default_config_file reads retroarch.cfg + # beside the exe, then under %APPDATA%\RetroArch, and a value left at + # default is \system (configuration.c, platform_win32.c). appdata = os.environ.get("APPDATA", "") - if appdata: - win_cfg = Path(appdata) / "RetroArch" / "retroarch.cfg" - ra_dir = _parse_retroarch_system_dir(win_cfg) + appdata_cfg = Path(appdata) / "RetroArch" / "retroarch.cfg" if appdata else None + exe_dirs = _windows_retroarch_dirs() if os_type == "windows" else [] + for exe_dir in exe_dirs: + ra_dir = _parse_retroarch_system_dir(exe_dir / "retroarch.cfg", exe_dir) + if ra_dir is None and appdata_cfg is not None: + ra_dir = _parse_retroarch_system_dir(appdata_cfg, exe_dir) + found.append(("retroarch", ra_dir or exe_dir / "system")) + break + if not exe_dirs and appdata_cfg is not None: + ra_dir = _parse_retroarch_system_dir(appdata_cfg) if ra_dir: found.append(("retroarch", ra_dir)) diff --git a/install.sh b/install.sh index 5cbc7c5f..261683fe 100755 --- a/install.sh +++ b/install.sh @@ -18,7 +18,7 @@ esac TEMP_INSTALLER="" TEMP_DIRECTORY="" DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -DEFAULT_INSTALL_SHA256="351450de7dc16195231c2065aef1d1ee72a2231d9a5d7af158dc199099b198ad" +DEFAULT_INSTALL_SHA256="3eac76733e9f27965ef62da4f3ef8e793b02af289d7a3317b1626c99a64ddcff" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/platforms/_registry.yml b/platforms/_registry.yml index c859ba68..4642cf58 100644 --- a/platforms/_registry.yml +++ b/platforms/_registry.yml @@ -171,9 +171,17 @@ platforms: method: config_file config: $HOME/.config/retroarch/retroarch.cfg parse_key: system_directory + # configuration.c reads /RetroArch/config; a value + # left at default is ~/Documents/RetroArch/system (platform_darwin.m). - os: darwin method: config_file - config: $HOME/Library/Application Support/RetroArch/retroarch.cfg + config: $HOME/Library/Application Support/RetroArch/config/retroarch.cfg + parse_key: system_directory + bios_path: $HOME/Documents/RetroArch/system + # Beside retroarch.exe first, then %APPDATA%; default \system. + - os: windows + method: config_file + config: 'C:\RetroArch-Win64\retroarch.cfg' parse_key: system_directory - os: windows method: config_file diff --git a/tests/test_install.py b/tests/test_install.py index 1173f194..80a03a7e 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -1743,5 +1743,54 @@ class TargetListFailureIsNotAnAnswer(unittest.TestCase): with unittest.mock.patch.object(install.urllib.request, "urlopen", missing): self.assertEqual(install.fetch_targets("retroarch"), {}) + +class RetroArchWhereItReallyLives(unittest.TestCase): + """Windows: retroarch.cfg sits beside retroarch.exe and system defaults + to \\system; only %APPDATA% was read, and the fallback was a + folder RetroArch never reads. macOS: the config is under + Application Support/RetroArch/config, the system folder in Documents.""" + + def _exe_dir(self) -> tuple[Path, dict]: + program_files = Path(_tempdir(self)) + exe_dir = program_files / "RetroArch" + exe_dir.mkdir() + (exe_dir / "retroarch.exe").write_bytes(b"MZ") + return exe_dir, {"ProgramFiles": str(program_files)} + + def test_windows_install_folder_without_a_config(self): + exe_dir, env = self._exe_dir() + with unittest.mock.patch.dict(os.environ, env, clear=True): + found = install.detect_platforms("windows") + self.assertIn(("retroarch", exe_dir / "system"), found) + + def test_windows_appdata_config_at_default_names_the_exe_folder(self): + exe_dir, env = self._exe_dir() + appdata = Path(_tempdir(self)) + (appdata / "RetroArch").mkdir() + (appdata / "RetroArch" / "retroarch.cfg").write_text('system_directory = "default"\n') + env["APPDATA"] = str(appdata) + with unittest.mock.patch.dict(os.environ, env, clear=True): + found = install.detect_platforms("windows") + self.assertIn(("retroarch", exe_dir / "system"), found) + + def test_windows_config_beside_the_exe_wins(self): + exe_dir, env = self._exe_dir() + (exe_dir / "retroarch.cfg").write_text('system_directory = "D:\\bios"\n') + with unittest.mock.patch.dict(os.environ, env, clear=True): + found = install.detect_platforms("windows") + self.assertIn(("retroarch", Path("D:\\bios")), found) + + def test_macos_config_folder_and_documents_default(self): + home = Path(_tempdir(self)) + support = home / "Library" / "Application Support" / "RetroArch" + (support / "config").mkdir(parents=True) + (support / "config" / "retroarch.cfg").write_text('system_directory = "default"\n') + with unittest.mock.patch.object(install.Path, "home", return_value=home): + found = install.detect_platforms("darwin") + self.assertIn(("retroarch", home / "Documents" / "RetroArch" / "system"), found) + (support / "portable.txt").write_text("") + found = install.detect_platforms("darwin") + self.assertIn(("retroarch", support / "system"), found) + if __name__ == "__main__": unittest.main() diff --git a/wiki/installer.md b/wiki/installer.md index cc804d3f..95e05fb8 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -277,8 +277,9 @@ found on one machine. | `~/.var/app/org.libretro.RetroArch/config/retroarch/retroarch.cfg` | RetroArch (Flatpak) | `system_directory` | | `~/snap/retroarch/current/.config/retroarch/retroarch.cfg` | RetroArch (Snap) | `system_directory` | | `~/.config/retroarch/retroarch.cfg` | RetroArch (native) | `system_directory` | -| `~/Library/Application Support/RetroArch/retroarch.cfg` | RetroArch (macOS) | `system_directory` | -| `%APPDATA%\RetroArch\retroarch.cfg` | RetroArch (Windows) | `system_directory` | +| `~/Library/Application Support/RetroArch/config/retroarch.cfg` | RetroArch (macOS) | `system_directory`; at default, `~/Documents/RetroArch/system` (portable: `Application Support/RetroArch/system`) | +| `retroarch.cfg` beside `retroarch.exe` (`C:\RetroArch-Win64`, `C:\RetroArch`, `Program Files\RetroArch`) | RetroArch (Windows) | `system_directory`; at default or without a config, `system` beside the exe | +| `%APPDATA%\RetroArch\retroarch.cfg` | RetroArch (Windows) | `system_directory`, read when the exe folder has no config | | `%ProgramFiles(x86)%\Steam\steamapps\common\RetroArch\retroarch.cfg` | RetroArch (Steam) | `system_directory` | | LaunchBox `Data\Emulators.xml` | RetroArch (portable) | the system directory that entry points at |