diff --git a/install.ps1 b/install.ps1 index c0ed4e27..c0f19d74 100644 --- a/install.ps1 +++ b/install.ps1 @@ -11,7 +11,7 @@ $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" - $defaultInstallSha256 = "9fbee0f382ca68880697ed573b79ecc1ffca793a406a3f258dad16e40d2845d6" + $defaultInstallSha256 = "b37588b71f2a6273cedf31ee05fc7b7053b5489f074349fef9cfae0dcf2555ff" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index 68bcb081..e77a8d2e 100644 --- a/install.py +++ b/install.py @@ -1154,9 +1154,12 @@ def found_label(plat: str, path: Path) -> str: platform whose files it gets when that is another name.""" name = detected_os(plat, path) shown = display_name(plat) + moved = "".join(f", {folder}/ at {place}" + for folder, place in sorted(_MOVED_FOLDERS.get(path, {}).items()) + if folder in ("bios", "roms")) if not name or name == shown: - return f"{shown} at {path}" - return f"{name} at {path} ({shown} files)" + return f"{shown} at {path}{moved}" + return f"{name} at {path} ({shown} files){moved}" def _android_storage_root() -> Path: @@ -1564,7 +1567,9 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]: f"{retrodeck_json}; assuming {home / 'retrodeck'}", file=sys.stderr, ) - found.append(("retrodeck", Path(rd_home) if rd_home else home / "retrodeck")) + rd_root = Path(rd_home) if rd_home else home / "retrodeck" + _record_moved_folders(rd_root, retrodeck_json) + found.append(("retrodeck", rd_root)) elif retrodeck_cfg.exists(): rd_home = _parse_bash_var(retrodeck_cfg, "rdhome") found.append(("retrodeck", Path(rd_home) if rd_home else home / "retrodeck")) @@ -1987,6 +1992,33 @@ def _safe_target_dir(value: object, field: str) -> str: return value +# A platform whose folders move one by one: root -> {folder under the root: +# where it is now}. RetroDECK's per-folder move (RetroDECK 7786a849, +# functions/api_data_processing.sh:913-935) records each new place in +# retrodeck.json paths._path (config/retrodeck/retrodeck.json:3-23), +# and its manifest places files under bios/ and roms/ of the home folder. +_MOVED_FOLDERS: dict[Path, dict[str, Path]] = {} + + +def _record_moved_folders(root: Path, retrodeck_json: Path) -> None: + """Each RetroDECK folder that is no longer /.""" + try: + paths = json.loads(retrodeck_json.read_text(encoding="utf-8", errors="replace"))["paths"] + except (OSError, ValueError, KeyError, TypeError): + return + if not isinstance(paths, dict): + return + moved = {} + for key, value in paths.items(): + name = key[:-len("_path")] if key.endswith("_path") else "" + if name and name != "rd_home" and isinstance(value, str) and value.strip(): + place = Path(value.strip()) + if place != root / name: + moved[name] = place + if moved: + _MOVED_FOLDERS[root] = moved + + def _destination_path(root: Path, value: object) -> Path: """Place a manifest destination below *root*. @@ -1997,6 +2029,9 @@ def _destination_path(root: Path, value: object) -> Path: emulator reads it. """ relative = _safe_relative_path(value, "dest") + moved = _MOVED_FOLDERS.get(root, {}) + if len(relative.parts) > 1 and relative.parts[0] in moved: + return moved[relative.parts[0]].resolve() / Path(*relative.parts[1:]) return root.resolve() / Path(*relative.parts) diff --git a/install.sh b/install.sh index 548c3a02..d3ceca20 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="9fbee0f382ca68880697ed573b79ecc1ffca793a406a3f258dad16e40d2845d6" +DEFAULT_INSTALL_SHA256="b37588b71f2a6273cedf31ee05fc7b7053b5489f074349fef9cfae0dcf2555ff" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/test_install.py b/tests/test_install.py index 8a68209f..d60f0bc1 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -2668,6 +2668,49 @@ class AnUnreadableRetroDeckConfigIsSaid(unittest.TestCase): +class RetroDeckFoldersMovedOneByOne(unittest.TestCase): + """RetroDECK moves its bios folder on its own and records the new place + in retrodeck.json paths.bios_path, but only rd_home_path was read: the + files went to /bios, a folder RetroDECK no longer reads.""" + + def _detect(self, paths: dict) -> tuple[Path, list]: + home = Path(_tempdir(self)) + conf = home / ".var/app/net.retrodeck.retrodeck/config/retrodeck" + conf.mkdir(parents=True) + (conf / "retrodeck.json").write_text(json.dumps({"paths": paths})) + with unittest.mock.patch.object(install.Path, "home", return_value=home), \ + unittest.mock.patch.object(install, "_detect_embedded", return_value=[]), \ + unittest.mock.patch.dict(install._MOVED_FOLDERS, clear=True), \ + contextlib.redirect_stderr(io.StringIO()): + found = install.detect_platforms("linux") + dests = {name: install._destination_path(home / "retrodeck", f"{name}/a.bin") + for name in ("bios", "roms", "saves")} + label = install.found_label("retrodeck", home / "retrodeck") + return home, [found, dests, label] + + def test_a_moved_bios_folder_is_followed(self): + home = Path(_tempdir(self)) + sd = home / "sd" + root, (found, dests, label) = self._detect({ + "rd_home_path": "", "bios_path": str(sd / "bios"), "roms_path": str(sd / "roms"), + }) + rd_home = root / "retrodeck" + self.assertIn(("retrodeck", rd_home), found) + self.assertEqual(dests["bios"], (sd / "bios").resolve() / "a.bin") + self.assertEqual(dests["roms"], (sd / "roms").resolve() / "a.bin") + self.assertEqual(dests["saves"], rd_home.resolve() / "saves" / "a.bin") + self.assertIn(f"bios/ at {sd / 'bios'}", label) + + def test_folders_in_place_change_nothing(self): + home = Path(_tempdir(self)) + rd_home = home / "rd" + _root, (found, dests, label) = self._detect({ + "rd_home_path": str(rd_home), "bios_path": str(rd_home / "bios"), + }) + self.assertIn(("retrodeck", rd_home), found) + self.assertNotIn("bios/ at", label) + + class TheBoardTheSystemNamesIsTheTarget(unittest.TestCase): """Batocera, Recalbox and ROCKNIX each write the board they were built for, but the installer read none of them, so a Batocera RPi4 installed diff --git a/wiki/installer.md b/wiki/installer.md index 126b7396..aac250cd 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -372,7 +372,7 @@ WSL does not pass them. A forced `--platform retrobat` falls back to | `HKCU\SOFTWARE\RetroBat` `LatestKnownInstallPath`, which `RetroBat.exe` writes on every start (read through `reg.exe` under WSL) | RetroBat | that folder + `\bios`, before the fixed folders above | | `~/.config/EmuDeck/settings.sh` (Linux and macOS) | EmuDeck | `emulationPath` + `/bios` | | `%APPDATA%\EmuDeck\settings.ps1`, else `%USERPROFILE%\EmuDeck\settings.ps1` | EmuDeck | `$emulationPath` + `\bios`, `C:\Emulation\bios` when it names none (a network install: the `$VolName` share + `\Emulation\bios`) | -| `~/.var/app/net.retrodeck.retrodeck/config/retrodeck/retrodeck.json` | RetroDECK | `paths.rd_home_path`, falling back to the pre-migration `retrodeck.cfg` and its `rdhome` | +| `~/.var/app/net.retrodeck.retrodeck/config/retrodeck/retrodeck.json` | RetroDECK | `paths.rd_home_path`, falling back to the pre-migration `retrodeck.cfg` and its `rdhome`. A folder RetroDECK moved on its own is followed: files for `bios/` go to `paths.bios_path`, files for `roms/` to `paths.roms_path`, and the run names the new place | | `~/.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` |