From 2fc3df50c6a6a2f7a8ae9afa216ee850dbbc49c0 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 10 Nov 2026 12:21:05 +0100 Subject: [PATCH] fix: place Dolphin's User folder a platform sets Dolphin reads its User folder at $XDG_DATA_HOME/dolphin-emu, else ~/.local/share/dolphin-emu (Source/Core/UICommon/UICommon.cpp:449-477). The profile names both, so a launcher handing that folder places the layout: - EmuELEC starts Dolphin with XDG_DATA_HOME=/storage/roms/dolphin (dolphinSA/scripts/dolphin.sh:28): its 30 files move from the BIOS root to dolphin/dolphin-emu/ of the install tree. - RetroDECK links User/Triforce to bios/Triforce (dolphin/component_prepare.sh:30, 49), where its list already puts segaboot.gcm. The rest of the User folder sits in the Flatpak's data directory and is named as read outside the BIOS folder. An entry only the standalone build reads, path and no standalone_path, now takes that path as its layout path (Load/Firmware/rtl_bt/), and the pass copying a name the platform declares elsewhere reaches an unprefixed path: gba_bios.bin lands in dolphin/dolphin-emu/GBA/. --- emulators/dolphin.yml | 3 +++ scripts/common.py | 6 ++++-- scripts/packextras.py | 11 +++++++++-- tests/test_launcher_placement.py | 23 +++++++++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/emulators/dolphin.yml b/emulators/dolphin.yml index 64d47251f..03f3891ba 100644 --- a/emulators/dolphin.yml +++ b/emulators/dolphin.yml @@ -23,6 +23,9 @@ cores: - "dolphin-qt-gc" - "dolphin-qt-wii" systems: [nintendo-gamecube, nintendo-wii, sega-triforce] +standalone_directories: + - "$XDG_DATA_HOME/dolphin-emu" + - "~/.local/share/dolphin-emu" data_root: android: path: "Android/data/org.dolphinemu.dolphinemu/files" diff --git a/scripts/common.py b/scripts/common.py index d0f4236d5..9be55a3f3 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -2985,12 +2985,14 @@ def standalone_layout_path(entry: dict, profile: dict) -> str | None: ``standalone_path`` when the entry has one. A profile with no libretro build has one layout, so its ``path`` is that layout: UAE4All2 reads - kickstarts/kick13.rom below its working directory. + kickstarts/kick13.rom below its working directory. So does an entry only + the standalone build reads (``mode: standalone``), whose destination is + its ``path``: Dolphin's Load/Firmware/rtl_bt/ under its User directory. """ own = entry.get("standalone_path") if own: return own - if "libretro" in str(profile.get("type", "")): + if "libretro" in str(profile.get("type", "")) and entry.get("mode") != "standalone": return None return entry.get("path") or None diff --git a/scripts/packextras.py b/scripts/packextras.py index 3b3ad8354..ad394c0ba 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -1399,7 +1399,7 @@ def _collect_emulator_extras( # covered elsewhere belongs to another program's file. # Nor is one placed outside the BIOS tree by the layout. if (fname not in covered_names or f.get("unsourceable") or f.get("unplaced") - or f.get("root") in OUTER_ROOTS or f.get("unprefixed")): + or f.get("root") in OUTER_ROOTS): continue # Installed with the emulator or by the platform's package, # as the first pass already decided for the same entry. @@ -1429,7 +1429,12 @@ def _collect_emulator_extras( # of the directory core files go to, which the platform may not # declare (Recalbox has its System Cards under pcenginecd/ only, # geargrafx reads them from the root). - full_dest = prefixed_destination(dest, extras_prefix) + # A path of the install tree outside the BIOS folder keeps no + # prefix: EmuELEC's dolphin/dolphin-emu/GBA/gba_bios.bin. + full_dest = ( + sanitize_pack_path(dest) if f.get("unprefixed") + else prefixed_destination(dest, extras_prefix) + ) if full_dest in seen_dests and full_dest not in taken: continue if full_dest in taken and full_dest not in seen and fname not in platform_names: @@ -1475,6 +1480,8 @@ def _collect_emulator_extras( extra["contents"] = f["contents"] if f.get("system_dir"): extra["system_dir"] = f["system_dir"] + if f.get("unprefixed"): + extra["unprefixed"] = True extra["claim_order"] = (2, 0, emu_name) if _take(extra, full_dest, seen_dests, taken): extras.append(extra) diff --git a/tests/test_launcher_placement.py b/tests/test_launcher_placement.py index e0140569e..b03bd26d2 100644 --- a/tests/test_launcher_placement.py +++ b/tests/test_launcher_placement.py @@ -784,5 +784,28 @@ class CemuOnRetroDECK(unittest.TestCase): self.assertEqual(otp.get("unplaced"), "outside_bios_folder") +class DolphinUserDirectory(unittest.TestCase): + """EmuELEC runs Dolphin with XDG_DATA_HOME=/storage/roms/dolphin (dolphin.sh:28), + RetroDECK links $XDG_DATA_HOME/dolphin-emu/Triforce to bios/Triforce + (dolphin/component_prepare.sh:30, 49).""" + + def test_emuelec_installs_the_user_directory_below_its_roms(self) -> None: + view = standalone_view("dolphin", "emuelec") + placed = {e["name"]: e["standalone_path"] for e in view["files"] if e.get("layout_path")} + self.assertEqual(placed["segaboot.gcm"], "dolphin/dolphin-emu/Triforce/segaboot.gcm") + # A standalone-only entry's path is its place in the User directory. + self.assertEqual( + placed["rtl8723a_fw.bin"], "dolphin/dolphin-emu/Load/Firmware/rtl_bt/rtl8723a_fw.bin" + ) + self.assertTrue(all(e.get("unprefixed") for e in view["files"] if e.get("layout_path"))) + + def test_retrodeck_places_triforce_alone(self) -> None: + view = standalone_view("dolphin", "retrodeck") + placed = {e["name"]: e["standalone_path"] for e in view["files"] if e.get("layout_path")} + self.assertEqual(placed, {"segaboot.gcm": "Triforce/segaboot.gcm"}) + (ipl, *_) = [e for e in view["files"] if e["name"] == "IPL.bin"] + self.assertEqual(ipl.get("unplaced"), "outside_bios_folder") + + if __name__ == "__main__": unittest.main()