From d92291684c55eabe0694111267d603627dea849a Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:53:57 +0200 Subject: [PATCH] fix: dual-mode only what ROCKNIX runs both ways The scraper declared every standalone package launched both ways, reasoning that RetroArch carries every libretro core. package.mk says otherwise: it adds ARMSX2 as armsx2-sa only (add_emu_core ps2 armsx2 armsx2-sa true), so the libretro build's pcsx2/resources entries reached ROCKNIX's list as armsx2-sa files the standalone installs itself. An emulator is now dual-mode only when package.mk also adds it as a retroarch core: ten of the 63 standalone names. --- platforms/rocknix.yml | 43 ----------------------------- scripts/scraper/rocknix_scraper.py | 44 ++++++++++++++++++++++++++---- tests/test_scraper_contract.py | 19 +++++++++++++ 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/platforms/rocknix.yml b/platforms/rocknix.yml index d4cde756..6b69b8fe 100644 --- a/platforms/rocknix.yml +++ b/platforms/rocknix.yml @@ -135,67 +135,24 @@ standalone_cores: - yabasanshiro - yabasanshiro-sa dual_mode_cores: -- aethersx2 -- aethersx2-sa -- amiberry -- ares -- ares-sa -- armsx2 -- armsx2-sa -- azahar -- azahar-sa -- bigpemu -- bigpemu-sa -- cemu -- cemu-sa -- daedalusx64 -- daedalusx64-sa - dolphin - dolphin-sa -- drastic -- drastic-sa -- dsperate -- dsperate-sa - duckstation - duckstation-sa - flycast - flycast-sa -- gopher64 -- gopher64-sa -- gzdoom -- gzdoom-sa - hatari - hatarisa -- heroic -- hypseus-singe -- mednafen - melonds - melonds-sa - mupen64plus - mupen64plus-sa -- nanoboyadvance -- nanoboyadvance-sa -- openbor -- pico-8 - ppsspp - ppsspp-sa -- rpcs3 -- rpcs3-sa - scummvm - scummvm-sa - skyemu - skyemu-sa -- steam -- supermodel -- supermodel-sa -- touchhle -- touchhle-sa -- vice -- vice-sa -- vita3k -- vita3k-sa -- xemu -- xemu-sa - yabasanshiro - yabasanshiro-sa systems: diff --git a/scripts/scraper/rocknix_scraper.py b/scripts/scraper/rocknix_scraper.py index 8c4c8431..fddc022e 100644 --- a/scripts/scraper/rocknix_scraper.py +++ b/scripts/scraper/rocknix_scraper.py @@ -43,6 +43,10 @@ SOURCE_TEMPLATE = ( # The branch a test or a manual run can still name. A scrape reads the tag # of the release its YAML is labelled with. SOURCE_URL = SOURCE_TEMPLATE.format(ref="next") +EMULATORS_MK_URL = ( + "https://raw.githubusercontent.com/ROCKNIX/distribution/{ref}" + "/projects/ROCKNIX/packages/virtual/emulators/package.mk" +) STANDALONE_TREE_URL = ( "https://api.github.com/repos/ROCKNIX/distribution/contents/" "projects/ROCKNIX/packages/emulators/standalone?ref={ref}" @@ -59,11 +63,26 @@ def standalone_emulators(package_names: list[str]) -> list[str]: names: set[str] = set() for package in package_names: names.add(package) - stripped = re.sub(r"-?sa$", "", package) - if stripped and stripped != package: + stripped = _emulator_of(package) + if stripped != package: names.add(stripped) return sorted(names) +def _emulator_of(package: str) -> str: + return re.sub(r"-?sa$", "", package) or package + + +def dual_mode_emulators(standalone: list[str], package_mk: str) -> list[str]: + """The standalone emulators RetroArch also runs as a core. + + package.mk adds each launch option with `add_emu_core + `. Only an emulator also added as `retroarch ` is + launched both ways: ARMSX2 is added as armsx2-sa alone. + """ + retroarch = set(re.findall(r"^\s*add_emu_core\s+\S+\s+retroarch\s+(\S+)", package_mk, re.M)) + return sorted(name for name in standalone if _emulator_of(name) in retroarch) + + # ROCKNIX system key -> retrobios system ID SYSTEM_SLUG_MAP = { "xbox": "microsoft-xbox", @@ -171,6 +190,21 @@ class Scraper(BaseScraper): raise RuntimeError(f"no standalone emulator package at {url}") return standalone_emulators(packages) + def _fetch_emulators_mk(self) -> str: + """The package.mk that adds each emulator and core to es_systems.""" + import urllib.error + import urllib.request + + url = EMULATORS_MK_URL.format(ref=self.tag or "next") + try: + with urllib.request.urlopen(url, timeout=30) as resp: + text = resp.read().decode("utf-8") + except urllib.error.URLError as e: + raise ConnectionError(f"Failed to fetch {url}: {e}") from e + if "add_emu_core" not in text: + raise RuntimeError(f"no add_emu_core line in {url}") + return text + def validate_format(self, raw_data: str) -> bool: """Validate the rocknix-systems format.""" has_dict = re.search(r"^systems\s*=\s*\{", raw_data, re.MULTILINE) is not None @@ -236,9 +270,9 @@ class Scraper(BaseScraper): # emulators ROCKNIX packages beside it. "cores": ["retroarch", *standalone], "standalone_cores": standalone, - # RetroArch carries every libretro core beside them, so an - # emulator with a libretro build is launched both ways. - "dual_mode_cores": list(standalone), + # Launched both ways only where package.mk also adds the + # emulator as a RetroArch core. + "dual_mode_cores": dual_mode_emulators(standalone, self._fetch_emulators_mk()), "systems": systems, } diff --git a/tests/test_scraper_contract.py b/tests/test_scraper_contract.py index 8dfd13b5..0f7119f9 100644 --- a/tests/test_scraper_contract.py +++ b/tests/test_scraper_contract.py @@ -414,6 +414,25 @@ class RocknixRunsItsStandaloneEmulators(unittest.TestCase): ["duckstation", "duckstation-sa", "hatari", "hatarisa", "openbor"], ) + def test_only_an_emulator_retroarch_also_runs_is_dual(self): + """Every standalone package was declared launched both ways, so + ARMSX2's libretro layout reached ROCKNIX, whose package.mk adds it + as armsx2-sa only (add_emu_core ps2 armsx2 armsx2-sa true).""" + from scraper.rocknix_scraper import dual_mode_emulators + + package_mk = ( + " add_emu_core psx retroarch duckstation false\n" + " add_emu_core psx duckstation duckstation-sa true\n" + " add_emu_core ps2 armsx2 armsx2-sa true\n" + " add_emu_core atarist retroarch hatari false\n" + ) + standalone = ["armsx2", "armsx2-sa", "duckstation", "duckstation-sa", + "hatari", "hatarisa"] + self.assertEqual( + dual_mode_emulators(standalone, package_mk), + ["duckstation", "duckstation-sa", "hatari", "hatarisa"], + ) + def test_the_platform_file_is_read_at_its_release(self): import yaml