refactor: one emudeck script-to-profile rule

The target scraper kept its own copy of the EmuScripts table and took
.ps1 files the platform scraper never read, so emudeck-we's template
skeleton was published as a core and removed afterwards by an override.
Both scrapers now read emulator_of_script, and the platform scraper
lists the installers at the pinned revision, as launchers: and the
SteamOS target already did. Targets rescraped, override dropped.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 08:12:19 +01:00
1 parent 19898cb6ea
commit 5c93c6dc6b
9 files changed
+86 -61

No files matched your search

+2
View File
@@ -83,6 +83,7 @@
"galaksija",
"gam4980",
"gambatte",
"gametank",
"gearboy",
"gearcoleco",
"geargrafx",
@@ -346,6 +347,7 @@
"galaksija",
"gam4980",
"gambatte",
"gametank",
"gearboy",
"gearcoleco",
"geargrafx",
-7
View File
@@ -121,13 +121,6 @@ retrodeck:
# includes the frontend name alongside the actual cores.
remove_cores: [retroarch]
emudeck:
targets:
_default:
# EmuDeck's EmuScripts tree carries a template.sh placeholder used
# to scaffold new installers. It is not an emulator.
remove_cores: [template]
lakka:
targets:
generic:
+4 -3
View File
@@ -1,6 +1,6 @@
platform: emudeck
source: https://github.com/dragoonDorise/EmuDeck
scraped_at: '2026-10-17T18:43:57Z'
source: https://github.com/dragoonDorise/EmuDeck/tree/dddb30229adbbd54d5ff7283cfd70e11c4322c26
scraped_at: '2026-11-06T07:10:22Z'
targets:
steamos:
architecture: x86_64
@@ -88,6 +88,7 @@ targets:
- galaksija
- gam4980
- gambatte
- gametank
- gearboy
- gearcoleco
- geargrafx
@@ -352,6 +353,7 @@ targets:
- galaksija
- gam4980
- gambatte
- gametank
- gearboy
- gearcoleco
- geargrafx
@@ -487,7 +489,6 @@ targets:
- supermodel
- swanstation
- tamalibretro
- template
- tgbdual
- theodore
- thepowdertoy
+9 -4
View File
@@ -46,14 +46,18 @@ STORAGE = "${storagePath}"
FLATPAK_DATA = "${HOME}/.var/app/"
# EmuScripts file names that differ from the emulator profile they set up.
# The platform and target scrapers both read them here.
SCRIPT_EMULATORS = {
"pcsx2qt": "pcsx2",
"rpcs3legacy": "rpcs3",
"cemuproton": "cemu",
"rmg": "mupen64plus_next",
}
# The main RetroArch config script installs no emulator.
NOT_EMULATORS = {"retroarch_maincfg"}
# The main RetroArch config script installs no emulator, and emudeck-we's
# emuDeckTemplate.ps1 is the skeleton new installers are copied from.
NOT_EMULATORS = {"retroarch_maincfg", "template"}
# EmuDeck's SteamOS installers are shell scripts, emudeck-we's PowerShell.
SCRIPT_SUFFIXES = (".sh", ".ps1")
# What the emulator does with a directory written into each key.
DIRECTORY_KINDS = {
@@ -66,9 +70,10 @@ DIRECTORY_KINDS = {
def emulator_of_script(file_name: str) -> str | None:
"""The emulator an EmuScripts file sets up, None for a helper script."""
if not file_name.endswith(".sh"):
stem, dot, suffix = file_name.rpartition(".")
if not dot or f".{suffix}" not in SCRIPT_SUFFIXES:
return None
key = re.sub(r"^emuDeck", "", file_name[: -len(".sh")], flags=re.IGNORECASE).lower()
key = re.sub(r"^emuDeck", "", stem, flags=re.IGNORECASE).lower()
if not key or key in NOT_EMULATORS:
return None
return SCRIPT_EMULATORS.get(key, key)
+8 -7
View File
@@ -469,9 +469,10 @@ class Scraper(BaseScraper):
version = fetch_github_latest_version("dragoonDorise/EmuDeck")
cores = self._fetch_installed_emulators()
revision = launcher_revision(self.checkbios_url)
cores = self._fetch_installed_emulators(revision)
standalone = [core for core in cores if core != "retroarch"]
tree = RepoTree.github(EMUDECK_REPO, launcher_revision(self.checkbios_url))
tree = RepoTree.github(EMUDECK_REPO, revision)
libretro = set(installed_libretro_cores(tree.read(RETROARCH_SCRIPT)))
return {
@@ -501,16 +502,16 @@ class Scraper(BaseScraper):
"launchers": extract_launchers(tree),
}
def _fetch_installed_emulators(self) -> list[str]:
"""Fetch the list of emulators installed by EmuDeck from EmuScripts.
Returns core names normalized to match emulator profile keys.
def _fetch_installed_emulators(self, revision: str) -> list[str]:
"""The emulators EmuDeck's EmuScripts install at the pinned revision,
named as their profiles are: the launcher layer and the SteamOS
target are read at that revision too.
"""
import json
api_url = (
"https://api.github.com/repos/dragoonDorise/EmuDeck/"
"contents/functions/EmuScripts"
f"contents/functions/EmuScripts?ref={revision}"
)
try:
req = urllib.request.Request(api_url, headers=github_headers())
@@ -12,7 +12,6 @@ from __future__ import annotations
import argparse
import json
import re
import sys
import urllib.error
import urllib.request
@@ -22,6 +21,7 @@ import yaml
from common import github_token, yaml_load
from ..emudeck_launchers import SCRIPT_SUFFIXES, emulator_of_script
from . import BaseTargetScraper
PLATFORM_NAME = "emudeck"
@@ -33,24 +33,6 @@ WINDOWS_API = (
"https://api.github.com/repos/EmuDeck/emudeck-we/contents/functions/EmuScripts"
)
# Map EmuDeck script names to emulator profile keys
# Script naming: emuDeckDolphin.sh -> dolphin
# Some need explicit mapping when names differ
_NAME_OVERRIDES: dict[str, str] = {
"pcsx2qt": "pcsx2",
"rpcs3legacy": "rpcs3",
"cemuproton": "cemu",
"rmg": "mupen64plus_next",
"bigpemu": "bigpemu",
"eden": "eden",
"suyu": "suyu",
"ares": "ares",
}
# Scripts that are not emulators (config helpers, etc.)
_SKIP = {"retroarch_maincfg", "retroarch"}
def _fetch(url: str) -> str:
headers = {"User-Agent": "retrobios-scraper/1.0"}
token = github_token()
@@ -69,26 +51,13 @@ def _list_emuscripts(api_url: str) -> list[str]:
"""List emulator script filenames from GitHub API."""
entries = json.loads(_fetch(api_url))
names = [
e["name"] for e in entries if e.get("name", "").endswith((".sh", ".ps1"))
e["name"] for e in entries if e.get("name", "").endswith(SCRIPT_SUFFIXES)
]
if not names:
raise RuntimeError(f"no emulator scripts listed at {api_url}")
return names
def _script_to_core(filename: str) -> str | None:
"""Convert EmuScripts filename to core profile key."""
# Strip extension and emuDeck prefix
name = re.sub(r"\.(sh|ps1)$", "", filename, flags=re.IGNORECASE)
name = re.sub(r"^emuDeck", "", name, flags=re.IGNORECASE)
if not name:
return None
key = name.lower()
if key in _SKIP:
return None
return _NAME_OVERRIDES.get(key, key)
class Scraper(BaseTargetScraper):
"""Fetches emulator lists for EmuDeck SteamOS and Windows targets."""
@@ -114,14 +83,13 @@ class Scraper(BaseTargetScraper):
seen: set[str] = set()
has_retroarch = False
for script in scripts:
core = _script_to_core(script)
if core and core not in seen:
core = emulator_of_script(script)
# RetroArch stands for its libretro cores, added below.
if core == "retroarch":
has_retroarch = True
elif core and core not in seen:
seen.add(core)
cores.append(core)
# Detect RetroArch presence (provides all libretro cores)
name = re.sub(r"\.(sh|ps1)$", "", script, flags=re.IGNORECASE)
if name.lower() in ("emudeckretroarch", "retroarch_maincfg"):
has_retroarch = True
standalone_count = len(cores)
# EmuDeck ships RetroArch = all its libretro cores are available
+2
View File
@@ -373,6 +373,8 @@ class Helpers(unittest.TestCase):
self.assertEqual(el.emulator_of_script("emuDeckXemu.sh"), "xemu")
self.assertIsNone(el.emulator_of_script("RetroArch_maincfg.sh"))
self.assertIsNone(el.emulator_of_script("README.md"))
self.assertEqual(el.emulator_of_script("emuDeckPCSX2QT.ps1"), "pcsx2")
self.assertIsNone(el.emulator_of_script("emuDeckTemplate.ps1"))
def test_the_revision_comes_from_the_pinned_url(self):
url = (
+1 -1
View File
@@ -724,7 +724,7 @@ class UnreadableReleaseStopsTheScrape(unittest.TestCase):
with mock.patch("urllib.request.urlopen", self._refuse), self.assertRaises(
RuntimeError
):
emudeck_scraper.Scraper()._fetch_installed_emulators()
emudeck_scraper.Scraper(checkbios_url="pinned")._fetch_installed_emulators("0" * 40)
def test_libretro_core_metadata_raises(self):
"""libretro dropped every system name and manufacturer when the
+53
View File
@@ -541,6 +541,59 @@ class TargetsReadThePlatformRevision(unittest.TestCase):
self.assertTrue(steamos)
self.assertTrue(all(u.endswith(f"?ref={pin}") for u in steamos), steamos)
def test_emudeck_platform_lists_its_installers_at_the_pin(self):
"""cores: was read on master while launchers: and the SteamOS target
were read at the pin: the YAML described two revisions."""
from scripts.scraper import emudeck_scraper
pin = "d" * 40
seen: list[str] = []
class _Body:
def __init__(self, url):
seen.append(url)
def __enter__(self):
return self
def __exit__(self, *_exc):
return False
def read(self):
return json.dumps([{"name": "emuDeckRMG.sh"}]).encode()
with mock.patch("urllib.request.urlopen", lambda req, **_kw: _Body(req.full_url)):
cores = emudeck_scraper.Scraper(checkbios_url="pinned")._fetch_installed_emulators(pin)
self.assertEqual(cores, ["mupen64plus_next"])
self.assertEqual(len(seen), 1)
self.assertTrue(seen[0].endswith(f"?ref={pin}"), seen)
class EmuDeckScriptNamesHaveOneRule(unittest.TestCase):
"""The target scraper kept its own copy of the EmuScripts-to-profile
table: it published emudeck-we's template skeleton as a core, and a
rename added to one copy only made --target filter that core out."""
def test_the_target_scraper_names_scripts_as_the_platform_does(self):
from scripts.scraper.emudeck_launchers import emulator_of_script
listing = [
"emuDeckRMG.sh",
"emuDeckPCSX2QT.ps1",
"emuDeckTemplate.ps1",
"emuDeckRetroArch.ps1",
"RetroArch_maincfg.sh",
"emuDeckXemu.ps1",
]
scraper = emudeck_targets_scraper.Scraper(revision="0" * 40)
with mock.patch.object(
emudeck_targets_scraper, "_list_emuscripts", return_value=listing
), mock.patch.object(scraper, "_load_retroarch_cores", return_value=["mgba"]):
cores = scraper._fetch_cores_for_target("unused", "Windows")
standalone = {emulator_of_script(name) for name in listing} - {None, "retroarch"}
self.assertEqual(standalone, {"mupen64plus_next", "pcsx2", "xemu"})
self.assertEqual(cores, sorted(standalone | {"mgba"}))
class HashScrapersRefuseAnUnknownVersion(unittest.TestCase):
"""mame_hash_scraper wrote core_version "unknown" after a failed API call