From e61d1e22c0a4c0b5e1db939588586b941f35f6df Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 23:19:29 +0200 Subject: [PATCH] fix: find retroarch.cfg for any --dest spelling The config was looked up by the BIOS folder exactly as typed: a relative --dest, a link or another case sent save-folder files to a guessed folder and refused --installed-cores. --- install.py | 37 ++++++++++++++++++++++++--- tests/test_save_root.py | 55 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/install.py b/install.py index 733c1c89..85eea2ff 100644 --- a/install.py +++ b/install.py @@ -3993,13 +3993,42 @@ def _retroarch_config_for(bios_path: Path) -> tuple[Path, Path] | None: sent Azahar's keys.txt to a guessed folder where line mode read savefile_directory, and found no core where line mode found them. """ - found = _RETROARCH_CONFIGS.get(bios_path) + found = _retroarch_config_at(bios_path) if found is not None or _CONFIGS_READ: return found _CONFIGS_READ.append(True) with contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()): detect_platforms(_INVOCATION.os_type or detect_os()) - return _RETROARCH_CONFIGS.get(bios_path) + return _retroarch_config_at(bios_path) + + +def _folder_key(path: Path) -> str: + """The folder as the system names it: absolute, links followed, and + case folded where the system's default filesystem folds it.""" + real = os.path.realpath(path) + return real.casefold() if sys.platform in ("win32", "darwin") else real + + +def _retroarch_config_at(bios_path: Path) -> tuple[Path, Path] | None: + """The config detection read for bios_path, however the folder is + written: --dest relative, through a link, or in another case. The + configs are keyed by the folder as each retroarch.cfg writes it.""" + found = _RETROARCH_CONFIGS.get(bios_path) + if found is not None or not _RETROARCH_CONFIGS: + return found + wanted = _folder_key(bios_path) + for system_dir, config in _RETROARCH_CONFIGS.items(): + if _folder_key(system_dir) == wanted: + return config + # A drive whose filesystem folds case where the system's default does + # not (or the reverse): the filesystem answers for folders that exist. + for system_dir, config in _RETROARCH_CONFIGS.items(): + try: + if os.path.samefile(system_dir, bios_path): + return config + except (OSError, ValueError): + continue + return None def save_directory_for( @@ -11632,7 +11661,7 @@ def _plan_lines(check: dict, dest: Path | None, state: TuiState) -> list: f"{wrong} wrong {'copy' if wrong == 1 else 'copies'}") if check is state.check: lines += [_styled(line, "path") for line in _saves_lines(state)] - found_in = _RETROARCH_CONFIGS.get(dest) if dest is not None else None + found_in = _retroarch_config_at(dest) if dest is not None else None if found_in: lines.append(_styled(f"Named by {_short_path(Path(found_in[0]), state.home)}", "path")) free = state.facts.get("free") @@ -12365,7 +12394,7 @@ def _install_kind(path: Path) -> str: """What sets one install of a frontend apart from another of the same.""" # The config detection read it from says how it was installed: the # Flatpak's system directory can be anywhere its config names. - found = _RETROARCH_CONFIGS.get(path) + found = _retroarch_config_at(path) text = path.as_posix() + (" " + Path(found[0]).as_posix() if found else "") for marker, word in (("/.var/app/", "Flatpak"), ("/steamapps/", "Steam"), ("/snap/", "Snap")): diff --git a/tests/test_save_root.py b/tests/test_save_root.py index f3c7eebc..e3cc58f8 100644 --- a/tests/test_save_root.py +++ b/tests/test_save_root.py @@ -25,6 +25,7 @@ import threading import unittest import zipfile from pathlib import Path +from unittest import mock REPO_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO_ROOT / "scripts")) @@ -552,6 +553,60 @@ class TheInstallerFindsTheSaveDirectory(unittest.TestCase): False, False)) install._check_save_directory(manifest) + def other_spelling(self, spelled: Path) -> install.SavePlace | str: + """save_directory_for the BIOS folder written another way, the config + keyed as detection writes it, with savefile_directory set.""" + saves = self.root / "MySaves" + saves.mkdir(exist_ok=True) + self.cfg.write_text(f'savefile_directory = "{saves}"\n') + install._RETROARCH_CONFIGS[self.bios] = (self.cfg, self.app) + self.addCleanup(install._SAVE_DIRS.pop, spelled, None) + with mock.patch.object(install, "_CONFIGS_READ", [True]): + return install.save_directory_for(spelled, self.MANIFEST) + + def test_a_relative_bios_folder_finds_its_config(self): + """cd ~/.config/retroarch && install.py --dest system: the save files + went to a guessed saves/ beside it.""" + self.addCleanup(os.chdir, os.getcwd()) + os.chdir(self.app) + self.assertEqual(self.other_spelling(Path("system")), + install.SavePlace(self.root / "MySaves", True)) + + def test_a_bios_folder_through_a_link_finds_its_config(self): + """EmuDeck links RetroArch's system folder to ~/Emulation/bios.""" + link = self.root / "bios-link" + link.symlink_to(self.bios, target_is_directory=True) + self.assertEqual(self.other_spelling(link), + install.SavePlace(self.root / "MySaves", True)) + linked_app = self.root / "RA-link" + linked_app.symlink_to(self.app, target_is_directory=True) + self.assertEqual(self.other_spelling(linked_app / "system"), + install.SavePlace(self.root / "MySaves", True)) + + def test_case_only_differs_where_the_system_folds_it(self): + spelled = Path(str(self.bios).upper()) + with mock.patch.object(install.sys, "platform", "linux"): + self.assertNotEqual(self.other_spelling(spelled), + install.SavePlace(self.root / "MySaves", True)) + install._SAVE_DIRS.pop(spelled, None) + with mock.patch.object(install.sys, "platform", "darwin"): + self.assertEqual(self.other_spelling(spelled), + install.SavePlace(self.root / "MySaves", True)) + + def test_the_cores_folder_through_another_spelling(self): + cores = self.root / "MyCores" + cores.mkdir() + self.cfg.write_text(f'libretro_directory = "{cores}"\n') + install._RETROARCH_CONFIGS[self.bios] = (self.cfg, self.app) + link = self.root / "bios-link" + link.symlink_to(self.bios, target_is_directory=True) + self.addCleanup(os.chdir, os.getcwd()) + os.chdir(self.app) + with mock.patch.object(install, "_CONFIGS_READ", [True]): + for spelled in (Path("system"), link, Path("./system/")): + with self.subTest(spelled=str(spelled)): + self.assertEqual(install.core_dirs("retroarch", spelled), [cores]) + def test_the_entry_path(self): saves = (self.app / "saves").resolve() install._SAVE_DIRS[self.bios] = install.SavePlace(self.app / "saves", True)