From ccc8af7a47ad71f8e50139a6ec9a36b656ff797d Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 20:43:38 +0200 Subject: [PATCH] fix: compare a card's BIOS folder by folders --dest NOTMUOS/bios ended with MUOS/bios as text and passed as a muOS card's folder. A card two systems claim now says so in one line, and the system named among them decides the folder. --- install.ps1 | 2 +- install.py | 34 ++++++++++++++++++++++++++++------ install.sh | 2 +- tests/test_install_tui.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/install.ps1 b/install.ps1 index cf6f068a..11bffe1d 100644 --- a/install.ps1 +++ b/install.ps1 @@ -11,7 +11,7 @@ $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" - $defaultInstallSha256 = "1097dfe33e56238089af275bfea662d29613ff592f68ecdb427a86b5bcbc3e81" + $defaultInstallSha256 = "2103f380bac5e2dc6ab91183283d268cccce81971da89f2963bab752d1518ef2" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index 4ce73ef1..8ddbcfc1 100644 --- a/install.py +++ b/install.py @@ -8667,16 +8667,29 @@ def card_system(root: Path) -> tuple[str, Path, str] | None: plat = card_platform(root) if plat: return plat, root, display_name(plat) - matched = [rule for rule in _card_rules() if _card_rule_matches(rule, root)] - bases = {rule.base for rule in matched if rule.base} - matched = [rule for rule in matched if rule.name not in bases] - targets = {(rule.manifest, rule.bios) for rule in matched} - if len(targets) != 1: + matched = _card_matches(root) + if len({(rule.manifest, rule.bios) for rule in matched}) != 1: return None rule = matched[0] return rule.manifest, root / rule.bios, rule.name +def _card_matches(root: Path) -> list[_OsRule]: + """The card rules root matches, a system another one is built on left + out for it.""" + matched = [rule for rule in _card_rules() if _card_rule_matches(rule, root)] + bases = {rule.base for rule in matched if rule.base} + return [rule for rule in matched if rule.name not in bases] + + +def _ends_with(path: Path, relative: str) -> bool: + """Whether path's last folders are relative's, compared folder by folder + and without case: NOTMUOS/bios does not end with MUOS/bios.""" + tail = [part.casefold() for part in PurePosixPath(relative).parts] + parts = [part.casefold() for part in path.parts] + return len(parts) >= len(tail) and parts[len(parts) - len(tail):] == tail + + def card_dest(named: str, plat: str, dest: Path) -> tuple[str, Path]: """The file list and folder for a --dest that names a handheld's card. @@ -8694,12 +8707,21 @@ def card_dest(named: str, plat: str, dest: Path) -> tuple[str, Path]: key = _name_key(named) rule = next((rule for rule in _card_rules() if key and key == _name_key(rule.name) and key != _name_key(display_name(rule.manifest))), None) + matched = [] if found else _card_matches(dest) if rule is None or found: + if len({(match.manifest, match.bios) for match in matched}) > 1: + names = " and ".join(sorted({match.name for match in matched})) + print(f" {screen_text(str(dest))} holds the files of {names}: installing into " + "it as named. Name the card's BIOS folder with --dest to write there.") return plat, dest # Its folder on the card, or one its device reads (/run/muos/storage/bios). own = {device.bios for device in _EMBEDDED_RULES if device.name == rule.name} - if dest.as_posix() in own or dest.as_posix().casefold().endswith(rule.bios.casefold()): + if dest.as_posix() in own or _ends_with(dest, rule.bios): return plat, dest + if any(match.name == rule.name for match in matched): + # A card two systems claim, the one named among them. + print(f" {rule.name} card: {display_name(plat)} files, in {dest / rule.bios}") + return plat, dest / rule.bios marker = rule.values[0].split("/")[0] print(f"Error: {dest} is not a {rule.name} card: it holds no {marker}. " f"{rule.name} reads its BIOS files from {rule.bios} on its card: name the card's " diff --git a/install.sh b/install.sh index 2d1e27a9..b0c2495d 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="1097dfe33e56238089af275bfea662d29613ff592f68ecdb427a86b5bcbc3e81" +DEFAULT_INSTALL_SHA256="2103f380bac5e2dc6ab91183283d268cccce81971da89f2963bab752d1518ef2" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install_tui.py b/tests/test_install_tui.py index ef7c2f67..73eee5ea 100644 --- a/tests/test_install_tui.py +++ b/tests/test_install_tui.py @@ -10,6 +10,7 @@ installs it). from __future__ import annotations import argparse +import contextlib import errno import fcntl import functools @@ -2122,6 +2123,33 @@ class AuditRoundThree(unittest.TestCase): self.assertEqual(install.card_dest("retroarch", "retroarch", root), ("retroarch", root)) + def test_p1_1_the_folder_named_is_compared_by_folders(self): + """NOTMUOS/bios ended with MUOS/bios as text and was taken for a + muOS card's folder.""" + root = self.card({"roms/": ""}) + stray = root / "NOTMUOS" / "bios" + with self.assertRaises(SystemExit), \ + contextlib.redirect_stderr(io.StringIO()) as err: + install.card_dest("muos", "retroarch", stray) + self.assertIn("is not a muOS card", err.getvalue()) + self.assertEqual(install.card_dest("muos", "retroarch", root / "muos" / "BIOS"), + ("retroarch", root / "muos" / "BIOS")) + + def test_p1_1_a_card_two_systems_claim_says_so(self): + root = self.card({"MUOS/": "", "spruce/": ""}) + self.assertIsNone(install.card_system(root)) + with contextlib.redirect_stdout(io.StringIO()) as out: + self.assertEqual(install.card_dest("retroarch", "retroarch", root), + ("retroarch", root)) + lines = out.getvalue().splitlines() + self.assertEqual(len(lines), 1, lines) + self.assertIn("holds the files of muOS and spruceOS", lines[0]) + # The system named among them decides. + with contextlib.redirect_stdout(io.StringIO()) as out: + self.assertEqual(install.card_dest("muos", "retroarch", root), + ("retroarch", root / "MUOS/bios")) + self.assertIn("muOS card", out.getvalue()) + # P1-2: Space is X on PortMaster and Y on muxterm. def test_p1_2_space_on_install_opens_the_confirm_screen(self):