From 15cb52d5513e3fa81c2e01a11ea2a70231241177 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 05:31:35 +0200 Subject: [PATCH] fix: find EmuDeck Windows where its scripts look functions/all.ps1:4-14 (emudeck-we 037460ef) reads the APPDATA settings.ps1, else the one under the profile, and takes C:\Emulation when neither names a folder. Only the first was read and a forced platform fell back to %USERPROFILE%\Emulation\bios. A network install names its share through $VolName (JSONtoPS1.ps1:166-172), and a UTF-16 settings file is decoded. --- install.ps1 | 2 +- install.py | 43 +++++++++++++++++++++++-------- install.sh | 2 +- platforms/_registry.yml | 7 +++++ tests/test_install.py | 57 ++++++++++++++++++++++++++++++++++++++++- wiki/installer.md | 4 +-- 6 files changed, 100 insertions(+), 15 deletions(-) diff --git a/install.ps1 b/install.ps1 index b5d6fbf8..d0de691a 100644 --- a/install.ps1 +++ b/install.ps1 @@ -11,7 +11,7 @@ $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" - $defaultInstallSha256 = "f05f862fcebc16e99d692a20a09163665828ea0383643fcc96a41b11d77fcff7" + $defaultInstallSha256 = "67bba818ec05142e431155c27919e087d0d5aa1d5e148019ebae630f0dd26bdf" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index 17a06481..6c8fbe6e 100644 --- a/install.py +++ b/install.py @@ -206,6 +206,9 @@ def _os_default_dests(os_type: str) -> dict[str, Path]: "retroarch": _host_path("C:/RetroArch-Win64/system"), # RetroBat's installer defaults to C:\RetroBat (installer.iss). "retrobat": _host_path("C:/RetroBat/bios"), + # EmuDeck's Windows edition: C:\Emulation when its settings name + # no folder (emudeck-we functions/all.ps1:12-14). + "emudeck": _host_path("C:/Emulation/bios"), } if os_type == "wsl": # RetroArch may be the Linux one inside the distribution, while RetroBat @@ -749,7 +752,11 @@ def _parse_ps1_var(path: Path, key: str) -> str | None: return None normalized = key.lstrip("$") try: - for line in path.read_text(encoding="utf-8", errors="replace").splitlines(): + raw = path.read_bytes() + # Windows PowerShell's Out-File writes UTF-16, Set-Content -Encoding + # UTF8 a byte order mark: EmuDeck's settings.ps1 passes through both. + encoding = "utf-16" if raw[:2] in (b"\xff\xfe", b"\xfe\xff") else "utf-8-sig" + for line in raw.decode(encoding, errors="replace").splitlines(): line = line.strip() check = line.lstrip("$") if check.startswith(f"{normalized}="): @@ -1231,13 +1238,9 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]: break if os_type in ("windows", "wsl"): - # EmuDeck Windows - home = Path.home() - emudeck_ps1 = Path(os.environ.get("APPDATA", "")) / "EmuDeck" / "settings.ps1" - if emudeck_ps1.exists(): - emu_path = _parse_ps1_var(emudeck_ps1, "$emulationPath") - if emu_path: - found.append(("emudeck", Path(emu_path) / "bios")) + emudeck = _emudeck_windows_bios() + if emudeck is not None: + found.append(("emudeck", emudeck)) # RetroArch Windows: open_default_config_file reads retroarch.cfg # beside the exe, then under %APPDATA%\RetroArch, and a value left at @@ -1302,6 +1305,27 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]: return found +def _emudeck_windows_bios() -> Path | None: + """The BIOS folder of EmuDeck's Windows edition, as its scripts find it. + + functions/all.ps1:4-14 (emudeck-we 037460ef) loads + %APPDATA%\\EmuDeck\\settings.ps1, else %USERPROFILE%\\EmuDeck\\settings.ps1, + and takes C:\\Emulation when neither sets $emulationPath. A network + install sets it through a drive mapped to $VolName in that session + (JSONtoPS1.ps1:166-172), which is the share itself here. + """ + candidates = [Path(os.environ[var]) / "EmuDeck" / "settings.ps1" + for var in ("APPDATA", "USERPROFILE") if os.environ.get(var)] + settings = next((path for path in candidates if path.is_file()), None) + if settings is None: + return None + emu_path = _parse_ps1_var(settings, "$emulationPath") + if emu_path and ("$" in emu_path or "+" in emu_path): + share = _parse_ps1_var(settings, "$VolName") + emu_path = f"{share.rstrip(chr(92))}\\Emulation" if share else None + return _host_path(emu_path or "C:\\Emulation") / "bios" + + # Identity Name in pkg/msvc-uwp/RetroArch-msvcUWP/Package.appxmanifest. RETROARCH_UWP_ID = "1e4cf179-f3c2-404f-b9f3-cb2070a5aad8" @@ -3828,8 +3852,7 @@ def _default_dest(os_type: str, platform_name: str) -> Path | None: 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 + return None if platform_name == "retrobat": return None return DEFAULT_DESTS.get(platform_name) diff --git a/install.sh b/install.sh index 54028c51..41a56140 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="f05f862fcebc16e99d692a20a09163665828ea0383643fcc96a41b11d77fcff7" +DEFAULT_INSTALL_SHA256="67bba818ec05142e431155c27919e087d0d5aa1d5e148019ebae630f0dd26bdf" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/platforms/_registry.yml b/platforms/_registry.yml index 495ccdc5..433339a8 100644 --- a/platforms/_registry.yml +++ b/platforms/_registry.yml @@ -392,11 +392,18 @@ platforms: - os: linux method: path_exists path: $HOME/Emulation/bios + # functions/all.ps1:4-14 (emudeck-we 037460ef): the APPDATA settings, + # else the profile ones, and C:\Emulation when neither names a folder. - os: windows method: config_file config: '%APPDATA%\EmuDeck\settings.ps1' parse_key: $emulationPath bios_subdir: bios + - os: windows + method: config_file + config: '%USERPROFILE%\EmuDeck\settings.ps1' + parse_key: $emulationPath + bios_subdir: bios standalone_copies: *standalone_copies lakka: config: lakka.yml diff --git a/tests/test_install.py b/tests/test_install.py index 46daaf4b..31dd80d5 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -3180,8 +3180,9 @@ class NoFallbackOutsideWhatAPlatformReads(unittest.TestCase): self.assertIsNone(install._default_dest("windows", "batocera")) self.assertIsNone(install._default_dest("windows", "retropie")) self.assertEqual(install._default_dest("linux", "batocera"), Path("/userdata/bios")) + # emudeck-we functions/all.ps1:12-14, not the profile folder. self.assertEqual( - install._default_dest("windows", "emudeck"), install.DEFAULT_DESTS["emudeck"] + install._default_dest("windows", "emudeck"), install._host_path("C:/Emulation/bios") ) def test_a_forced_platform_without_a_folder_is_refused(self): @@ -3847,5 +3848,59 @@ class RetroBatIsFoundWhereItSaysItIs(unittest.TestCase): self.assertEqual(run.call_args[0][0][:2], ["reg.exe", "query"]) +class EmuDeckWindowsIsFoundAsItsScriptsFindIt(unittest.TestCase): + """EmuDeck's Windows scripts read %APPDATA%\\EmuDeck\\settings.ps1, else + %USERPROFILE%\\EmuDeck\\settings.ps1, and take C:\\Emulation when neither + names a folder (functions/all.ps1:4-14). Only the first was read, and a + forced --platform emudeck wrote into %USERPROFILE%\\Emulation\\bios.""" + + def _env(self) -> tuple[Path, Path]: + root = Path(_tempdir(self)) + appdata, profile = root / "appdata", root / "profile" + patcher = unittest.mock.patch.dict( + os.environ, {"APPDATA": str(appdata), "USERPROFILE": str(profile)}) + patcher.start() + self.addCleanup(patcher.stop) + return appdata, profile + + @staticmethod + def _settings(folder: Path, text: str, encoding: str = "utf-8-sig") -> None: + (folder / "EmuDeck").mkdir(parents=True, exist_ok=True) + (folder / "EmuDeck" / "settings.ps1").write_bytes(text.encode(encoding)) + + def test_the_profile_settings_are_read_when_appdata_has_none(self): + _appdata, profile = self._env() + self._settings(profile, '\n$emulationPath="/games/Emulation"\n') + self.assertEqual(install._emudeck_windows_bios(), Path("/games/Emulation/bios")) + + def test_appdata_comes_first(self): + appdata, profile = self._env() + self._settings(profile, '$emulationPath="/b/Emulation"\n') + self._settings(appdata, '$emulationPath="/a/Emulation"\n', "utf-16") + self.assertEqual(install._emudeck_windows_bios(), Path("/a/Emulation/bios")) + + def test_settings_naming_no_folder_mean_c_emulation(self): + appdata, _profile = self._env() + self._settings(appdata, '$device="Windows"\n') + self.assertEqual(install._emudeck_windows_bios(), + install._host_path("C:\\Emulation") / "bios") + + def test_a_network_install_is_its_share(self): + appdata, _profile = self._env() + self._settings(appdata, '$networkInstallation="true"\n' + '$VolName="/nas/games"\n' + '$emulationPath=$testdrive.ToString() + ":\\Emulation"\n') + self.assertEqual(install._emudeck_windows_bios(), + Path("/nas/games\\Emulation") / "bios") + + def test_no_settings_detects_nothing(self): + self._env() + self.assertIsNone(install._emudeck_windows_bios()) + + def test_a_forced_platform_falls_back_to_c_emulation(self): + self.assertEqual(install._default_dest("windows", "emudeck"), + install._host_path("C:/Emulation/bios")) + + if __name__ == "__main__": unittest.main() diff --git a/wiki/installer.md b/wiki/installer.md index 63ffad36..a649467a 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -320,7 +320,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` | EmuDeck | `emulationPath` + `/bios` | -| `%APPDATA%\EmuDeck\settings.ps1` | 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/org.libretro.RetroArch/config/retroarch/retroarch.cfg` | RetroArch (Flatpak) | `system_directory` | | `~/snap/retroarch/current/.config/retroarch/retroarch.cfg` | RetroArch (Snap) | `system_directory` | @@ -363,7 +363,7 @@ When a platform named with `--platform` is not found on the machine, the destination falls back to the directory its detection looks for: `/userdata/bios` for Batocera, `/recalbox/share/bios` for Recalbox, `/storage/system` for Lakka, `~/retrodeck` for RetroDECK, `~/Emulation/bios` -for EmuDeck, `/storage/roms/bios` for ROCKNIX, `/media/fat/games` for MiSTer, +for EmuDeck (`C:\Emulation\bios` on Windows), `/storage/roms/bios` for ROCKNIX, `/media/fat/games` for MiSTer, `~/RetroPie/BIOS` for RetroPie, `/romm/library/bios` for RomM, `C:\RetroBat\bios` for RetroBat, and RetroArch's own default system directory for RetroArch. BizHawk has no such folder: it reads `Firmware`