fix: filter baseline by platform-scoped cores, include retroarch cores in emudeck targets

This commit is contained in:
Abdessamad Derraz
2026-03-26 10:20:43 +01:00
parent 6402b77374
commit 0a1880f606
5 changed files with 547 additions and 67 deletions

View File

@@ -1,40 +1,461 @@
platform: emudeck
source: https://github.com/dragoonDorise/EmuDeck
scraped_at: '2026-03-26T08:44:04Z'
scraped_at: '2026-03-26T09:14:02Z'
targets:
steamos:
architecture: x86_64
cores:
- beetle_psx
- beetle_saturn
- '2048'
- 3dengine
- '81'
- DoubleCherryGB
- a5200
- amiarcadia
- anarch
- applewin
- ardens
- arduous
- ares
- atari800
- azahar
- b2
- bigpemu
- bk
- blastem
- bluemsx
- boom3
- boom3_xp
- bsnes
- bsnes-jg
- bsnes2014_accuracy
- bsnes2014_balanced
- bsnes2014_performance
- bsnes_cplusplus98
- bsnes_hd_beta
- bsnes_mercury_accuracy
- bsnes_mercury_balanced
- bsnes_mercury_performance
- cannonball
- cap32
- cdi2015
- cemu
- chailove
- citra
- citra2018
- citron
- clownmdemu
- craft
- crocods
- desmume
- desmume2015
- dice
- dinothawr
- dirksimple
- dolphin
- dosbox_core
- dosbox_pure
- dosbox_svn
- doukutsu_rs
- duckstation
- easyrpg
- ecwolf
- eden
- ep128emu_core
- fbalpha
- fbalpha2012
- fbalpha2012_cps1
- fbalpha2012_cps2
- fbalpha2012_cps3
- fbalpha2012_neogeo
- fbneo
- fceumm
- fixgb
- fixnes
- flycast
- genesisplusgx
- fmsx
- freechaf
- freeintv
- frodo
- fuse
- galaksija
- gam4980
- gambatte
- gearboy
- gearcoleco
- geargrafx
- gearlynx
- gearsystem
- genesis_plus_gx
- genesis_plus_gx_wide
- geolith
- gme
- gong
- gpsp
- gw
- handy
- hatari
- holani
- jaxe
- jollycv
- jumpnbump
- kronos
- lowresnx
- lutro
- m2000
- mame
- mame2000
- mame2003
- mame2003_midway
- mame2003_plus
- mame2010
- mcsoftserve
- mednafen_gba
- mednafen_lynx
- mednafen_ngp
- mednafen_pce
- mednafen_pce_fast
- mednafen_pcfx
- mednafen_psx
- mednafen_psx_hw
- mednafen_saturn
- mednafen_snes
- mednafen_supafaust
- mednafen_supergrafx
- mednafen_vb
- mednafen_wswan
- melonds
- melondsds
- mesen
- mesen-s
- meteor
- mgba
- minivmac
- model2
- mojozork
- mrboom
- mu
- mupen64plus_next
- nekop2
- neocd
- nestopia
- noods
- np2kai
- numero
- nxengine
- o2em
- oberon
- openlara
- opera
- panda3ds
- parallel_n64
- pcsx2
- pcsx_rearmed
- pd777
- picodrive
- play
- pocketcdg
- pokemini
- potator
- ppsspp
- prboom
- primehack
- prosystem
- puae
- puae2021
- px68k
- qemu
- quasi88
- quicknes
- race
- reminiscence
- retro8
- romcleaner
- rpcs3
- ryujinx
- same_cdi
- sameboy
- sameduck
- scummvm
- shadps4
- skyemu
- smsplus
- snes9x
- snes9x2002
- snes9x2005
- snes9x2005_plus
- snes9x2010
- squirreljme
- stella
- stella2014
- stella2023
- superbroswar
- supermodel
- suyu
- swanstation
- tamalibretro
- tgbdual
- theodore
- thepowdertoy
- tic80
- tyrquake
- uw8
- uzem
- vaporspec
- vba_next
- vbam
- vecx
- vemulator
- vice_x128
- vice_x64
- vice_x64sc
- vice_xcbm2
- vice_xcbm5x0
- vice_xpet
- vice_xplus4
- vice_xscpu64
- vice_xvic
- vircon32
- virtualjaguar
- virtualxt
- vita3k
- vitaquake2
- vitaquake2-rogue
- vitaquake2-xatrix
- vitaquake2-zaero
- vitaquake3
- wasm4
- x1
- xemu
- xenia
- xrick
- yabasanshiro
- yabause
- yuzu
windows:
architecture: x86_64
cores:
- beetle_psx
- beetle_saturn
- '2048'
- 3dengine
- '81'
- DoubleCherryGB
- a5200
- amiarcadia
- anarch
- applewin
- ardens
- arduous
- atari800
- azahar
- b2
- bigpemu
- bk
- blastem
- bluemsx
- boom3
- boom3_xp
- bsnes
- bsnes-jg
- bsnes2014_accuracy
- bsnes2014_balanced
- bsnes2014_performance
- bsnes_cplusplus98
- bsnes_hd_beta
- bsnes_mercury_accuracy
- bsnes_mercury_balanced
- bsnes_mercury_performance
- cannonball
- cap32
- cdi2015
- cemu
- chailove
- citra
- citra2018
- citron
- clownmdemu
- craft
- crocods
- desmume
- desmume2015
- dice
- dinothawr
- dirksimple
- dolphin
- dosbox_core
- dosbox_pure
- dosbox_svn
- doukutsu_rs
- duckstation
- easyrpg
- ecwolf
- eden
- ep128emu_core
- fbalpha
- fbalpha2012
- fbalpha2012_cps1
- fbalpha2012_cps2
- fbalpha2012_cps3
- fbalpha2012_neogeo
- fbneo
- fceumm
- fixgb
- fixnes
- flycast
- genesisplusgx
- fmsx
- freechaf
- freeintv
- frodo
- fuse
- galaksija
- gam4980
- gambatte
- gearboy
- gearcoleco
- geargrafx
- gearlynx
- gearsystem
- genesis_plus_gx
- genesis_plus_gx_wide
- geolith
- gme
- gong
- gpsp
- gw
- handy
- hatari
- holani
- jaxe
- jollycv
- jumpnbump
- kronos
- lowresnx
- lutro
- m2000
- mame
- mame2000
- mame2003
- mame2003_midway
- mame2003_plus
- mame2010
- mcsoftserve
- mednafen_gba
- mednafen_lynx
- mednafen_ngp
- mednafen_pce
- mednafen_pce_fast
- mednafen_pcfx
- mednafen_psx
- mednafen_psx_hw
- mednafen_saturn
- mednafen_snes
- mednafen_supafaust
- mednafen_supergrafx
- mednafen_vb
- mednafen_wswan
- melonds
- melondsds
- mesen
- mesen-s
- meteor
- mgba
- minivmac
- model2
- mojozork
- mrboom
- mu
- mupen64plus_next
- nekop2
- neocd
- nestopia
- noods
- np2kai
- numero
- nxengine
- o2em
- oberon
- openlara
- opera
- panda3ds
- parallel_n64
- pcsx2
- pcsx_rearmed
- pd777
- picodrive
- play
- pocketcdg
- pokemini
- potator
- ppsspp
- prboom
- primehack
- prosystem
- puae
- puae2021
- px68k
- qemu
- quasi88
- quicknes
- race
- reminiscence
- retro8
- romcleaner
- rpcs3
- ryujinx
- same_cdi
- sameboy
- sameduck
- scummvm
- shadps4
- skyemu
- smsplus
- snes9x
- snes9x2002
- snes9x2005
- snes9x2005_plus
- snes9x2010
- squirreljme
- stella
- stella2014
- stella2023
- superbroswar
- supermodel
- swanstation
- tamalibretro
- template
- tgbdual
- theodore
- thepowdertoy
- tic80
- tyrquake
- uw8
- uzem
- vaporspec
- vba_next
- vbam
- vecx
- vemulator
- vice_x128
- vice_x64
- vice_x64sc
- vice_xcbm2
- vice_xcbm5x0
- vice_xpet
- vice_xplus4
- vice_xscpu64
- vice_xvic
- vircon32
- virtualjaguar
- virtualxt
- vita3k
- vitaquake2
- vitaquake2-rogue
- vitaquake2-xatrix
- vitaquake2-zaero
- vitaquake3
- wasm4
- x1
- xemu
- xenia
- xrick
- yabasanshiro
- yabause
- yuzu

