From 74ef0a37d5711b7e854a1636ce7c6113af839155 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 10 Nov 2026 12:20:55 +0100 Subject: [PATCH] fix: place Cemu's MLC where RetroDECK sets it RetroDECK sets mlc_path to $bios_path/cemu (cemu/component_prepare.sh:14, 29), which replaces the MLC Cemu reads under its user data directory (src/config/CemuConfig.cpp:25-26, ActiveSettings.cpp:295-320). The profile names that key, so the layout's mlc01/ folder is placed in bios/cemu. Two readers kept the binding from reaching the manifest: - a moved entry kept its standalone load_from, data_dir, and the gap report set the MLC aside as read outside the system directory. The platform's folder now replaces the build's own root. - an entry read outside the BIOS tree was no longer offered to the launcher layer, so RetroDECK's link of keys.txt to bios/cemu/keys.txt went unserved. It is offered again, fills no destination by its own path, and a launcher place it fills settles it. The RetroDECK manifest gains the 55 MLC files under bios/cemu/. It drops the root copies of keys.txt, otp.bin, seeprom.bin, shareddata.bin, cafeLibs/ and resources/sharedFonts/, which standalone Cemu reads in the Flatpak's $XDG_DATA_HOME/Cemu and names them as read outside the BIOS folder. The libretro copies under bios/Cemu/ stay. --- emulators/cemu.yml | 6 ++++-- scripts/common.py | 8 ++++++++ scripts/launchers.py | 7 ++++++- scripts/verify.py | 5 ++++- tests/test_launcher_placement.py | 16 ++++++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/emulators/cemu.yml b/emulators/cemu.yml index dd27018ea..5f1da83ef 100644 --- a/emulators/cemu.yml +++ b/emulators/cemu.yml @@ -20,6 +20,8 @@ cores: - cemu - "cemu-sa" systems: [nintendo-wiiu] +standalone_directories: + - "mlc_path = mlc01" data_directories: - ref: cemu-gameprofiles @@ -71,7 +73,7 @@ notes: | directory of that name (saves sorted per core). Standalone Cemu uses the portable directory next to the executable when it exists, otherwise the user data directory given per system in data_root, and the MLC defaults to - /mlc01. path: entries below are + /mlc01, which mlc_path in settings.xml replaces. path: entries below are the libretro destination under the system directory, or under the save directory for the MLC (load_from), and standalone_path: is relative to the Cemu user data directory, which holds the MLC by default (load_from @@ -80,7 +82,7 @@ notes: | ref: src/libretro/CemuLibretro.cpp:2110-2170, src/libretro/CemuLibretro.cpp:4083-4091, src/gui/wxgui/CemuApp.cpp:89-124, src/gui/wxgui/CemuApp.cpp:126-172, - src/config/ActiveSettings.cpp:295-321 + src/config/ActiveSettings.cpp:295-321, src/config/CemuConfig.cpp:25-26 keys.txt is read once from the user data path. A missing file is created with a comment header and an example key. Each line is stripped of diff --git a/scripts/common.py b/scripts/common.py index 09d2900e2..d0f4236d5 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -3117,6 +3117,14 @@ def platform_layouts( tree, path = placed moved = {**f, "standalone_path": path, "layout_path": standalone_layout_path(f, profile)} + if f.get("load_from"): + # The build's own root no longer applies: RetroDECK hands + # Cemu's MLC, read from its data directory, to bios/cemu. + load_from = f["load_from"] + moved["load_from"] = { + **(load_from if isinstance(load_from, dict) else {"libretro": load_from}), + "standalone": "system_dir", + } if tree == "install": # Below the roms root inside the install tree (AmberELEC's # /storage/roms): a plain dest the platform prefix skips. diff --git a/scripts/launchers.py b/scripts/launchers.py index e3ca1e6ff..6f1b68dda 100644 --- a/scripts/launchers.py +++ b/scripts/launchers.py @@ -1093,7 +1093,9 @@ class LauncherLayer: # core asked for it: the pack puts a file there either way. reported_at: dict[str, dict] = {} for _emulator, _standalone, _entry, report in candidates: - if report.get("in_repo"): + # A file the layout reads outside the BIOS tree fills no + # destination of it. + if report.get("in_repo") and not report.get("unplaced"): destination = sanitize_pack_path(report.get("path") or report.get("name", "")) reported_at.setdefault(destination, report) by_layout: dict[tuple[str, bool], list[tuple[dict, dict]]] = {} @@ -1137,6 +1139,9 @@ class LauncherLayer: continue if isinstance(filled, dict): added.append(filled) + # The launcher hands the emulator that file from the BIOS + # tree: read outside it no more. + settled.update(id(report) for _c, report in matched if report.get("unplaced")) if place.stands_for_default: # The emulator opens the launcher's file instead of its # own name, so a copy under that name is read by nobody: diff --git a/scripts/verify.py b/scripts/verify.py index 9aaf6b4ca..b17248aeb 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -809,7 +809,10 @@ def find_undeclared_files( # Read below the roms directory (ROCKNIX's roms/pico-8) # or in an emulator folder (RetroBat's emulators/pico8). entry["root"] = f["root"] - elif launcher_layer and not save_root: + if launcher_layer and not save_root and f.get("root") not in OUTER_ROOTS: + # One read outside the BIOS tree is still served where a + # launcher links that very file: RetroDECK links Cemu's + # keys.txt to bios/cemu/keys.txt. launcher_candidates.append((emu_name, is_standalone, f, entry)) keys = set_keys((emu_name, is_standalone), f) if duplicate: diff --git a/tests/test_launcher_placement.py b/tests/test_launcher_placement.py index 932223245..e0140569e 100644 --- a/tests/test_launcher_placement.py +++ b/tests/test_launcher_placement.py @@ -768,5 +768,21 @@ class Eka2l1OnRetroBat(unittest.TestCase): self.assertEqual(entry.get("unplaced"), "outside_bios_folder", entry["name"]) +class CemuOnRetroDECK(unittest.TestCase): + """RetroDECK sets mlc_path to $bios_path/cemu (cemu/component_prepare.sh:14, 29) + and keeps Cemu's user data in its Flatpak's $XDG_DATA_HOME/Cemu.""" + + def test_the_mlc_lands_in_cemu(self) -> None: + view = standalone_view("cemu", "retrodeck") + mlc = [e for e in view["files"] if (e.get("layout_path") or "").startswith("mlc01/")] + self.assertTrue(mlc) + for entry in mlc: + self.assertEqual( + entry["standalone_path"], "cemu/" + entry["layout_path"].removeprefix("mlc01/") + ) + (otp,) = [e for e in view["files"] if e["name"] == "otp.bin"] + self.assertEqual(otp.get("unplaced"), "outside_bios_folder") + + if __name__ == "__main__": unittest.main()