feat: read retrodeck dual-mode cores from es-de

RetroDECK listed every component standalone only. Azahar, Cemu, Dolphin,
MAME, melonDS, PCSX2 and PPSSPP also run through RetroArch: RetroDECK/ES-DE
es_systems.xml at 15e1b236 loads <name>_libretro.so beside each standalone
command, and the cores ship whole from the buildbot nightly
RetroArch_cores.7z (retroarch/component_recipe.json:250-262). Their
libretro layouts, Azahar's save-dir files among them, now reach the pack.
This commit is contained in:
Abdessamad Derraz committed 2026-10-22 22:59:47 +02:00
1 parent 8d0cd27103
commit be09d517d9
3 files changed
+105 -5

No files matched your search

+8
View File
@@ -44,6 +44,14 @@ standalone_cores:
- vita3k
- xemu
- xroar
dual_mode_cores:
- azahar
- cemu
- dolphin
- mame
- melonds
- pcsx2
- ppsspp
systems:
triforce:
files:
+25 -5
View File
@@ -96,6 +96,25 @@ def es_systems_libretro_systems(text: str) -> dict[str, list[str]]:
if match:
found.setdefault(match.group(1), set()).add(folder)
return {core: sorted(folders) for core, folders in sorted(found.items())}
def component_modes(
components: list[str], libretro_systems: dict[str, list[str]]
) -> tuple[list[str], list[str]]:
"""(standalone_cores, dual_mode_cores) of RetroDECK's emulator components.
Every component but retroarch is an emulator RetroDECK launches on its
own (its component_launcher.sh execs the emulator's binary). One whose name
is also a core an ES-DE command loads through RetroArch is offered both
ways: es_systems.xml lists "Azahar (Standalone)" and "Azahar"
(azahar_libretro.so) for n3ds. The core itself ships, RetroArch's cores
coming whole from the buildbot's nightly RetroArch_cores.7z
(retroarch/component_recipe.json:250-262 at components fcd0d729).
"""
standalone = [c for c in components if c != "retroarch"]
return standalone, [c for c in standalone if c in libretro_systems]
NON_EMULATOR_COMPONENTS = {
"framework",
"es-de",
@@ -533,6 +552,9 @@ class Scraper(BaseScraper):
sys_entry["files"].append(requirement_entry(req))
version = fetch_github_latest_version("RetroDECK/RetroDECK")
# The ROM folders each libretro core runs for: its save folders.
libretro_systems = self._fetch_libretro_systems()
standalone, dual = component_modes(cores, libretro_systems)
return {
"platform": "RetroDECK",
@@ -547,12 +569,10 @@ class Scraper(BaseScraper):
"hash_type": "md5",
"verification_mode": "md5",
"cores": cores,
# Each component is one emulator RetroDECK ships and launches on
# its own, and retroarch is the one that runs cores.
"standalone_cores": [c for c in cores if c != "retroarch"],
"standalone_cores": standalone,
**({"dual_mode_cores": dual} if dual else {}),
"systems": systems,
# The ROM folders each libretro core runs for: its save folders.
"libretro_systems": self._fetch_libretro_systems(),
"libretro_systems": libretro_systems,
"launchers": extract_launchers(cores, self.component_file),
}
+72
View File
@@ -472,6 +472,78 @@ class RetroDeckCoresAreItsComponents(unittest.TestCase):
self.assertNotIn("cores", registry["platforms"]["retrodeck"])
class RetroDeckComponentModes(unittest.TestCase):
"""Every RetroDECK component was written standalone only: Azahar, whose
ES-DE system also loads azahar_libretro.so, never got its libretro
files, which the core reads below its save folder."""
# Commands as RetroDECK/ES-DE 15e1b236 writes them in
# resources/systems/linux/es_systems.xml (n3ds at 1379-1383, psx at
# 1843 and 1847). The commented-out DuckStation core is the fixture's own.
ES_SYSTEMS = """\
<?xml version="1.0"?>
<systemList>
<system><name>n3ds</name><path>%ROMPATH%/n3ds</path>
<command label="Azahar (Standalone)">%EMULATOR_AZAHAR% %ROM%</command>
<command label="Azahar">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/azahar_libretro.so %ROM%</command>
<command label="Citra">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/citra_libretro.so %ROM%</command>
</system>
<system><name>psx</name><path>%ROMPATH%/psx</path>
<command label="SwanStation">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/swanstation_libretro.so %ROM%</command>
<!-- <command label="DuckStation">%EMULATOR_RETROARCH% -L %CORE_RETROARCH%/duckstation_libretro.so %ROM%</command> -->
<command label="DuckStation (Legacy) (Standalone)">%EMULATOR_DUCKSTATION% -batch %ROM%</command>
</system>
</systemList>
"""
def _config(self) -> dict:
from scraper import retrodeck_scraper as rd
with tempfile.TemporaryDirectory() as tmp:
for component in ("azahar", "duckstation", "es-de", "retroarch"):
(Path(tmp) / component).mkdir()
(Path(tmp) / component / "component_manifest.json").write_text(
json.dumps({component: {"system": component}})
)
scraper = rd.Scraper(manifests_dir=tmp)
systems = rd.es_systems_libretro_systems(self.ES_SYSTEMS)
with mock.patch.object(rd, "fetch_github_latest_version", return_value="0.10.10b"), \
mock.patch.object(rd, "extract_launchers", return_value=[]), \
mock.patch.object(rd.Scraper, "_fetch_libretro_systems", return_value=systems):
return scraper.generate_platform_yaml()
def test_a_component_es_de_also_runs_as_a_core_is_dual_mode(self):
config = self._config()
self.assertEqual(config["cores"], ["azahar", "duckstation", "retroarch"])
self.assertEqual(config["standalone_cores"], ["azahar", "duckstation"])
self.assertEqual(config["dual_mode_cores"], ["azahar"])
def test_a_commented_out_core_command_offers_nothing(self):
"""duckstation_libretro.so sits in a comment: DuckStation runs standalone only."""
config = self._config()
self.assertNotIn("duckstation", config["dual_mode_cores"])
self.assertNotIn("duckstation", config["libretro_systems"])
def test_a_core_without_a_component_is_not_a_component(self):
"""citra_libretro.so runs through RetroArch, and RetroDECK ships no citra build."""
from scraper.retrodeck_scraper import component_modes
standalone, dual = component_modes(
["azahar", "retroarch"], {"azahar": ["n3ds"], "citra": ["n3ds"]}
)
self.assertEqual((standalone, dual), (["azahar"], ["azahar"]))
def test_the_platform_file_carries_the_modes(self):
import yaml
root = Path(__file__).resolve().parents[1]
platform = yaml.safe_load((root / "platforms" / "retrodeck.yml").read_text())
dual = set(platform.get("dual_mode_cores") or [])
self.assertIn("azahar", dual)
self.assertLessEqual(dual, set(platform["standalone_cores"]))
self.assertLessEqual(dual, set(platform["libretro_systems"]))
class BizHawkRunsAresForN64Only(unittest.TestCase):
"""BizHawk embeds ares as Ares64, its N64 core. Mapping it to the ares
profile claimed thirty systems and accused FirmwareDatabase.cs of a