From a9d384d16755381a86303559fb5f9cf55460f73a Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 20 Oct 2026 03:17:42 +0200 Subject: [PATCH] fix: no install folder no platform reads A platform named but not found fell back to a folder it never opens: ~/bios for RetroBat on Linux and for BizHawk picked from the menu, and on Windows the embedded systems' /userdata/bios, which resolves to the root of the current drive. Each run downloaded gigabytes and reported done. Where no folder on the OS is one the platform reads, a forced run now asks for --dest and the menu asks for a path with no default. --- install.ps1 | 2 +- install.py | 49 +++++++++++++++++++++++++++++++++---------- install.sh | 2 +- tests/test_install.py | 28 +++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/install.ps1 b/install.ps1 index fc05635e..27b98fd3 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 = "9806af7e6f6686274f4eff644933e81ad384dd6a74ecc8ad1f82b59becf6c64e" +$defaultInstallSha256 = "98bc3ac3442126a44534c4c6817dfda2f680e9f9a5aa499efb81cccebd692ec2" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index a474802c..25259e24 100644 --- a/install.py +++ b/install.py @@ -3205,11 +3205,18 @@ def _prompt_manual_platform(os_type: str) -> list[tuple[str, Path]]: print(f" Type a number from 1 to {len(AVAILABLE_PLATFORMS)}, a platform name, " "or q to quit.") default_dest = _default_dest(os_type, plat) - try: - raw = input(f"BIOS directory [{default_dest}]: ").strip() - except (EOFError, KeyboardInterrupt): - print() - sys.exit(0) + raw = "" + while not raw: + try: + raw = input( + f"BIOS directory [{default_dest}]: " if default_dest + else "BIOS directory (no default on this system): " + ).strip() + except (EOFError, KeyboardInterrupt): + print() + sys.exit(0) + if default_dest is not None: + break dest = Path(raw).expanduser() if raw else default_dest if os_type == "windows" and raw: dest = Path(os.path.expandvars(raw)) @@ -3223,17 +3230,29 @@ PORTABLE_ONLY = { } -def _default_dest(os_type: str, platform_name: str) -> Path: - """Fall back path for a platform named on the command line but not found. +def _default_dest(os_type: str, platform_name: str) -> Path | None: + """Fall back path for a platform named on the command line but not found, + or None where no folder on this OS is one the platform reads. A home directory is meaningless for RetroArch on Android, where Termux - owns the home and the frontend reads from shared storage. + owns the home and the frontend reads from shared storage. ~/bios is read + by no platform, RetroBat lives on Windows only, and a Batocera or Recalbox + path on Windows resolved to the root of the current drive: 8.7 GB under + C:\\userdata\\bios, reported done. """ if os_type == "android" and platform_name == "retroarch": return _android_storage_root() / "RetroArch" / "system" - return _os_default_dests(os_type).get( - platform_name, DEFAULT_DESTS.get(platform_name, Path.home() / "bios") - ) + if platform_name in PORTABLE_ONLY: + return None + own = _os_default_dests(os_type).get(platform_name) + if own is not None: + return own + if os_type == "windows": + # EmuDeck's Windows edition keeps the same folder under the profile. + return DEFAULT_DESTS["emudeck"] if platform_name == "emudeck" else None + if platform_name == "retrobat": + return None + return DEFAULT_DESTS.get(platform_name) def _manual_usage_hint(os_type: str) -> list[str]: @@ -3670,6 +3689,14 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None: sys.exit(1) else: default_dest = _default_dest(os_type, args.platform) + if default_dest is None: + print( + f" Platform '{args.platform}' not found, and no folder on this " + f"system is one it reads. Name it with --dest, for example: " + f"--platform {args.platform} --dest ", + file=sys.stderr, + ) + sys.exit(1) print( f" Platform '{args.platform}' not detected, " f"using default path: {default_dest}" diff --git a/install.sh b/install.sh index 86b1bbd3..4586e004 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="9806af7e6f6686274f4eff644933e81ad384dd6a74ecc8ad1f82b59becf6c64e" +DEFAULT_INSTALL_SHA256="98bc3ac3442126a44534c4c6817dfda2f680e9f9a5aa499efb81cccebd692ec2" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install.py b/tests/test_install.py index 6244007a..be0b9bd1 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -2972,5 +2972,33 @@ class TheInstallerSaysWhatItFound(unittest.TestCase): self.assertNotIn("Traceback", err) +class NoFallbackOutsideWhatAPlatformReads(unittest.TestCase): + """A forced platform not found here fell back to ~/bios for RetroBat on + Linux and for BizHawk from the menu, and on Windows to /userdata/bios, + which resolves to the root of the current drive. Each run reported done.""" + + def test_the_fallback_is_none_where_nothing_is_read(self): + self.assertIsNone(install._default_dest("linux", "retrobat")) + self.assertIsNone(install._default_dest("linux", "bizhawk")) + self.assertIsNone(install._default_dest("windows", "batocera")) + self.assertIsNone(install._default_dest("windows", "retropie")) + self.assertEqual(install._default_dest("linux", "batocera"), Path("/userdata/bios")) + self.assertEqual( + install._default_dest("windows", "emudeck"), install.DEFAULT_DESTS["emudeck"] + ) + + def test_a_forced_platform_without_a_folder_is_refused(self): + with unittest.mock.patch.object(install, "detect_platforms", return_value=[]), \ + unittest.mock.patch.object(install, "detect_os", return_value="linux"), \ + unittest.mock.patch("sys.argv", ["install.py", "--platform", "retrobat", + "--no-input"]), \ + contextlib.redirect_stdout(io.StringIO()), \ + contextlib.redirect_stderr(io.StringIO()) as err, \ + self.assertRaises(SystemExit) as caught: + install.main() + self.assertEqual(caught.exception.code, 1) + self.assertIn("--dest", err.getvalue()) + + if __name__ == "__main__": unittest.main()