feat: run retrodeck dual-mode cores as libretro

This commit is contained in:
Abdessamad Derraz committed 2026-10-22 23:21:44 +02:00
commit 7a5bc1d2e3
5 files changed
+21916 -11678

No files matched your search

+2 -2
View File
@@ -120,7 +120,7 @@ Full list with per-file details: **[https://abdess.github.io/retrobios/](https:/
| Recalbox | 351/351 | 2,762/2,933 | MD5 hash | | Recalbox | 351/351 | 2,762/2,933 | MD5 hash |
| RetroArch | 526/526 | 7,691/7,733 | file presence | | RetroArch | 526/526 | 7,691/7,733 | file presence |
| RetroBat | 348/348 | 4,752/9,218 | MD5 hash | | RetroBat | 348/348 | 4,752/9,218 | MD5 hash |
| RetroDECK | 2,007/2,008 | 6,287/6,547 | MD5 hash | | RetroDECK | 2,007/2,008 | 6,480/6,741 | MD5 hash |
| RetroPie * | 532/532 | 7,858/12,489 | file presence | | RetroPie * | 532/532 | 7,858/12,489 | file presence |
| RomM | 383/383 | 83/860 | MD5 hash | | RomM | 383/383 | 83/860 | MD5 hash |
@@ -209,4 +209,4 @@ The scripts and tooling are released under the [MIT License](LICENSE).
The BIOS and firmware files are not covered by that license: they are third-party system software, preserved and provided for personal backup, archival, and interoperability with emulation software. [NOTICE](NOTICE) sets out their status and how to ask for a file to be removed. The BIOS and firmware files are not covered by that license: they are third-party system software, preserved and provided for personal backup, archival, and interoperability with emulation software. [NOTICE](NOTICE) sets out their status and how to ask for a file to be removed.
The reasoning, and where it is weakest, is in [the project's legal position](https://abdess.github.io/retrobios/wiki/legal/). The reasoning, and where it is weakest, is in [the project's legal position](https://abdess.github.io/retrobios/wiki/legal/).
*Auto-generated on 2026-10-22T19:15:47Z* *Auto-generated on 2026-10-22T20:58:47Z*
+21809 -11671
View File
File diff suppressed because it is too large. Load diff
+8
View File
@@ -44,6 +44,14 @@ standalone_cores:
- vita3k - vita3k
- xemu - xemu
- xroar - xroar
dual_mode_cores:
- azahar
- cemu
- dolphin
- mame
- melonds
- pcsx2
- ppsspp
systems: systems:
triforce: triforce:
files: files:
+25 -5
View File
@@ -96,6 +96,25 @@ def es_systems_libretro_systems(text: str) -> dict[str, list[str]]:
if match: if match:
found.setdefault(match.group(1), set()).add(folder) found.setdefault(match.group(1), set()).add(folder)
return {core: sorted(folders) for core, folders in sorted(found.items())} 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 = { NON_EMULATOR_COMPONENTS = {
"framework", "framework",
"es-de", "es-de",
@@ -533,6 +552,9 @@ class Scraper(BaseScraper):
sys_entry["files"].append(requirement_entry(req)) sys_entry["files"].append(requirement_entry(req))
version = fetch_github_latest_version("RetroDECK/RetroDECK") 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 { return {
"platform": "RetroDECK", "platform": "RetroDECK",
@@ -547,12 +569,10 @@ class Scraper(BaseScraper):
"hash_type": "md5", "hash_type": "md5",
"verification_mode": "md5", "verification_mode": "md5",
"cores": cores, "cores": cores,
# Each component is one emulator RetroDECK ships and launches on "standalone_cores": standalone,
# its own, and retroarch is the one that runs cores. **({"dual_mode_cores": dual} if dual else {}),
"standalone_cores": [c for c in cores if c != "retroarch"],
"systems": systems, "systems": systems,
# The ROM folders each libretro core runs for: its save folders. "libretro_systems": libretro_systems,
"libretro_systems": self._fetch_libretro_systems(),
"launchers": extract_launchers(cores, self.component_file), "launchers": extract_launchers(cores, self.component_file),
} }
+72
View File
@@ -472,6 +472,78 @@ class RetroDeckCoresAreItsComponents(unittest.TestCase):
self.assertNotIn("cores", registry["platforms"]["retrodeck"]) 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): class BizHawkRunsAresForN64Only(unittest.TestCase):
"""BizHawk embeds ares as Ares64, its N64 core. Mapping it to the ares """BizHawk embeds ares as Ares64, its N64 core. Mapping it to the ares
profile claimed thirty systems and accused FirmwareDatabase.cs of a profile claimed thirty systems and accused FirmwareDatabase.cs of a