View File

@@ -650,11 +650,16 @@ def filter_systems_by_target(
systems: dict[str, dict],
profiles: dict[str, dict],
target_cores: set[str] | None,
platform_cores: set[str] | None = None,
) -> dict[str, dict]:
"""Filter platform systems to only those reachable by target cores.
A system is reachable if at least one core that emulates it is available
on the target. Returns the filtered systems dict (or all if no target).
on the target. Only considers cores relevant to the platform (from
platform_cores). Systems whose cores are all outside the platform's
scope are kept (no information to exclude them).
Returns the filtered systems dict (or all if no target).
"""
if target_cores is None:
return systems
@@ -667,19 +672,26 @@ def filter_systems_by_target(
upstream_to_profile[str(alias)] = name
expanded_target = {upstream_to_profile.get(c, c) for c in target_cores}
# Build system -> profile keys mapping
# Build system -> profile keys mapping (only platform-relevant cores)
system_to_cores: dict[str, set[str]] = {}
for name, p in profiles.items():
if p.get("type") == "alias":
continue
if platform_cores is not None and name not in platform_cores:
continue
for sid in p.get("systems", []):
system_to_cores.setdefault(sid, set()).add(name)
filtered = {}
for sys_id, sys_data in systems.items():
cores_for_system = system_to_cores.get(sys_id, set())
if cores_for_system & expanded_target:
if not cores_for_system:
# No platform-relevant core maps to this system — keep it
filtered[sys_id] = sys_data
elif cores_for_system & expanded_target:
# At least one core for this system is on the target
filtered[sys_id] = sys_data
# else: all platform cores for this system are off-target — exclude
return filtered

View File

@@ -265,10 +265,13 @@ def generate_pack(
validation_index = _build_validation_index(emu_profiles)
# Filter systems by target if specified
from common import resolve_platform_cores
plat_cores = resolve_platform_cores(config, emu_profiles or {}) if target_cores else None
pack_systems = filter_systems_by_target(
config.get("systems", {}),
emu_profiles or {},
target_cores,
platform_cores=plat_cores,
)
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:

View File

@@ -1,12 +1,13 @@
"""Scraper for EmuDeck emulator targets.
Sources:
SteamOS: dragoonDorise/EmuDeck — checkBIOS.sh, install scripts
Windows: EmuDeck/emudeck-we — checkBIOS.ps1
SteamOS: dragoonDorise/EmuDeck — functions/EmuScripts/*.sh
Windows: EmuDeck/emudeck-we — functions/EmuScripts/*.ps1
"""
from __future__ import annotations
import argparse
import json
import re
import sys
import urllib.error
@@ -19,38 +20,25 @@ from . import BaseTargetScraper
PLATFORM_NAME = "emudeck"
STEAMOS_CHECKBIOS_URL = (
"https://raw.githubusercontent.com/dragoonDorise/EmuDeck/"
"main/functions/checkBIOS.sh"
)
WINDOWS_CHECKBIOS_URL = (
"https://raw.githubusercontent.com/EmuDeck/emudeck-we/"
"main/functions/checkBIOS.ps1"
)
STEAMOS_API = "https://api.github.com/repos/dragoonDorise/EmuDeck/contents/functions/EmuScripts"
WINDOWS_API = "https://api.github.com/repos/EmuDeck/emudeck-we/contents/functions/EmuScripts"
# checkBIOS functions check by system, not by core. Map to actual emulators.
# Source: EmuDeck install scripts + wiki documentation.
_BIOS_SYSTEM_TO_CORES: dict[str, list[str]] = {
"ps1bios": ["beetle_psx", "pcsx_rearmed", "duckstation", "swanstation"],
"ps2bios": ["pcsx2"],
"segacdbios": ["genesisplusgx", "picodrive"],
"saturnbios": ["beetle_saturn", "kronos", "yabasanshiro", "yabause"],
"dreamcastbios": ["flycast"],
"dsbios": ["melonds", "desmume"],
"ryujinxbios": [], # standalone, not libretro
"yuzubios": [], # standalone, not libretro
"citronbios": ["citron"],
# 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",
}
# Patterns for BIOS check function names
_SH_EMULATOR_RE = re.compile(
r'(?:function\s+|^)(?:check|install|setup)([A-Za-z0-9_]+)\s*\(',
re.MULTILINE,
)
_PS1_EMULATOR_RE = re.compile(
r'function\s+(?:check|install|setup)([A-Za-z0-9_]+)\s*(?:\(\))?\s*\{',
re.MULTILINE | re.IGNORECASE,
)
# Scripts that are not emulators (config helpers, etc.)
_SKIP = {"retroarch_maincfg", "retroarch"}
def _fetch(url: str) -> str | None:
@@ -65,19 +53,31 @@ def _fetch(url: str) -> str | None:
return None
def _extract_cores(text: str, pattern: re.Pattern[str]) -> list[str]:
"""Extract core names by parsing BIOS check functions and mapping to cores."""
seen: set[str] = set()
results: list[str] = []
for m in pattern.finditer(text):
system_name = m.group(1).lower()
# Map system BIOS check to actual core names
cores = _BIOS_SYSTEM_TO_CORES.get(system_name, [])
for core in cores:
if core not in seen:
seen.add(core)
results.append(core)
return sorted(results)
def _list_emuscripts(api_url: str) -> list[str]:
"""List emulator script filenames from GitHub API."""
raw = _fetch(api_url)
if not raw:
return []
entries = json.loads(raw)
names = []
for e in entries:
name = e.get("name", "")
if name.endswith(".sh") or name.endswith(".ps1"):
names.append(name)
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):
@@ -86,24 +86,66 @@ class Scraper(BaseTargetScraper):
def __init__(self, url: str = "https://github.com/dragoonDorise/EmuDeck"):
super().__init__(url=url)
def _fetch_cores_for_target(self, api_url: str, label: str,
arch: str = "x86_64") -> list[str]:
print(f" fetching {label} EmuScripts...", file=sys.stderr)
scripts = _list_emuscripts(api_url)
cores: list[str] = []
seen: set[str] = set()
has_retroarch = False
for script in scripts:
core = _script_to_core(script)
if 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
if has_retroarch:
ra_cores = self._load_retroarch_cores(arch)
for c in ra_cores:
if c not in seen:
seen.add(c)
cores.append(c)
print(f" {label}: {standalone_count} standalone + "
f"{len(cores) - standalone_count} via RetroArch = {len(cores)} total",
file=sys.stderr)
return sorted(cores)
@staticmethod
def _load_retroarch_cores(arch: str) -> list[str]:
"""Load RetroArch target cores for given architecture."""
import os
target_path = os.path.join("platforms", "targets", "retroarch.yml")
if not os.path.exists(target_path):
return []
with open(target_path) as f:
data = yaml.safe_load(f) or {}
# Find a target matching the architecture
for tname, tinfo in data.get("targets", {}).items():
if tinfo.get("architecture") == arch:
return tinfo.get("cores", [])
return []
def fetch_targets(self) -> dict:
print(" fetching SteamOS checkBIOS.sh...", file=sys.stderr)
sh_text = _fetch(STEAMOS_CHECKBIOS_URL)
steamos_cores = _extract_cores(sh_text, _SH_EMULATOR_RE) if sh_text else []
steamos_cores = self._fetch_cores_for_target(STEAMOS_API, "SteamOS")
windows_cores = self._fetch_cores_for_target(WINDOWS_API, "Windows")
print(" fetching Windows checkBIOS.ps1...", file=sys.stderr)
ps1_text = _fetch(WINDOWS_CHECKBIOS_URL)
windows_cores = _extract_cores(ps1_text, _PS1_EMULATOR_RE) if ps1_text else []
targets: dict[str, dict] = {
"steamos": {
targets: dict[str, dict] = {}
if steamos_cores:
targets["steamos"] = {
"architecture": "x86_64",
"cores": steamos_cores,
},
"windows": {
}
if windows_cores:
targets["windows"] = {
"architecture": "x86_64",
"cores": windows_cores,
},
}
return {

View File

@@ -394,8 +394,10 @@ def verify_platform(
validation_index = _build_validation_index(profiles)
# Filter systems by target
plat_cores = resolve_platform_cores(config, profiles) if target_cores else None
verify_systems = filter_systems_by_target(
config.get("systems", {}), profiles, target_cores,
platform_cores=plat_cores,
)
# Per-entry results