mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 13:33:24 -05:00
feat: lay out standalone files by build mode
This commit is contained in:
1 parent
6148ab2e55
commit
887fc34bf6
8 files changed
+107
-37
No files matched your search
+23
-9
@@ -1,7 +1,7 @@
|
|||||||
platform: Recalbox
|
platform: Recalbox
|
||||||
version: '10.1'
|
version: 10.1.1
|
||||||
homepage: "https://www.recalbox.com"
|
homepage: "https://www.recalbox.com"
|
||||||
source: "https://gitlab.com/recalbox/recalbox/-/raw/10.1/board/recalbox/fsoverlay/recalbox/share_init/system/.emulationstation/es_bios.xml"
|
source: "https://gitlab.com/recalbox/recalbox/-/raw/10.1.1/board/recalbox/fsoverlay/recalbox/share_init/system/.emulationstation/es_bios.xml"
|
||||||
base_destination: bios
|
base_destination: bios
|
||||||
hash_type: md5
|
hash_type: md5
|
||||||
verification_mode: md5
|
verification_mode: md5
|
||||||
@@ -14,6 +14,7 @@ cores:
|
|||||||
- libretro-hatari
|
- libretro-hatari
|
||||||
- libretro/ mame2003-plus
|
- libretro/ mame2003-plus
|
||||||
- libretro/a5200
|
- libretro/a5200
|
||||||
|
- libretro/amiberry
|
||||||
- libretro/atari800
|
- libretro/atari800
|
||||||
- libretro/bk
|
- libretro/bk
|
||||||
- libretro/bluemsx
|
- libretro/bluemsx
|
||||||
@@ -98,6 +99,19 @@ cores:
|
|||||||
- ti99sim
|
- ti99sim
|
||||||
- xemu
|
- xemu
|
||||||
- xroar
|
- xroar
|
||||||
|
standalone_cores:
|
||||||
|
- amiberry
|
||||||
|
- dolphin
|
||||||
|
- duckstation
|
||||||
|
- gsplus
|
||||||
|
- hatari
|
||||||
|
- oricutron
|
||||||
|
- pcsx2
|
||||||
|
- reicast
|
||||||
|
- scummvm
|
||||||
|
- ti99sim
|
||||||
|
- xemu
|
||||||
|
- xroar
|
||||||
systems:
|
systems:
|
||||||
commodore-amiga:
|
commodore-amiga:
|
||||||
files:
|
files:
|
||||||
@@ -113,7 +127,7 @@ systems:
|
|||||||
required: false
|
required: false
|
||||||
md5: 82a21c1890cae844b3df741f2762d48d
|
md5: 82a21c1890cae844b3df741f2762d48d
|
||||||
native_system: amiga600
|
native_system: amiga600
|
||||||
core: amiberry,libretro/uae,libretro/uae4arm,libretro/uae4all
|
core: amiberry,libretro/amiberry,libretro/uae,libretro/uae4arm,libretro/uae4all
|
||||||
mandatory_declared: false
|
mandatory_declared: false
|
||||||
native_name: "Amiga 600 (ECS/OCS)"
|
native_name: "Amiga 600 (ECS/OCS)"
|
||||||
- name: kick37175.A500
|
- name: kick37175.A500
|
||||||
@@ -128,7 +142,7 @@ systems:
|
|||||||
required: false
|
required: false
|
||||||
md5: e40a5dfb3d017ba8779faba30cbd1c8e
|
md5: e40a5dfb3d017ba8779faba30cbd1c8e
|
||||||
native_system: amiga600
|
native_system: amiga600
|
||||||
core: amiberry,libretro/uae,libretro/uae4arm
|
core: amiberry,libretro/amiberry,libretro/uae,libretro/uae4arm
|
||||||
mandatory_declared: false
|
mandatory_declared: false
|
||||||
native_name: "Amiga 600 (ECS/OCS)"
|
native_name: "Amiga 600 (ECS/OCS)"
|
||||||
- name: kick02019.AROS.ext
|
- name: kick02019.AROS.ext
|
||||||
@@ -161,14 +175,14 @@ systems:
|
|||||||
required: true
|
required: true
|
||||||
md5: 646773759326fbac3b2311fd8c8793ee
|
md5: 646773759326fbac3b2311fd8c8793ee
|
||||||
native_system: amiga1200
|
native_system: amiga1200
|
||||||
core: amiberry,libretro/uae,libretro/uae4arm
|
core: amiberry,libretro/amiberry,libretro/uae,libretro/uae4arm
|
||||||
native_name: "Amiga 1200 (AGA)"
|
native_name: "Amiga 1200 (AGA)"
|
||||||
- name: kick40068.A4000
|
- name: kick40068.A4000
|
||||||
destination: amiga/bios/kick40068.A4000
|
destination: amiga/bios/kick40068.A4000
|
||||||
required: true
|
required: true
|
||||||
md5: 9bdedde6a4f33555b4a270c8ca53297d
|
md5: 9bdedde6a4f33555b4a270c8ca53297d
|
||||||
native_system: amiga1200
|
native_system: amiga1200
|
||||||
core: amiberry,libretro/uae
|
core: amiberry,libretro/amiberry,libretro/uae
|
||||||
native_name: "Amiga 1200 (AGA)"
|
native_name: "Amiga 1200 (AGA)"
|
||||||
note: This bios is required only for A4000 emulation
|
note: This bios is required only for A4000 emulation
|
||||||
- name: kick34005.CDTV
|
- name: kick34005.CDTV
|
||||||
@@ -176,7 +190,7 @@ systems:
|
|||||||
required: true
|
required: true
|
||||||
md5: 89da1838a24460e4b93f4f0c5d92d48d,d98112f18792ee3714df16a6eb421b89,d1145ab3a0f89340f94c9e734762c198
|
md5: 89da1838a24460e4b93f4f0c5d92d48d,d98112f18792ee3714df16a6eb421b89,d1145ab3a0f89340f94c9e734762c198
|
||||||
native_system: amigacdtv
|
native_system: amigacdtv
|
||||||
core: amiberry,libretro/uae,libretro/uae4arm
|
core: amiberry,libretro/amiberry,libretro/uae,libretro/uae4arm
|
||||||
hash_match_mandatory: true
|
hash_match_mandatory: true
|
||||||
mandatory_declared: true
|
mandatory_declared: true
|
||||||
native_name: Amiga CDTV
|
native_name: Amiga CDTV
|
||||||
@@ -186,7 +200,7 @@ systems:
|
|||||||
required: true
|
required: true
|
||||||
md5: f2f241bf094168cfb9e7805dc2856433,5f8924d013dd57a89cf349f4cdedc6b1
|
md5: f2f241bf094168cfb9e7805dc2856433,5f8924d013dd57a89cf349f4cdedc6b1
|
||||||
native_system: amigacd32
|
native_system: amigacd32
|
||||||
core: amiberry,libretro/uae,libretro/uae4arm
|
core: amiberry,libretro/amiberry,libretro/uae,libretro/uae4arm
|
||||||
hash_match_mandatory: false
|
hash_match_mandatory: false
|
||||||
mandatory_declared: true
|
mandatory_declared: true
|
||||||
native_name: Amiga CD32
|
native_name: Amiga CD32
|
||||||
@@ -197,7 +211,7 @@ systems:
|
|||||||
required: false
|
required: false
|
||||||
md5: bb72565701b1b6faece07d68ea5da639
|
md5: bb72565701b1b6faece07d68ea5da639
|
||||||
native_system: amigacd32
|
native_system: amigacd32
|
||||||
core: amiberry,libretro/uae,libretro/uae4arm
|
core: amiberry,libretro/amiberry,libretro/uae,libretro/uae4arm
|
||||||
mandatory_declared: false
|
mandatory_declared: false
|
||||||
native_name: Amiga CD32
|
native_name: Amiga CD32
|
||||||
note: Extended ROM rev 40.060
|
note: Extended ROM rev 40.060
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from common import expand_platform_declared_names
|
|||||||
from common import load_emulator_profiles
|
from common import load_emulator_profiles
|
||||||
from common import resolution_is_hash_exact
|
from common import resolution_is_hash_exact
|
||||||
from common import resolve_local_file
|
from common import resolve_local_file
|
||||||
|
from common import runs_standalone
|
||||||
from common import sanitize_pack_path
|
from common import sanitize_pack_path
|
||||||
def _emulator_systems_index(emu_profiles: dict | None) -> dict[str, list[str]]:
|
def _emulator_systems_index(emu_profiles: dict | None) -> dict[str, list[str]]:
|
||||||
"""Map both the profile key and its display name to the profile's systems.
|
"""Map both the profile key and its display name to the profile's systems.
|
||||||
@@ -469,9 +470,7 @@ def _collect_emulator_extras(
|
|||||||
continue
|
continue
|
||||||
if emu_name not in relevant:
|
if emu_name not in relevant:
|
||||||
continue
|
continue
|
||||||
is_standalone = emu_name in standalone_set or bool(
|
is_standalone = runs_standalone(emu_name, profile, standalone_set)
|
||||||
standalone_set & {str(c) for c in profile.get("cores", [])}
|
|
||||||
)
|
|
||||||
for f in profile.get("files", []):
|
for f in profile.get("files", []):
|
||||||
fname = f.get("name", "")
|
fname = f.get("name", "")
|
||||||
if not fname:
|
if not fname:
|
||||||
|
|||||||
@@ -99,26 +99,34 @@ SYSTEM_SLUG_MAP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def split_cores(names) -> tuple[list[str], list[str]]:
|
||||||
|
"""(every core name, the standalone ones) from es_bios.xml ``core`` values."""
|
||||||
|
cores = {name for name in names if name}
|
||||||
|
standalone = {name for name in cores if not name.startswith("libretro")}
|
||||||
|
return sorted(cores), sorted(standalone)
|
||||||
|
|
||||||
|
|
||||||
class Scraper(BaseScraper):
|
class Scraper(BaseScraper):
|
||||||
"""Scraper for Recalbox es_bios.xml."""
|
"""Scraper for Recalbox es_bios.xml."""
|
||||||
|
|
||||||
def __init__(self, url: str = SOURCE_URL):
|
def __init__(self, url: str = SOURCE_URL):
|
||||||
super().__init__(url=url)
|
super().__init__(url=url)
|
||||||
|
|
||||||
def _fetch_cores(self) -> list[str]:
|
def _fetch_cores(self) -> tuple[list[str], list[str]]:
|
||||||
"""Extract unique core names from es_bios.xml bios elements."""
|
"""Core names from es_bios.xml, and those Recalbox runs standalone.
|
||||||
|
|
||||||
|
The ``core`` attribute writes a libretro core as ``libretro/<name>``
|
||||||
|
and a standalone emulator by its bare name (``dolphin``, ``pcsx2``,
|
||||||
|
``xemu``), so the prefix is the build mode. One entry spells
|
||||||
|
``libretro-hatari``; a name that starts with ``libretro`` is a core.
|
||||||
|
"""
|
||||||
raw = self._fetch_raw()
|
raw = self._fetch_raw()
|
||||||
root = parse_untrusted_xml(raw, "es_bios.xml")
|
root = parse_untrusted_xml(raw, "es_bios.xml")
|
||||||
cores: set[str] = set()
|
return split_cores(
|
||||||
for bios_elem in root.findall(".//system/bios"):
|
part.strip()
|
||||||
raw_core = bios_elem.get("core", "").strip()
|
for bios_elem in root.findall(".//system/bios")
|
||||||
if not raw_core:
|
for part in bios_elem.get("core", "").split(",")
|
||||||
continue
|
)
|
||||||
for part in raw_core.split(","):
|
|
||||||
name = part.strip()
|
|
||||||
if name:
|
|
||||||
cores.add(name)
|
|
||||||
return sorted(cores)
|
|
||||||
|
|
||||||
def fetch_requirements(self) -> list[BiosRequirement]:
|
def fetch_requirements(self) -> list[BiosRequirement]:
|
||||||
"""Parse es_bios.xml and return BIOS requirements."""
|
"""Parse es_bios.xml and return BIOS requirements."""
|
||||||
@@ -213,6 +221,7 @@ class Scraper(BaseScraper):
|
|||||||
if not version:
|
if not version:
|
||||||
version = "10.0"
|
version = "10.0"
|
||||||
|
|
||||||
|
cores, standalone = self._fetch_cores()
|
||||||
return {
|
return {
|
||||||
"platform": "Recalbox",
|
"platform": "Recalbox",
|
||||||
"version": version,
|
"version": version,
|
||||||
@@ -221,7 +230,8 @@ class Scraper(BaseScraper):
|
|||||||
"base_destination": "bios",
|
"base_destination": "bios",
|
||||||
"hash_type": "md5",
|
"hash_type": "md5",
|
||||||
"verification_mode": "md5",
|
"verification_mode": "md5",
|
||||||
"cores": self._fetch_cores(),
|
"cores": cores,
|
||||||
|
"standalone_cores": standalone,
|
||||||
"systems": systems,
|
"systems": systems,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -24,6 +24,7 @@ from common import (
|
|||||||
load_data_dir_registry,
|
load_data_dir_registry,
|
||||||
resolution_is_hash_exact,
|
resolution_is_hash_exact,
|
||||||
resolve_local_file,
|
resolve_local_file,
|
||||||
|
runs_standalone,
|
||||||
)
|
)
|
||||||
|
|
||||||
# A profile entry can prove a slot without declaring a hash: Dolphin names no
|
# A profile entry can prove a slot without declaring a hash: Dolphin names no
|
||||||
@@ -148,9 +149,7 @@ def profile_claims(
|
|||||||
for emu_name, profile in sorted(profiles.items()):
|
for emu_name, profile in sorted(profiles.items()):
|
||||||
if profile.get("type") in ("launcher", "alias"):
|
if profile.get("type") in ("launcher", "alias"):
|
||||||
continue
|
continue
|
||||||
is_standalone = emu_name in standalone_cores or bool(
|
is_standalone = runs_standalone(emu_name, profile, standalone_cores)
|
||||||
standalone_cores & {str(c) for c in profile.get("cores", [])}
|
|
||||||
)
|
|
||||||
for entry in profile.get("files") or []:
|
for entry in profile.get("files") or []:
|
||||||
if not isinstance(entry, dict):
|
if not isinstance(entry, dict):
|
||||||
continue
|
continue
|
||||||
|
|||||||
+3
-7
@@ -52,6 +52,7 @@ from common import (
|
|||||||
resolve_local_file,
|
resolve_local_file,
|
||||||
ProfileSelectionError,
|
ProfileSelectionError,
|
||||||
resolve_platform_cores,
|
resolve_platform_cores,
|
||||||
|
runs_standalone,
|
||||||
sanitize_pack_path,
|
sanitize_pack_path,
|
||||||
select_emulator_profiles,
|
select_emulator_profiles,
|
||||||
)
|
)
|
||||||
@@ -455,10 +456,7 @@ def find_undeclared_files(
|
|||||||
if profile.get("bios_mode") == "agnostic":
|
if profile.get("bios_mode") == "agnostic":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check if this profile is standalone: match profile name or any cores: alias
|
is_standalone = runs_standalone(emu_name, profile, standalone_set)
|
||||||
is_standalone = emu_name in standalone_set or bool(
|
|
||||||
standalone_set & {str(c) for c in profile.get("cores", [])}
|
|
||||||
)
|
|
||||||
|
|
||||||
for f in profile.get("files", []):
|
for f in profile.get("files", []):
|
||||||
fname = f.get("name", "")
|
fname = f.get("name", "")
|
||||||
@@ -673,9 +671,7 @@ def find_exclusion_notes(
|
|||||||
# Count standalone-only files -but only report as excluded if the
|
# Count standalone-only files -but only report as excluded if the
|
||||||
# platform does NOT use this emulator in standalone mode
|
# platform does NOT use this emulator in standalone mode
|
||||||
standalone_set = set(str(c) for c in config.get("standalone_cores", []))
|
standalone_set = set(str(c) for c in config.get("standalone_cores", []))
|
||||||
is_standalone = emu_name in standalone_set or bool(
|
is_standalone = runs_standalone(emu_name, profile, standalone_set)
|
||||||
standalone_set & {str(c) for c in profile.get("cores", [])}
|
|
||||||
)
|
|
||||||
if not is_standalone:
|
if not is_standalone:
|
||||||
standalone_files = [
|
standalone_files = [
|
||||||
f for f in profile.get("files", []) if f.get("mode") == "standalone"
|
f for f in profile.get("files", []) if f.get("mode") == "standalone"
|
||||||
|
|||||||
@@ -6184,5 +6184,26 @@ struct BurnDriver BurnDrvneogeo = {
|
|||||||
cross_reference.get_mame_clone_map = original
|
cross_reference.get_mame_clone_map = original
|
||||||
|
|
||||||
|
|
||||||
|
class StandaloneModeRequiresAStandaloneBuild(unittest.TestCase):
|
||||||
|
"""A platform names its standalone emulators by its own names, which can
|
||||||
|
equal the key of a libretro-only profile (Recalbox and Batocera both
|
||||||
|
call their standalone ScummVM ``scummvm``). Laid out standalone, such a
|
||||||
|
profile loses every ``path:`` and drops its files at the root."""
|
||||||
|
|
||||||
|
def test_libretro_only_profile_keeps_its_layout(self):
|
||||||
|
from common import runs_standalone
|
||||||
|
|
||||||
|
libretro_only = {"type": "libretro", "cores": ["scummvm"]}
|
||||||
|
dual = {"type": "standalone + libretro", "cores": ["dolphin"]}
|
||||||
|
standalone = {"type": "standalone", "cores": ["xemu"]}
|
||||||
|
named = {"scummvm", "dolphin", "xemu", "hatari"}
|
||||||
|
self.assertFalse(runs_standalone("scummvm", libretro_only, named))
|
||||||
|
self.assertTrue(runs_standalone("dolphin", dual, named))
|
||||||
|
self.assertTrue(runs_standalone("xemu", standalone, named))
|
||||||
|
self.assertTrue(runs_standalone("other-key", {"type": "standalone", "cores": ["hatari"]}, named))
|
||||||
|
self.assertFalse(runs_standalone("dolphin", dual, set()))
|
||||||
|
self.assertFalse(runs_standalone("dolphin", dual, {"pcsx2"}))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
@@ -188,5 +188,24 @@ class RetroPiePackageList(unittest.TestCase):
|
|||||||
self.assertEqual(scraper.fetch_requirements(), [])
|
self.assertEqual(scraper.fetch_requirements(), [])
|
||||||
|
|
||||||
|
|
||||||
|
class RecalboxBuildMode(unittest.TestCase):
|
||||||
|
"""es_bios.xml names a libretro core as libretro/<name> and a standalone
|
||||||
|
emulator by its bare name: the prefix is the build mode, and a platform
|
||||||
|
that runs an emulator standalone must say so or the pack lays its files
|
||||||
|
out for the libretro build."""
|
||||||
|
|
||||||
|
def test_bare_names_are_the_standalone_emulators(self):
|
||||||
|
from scraper.recalbox_scraper import split_cores
|
||||||
|
|
||||||
|
cores, standalone = split_cores(
|
||||||
|
["libretro/uae", "amiberry", "", "dolphin", "libretro/dolphin", "libretro-hatari", "hatari"]
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
cores,
|
||||||
|
["amiberry", "dolphin", "hatari", "libretro-hatari", "libretro/dolphin", "libretro/uae"],
|
||||||
|
)
|
||||||
|
self.assertEqual(standalone, ["amiberry", "dolphin", "hatari"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
+14
-2
@@ -393,7 +393,8 @@ class TestBuildModeDecidesWhoClaims(unittest.TestCase):
|
|||||||
entry["mode"] = mode
|
entry["mode"] = mode
|
||||||
if standalone_path:
|
if standalone_path:
|
||||||
entry["standalone_path"] = standalone_path
|
entry["standalone_path"] = standalone_path
|
||||||
return {"ares": {"cores": ["ares"], "files": [entry]}}
|
return {"ares": {"type": "standalone + libretro", "cores": ["ares"],
|
||||||
|
"files": [entry]}}
|
||||||
|
|
||||||
def test_a_standalone_only_file_does_not_claim_a_core_slot(self):
|
def test_a_standalone_only_file_does_not_claim_a_core_slot(self):
|
||||||
conflicts = slots.find_conflicts(
|
conflicts = slots.find_conflicts(
|
||||||
@@ -428,7 +429,8 @@ class TestBuildModeDecidesWhoClaims(unittest.TestCase):
|
|||||||
self.assertEqual(claims[0].destination, "elsewhere/IPL.bin")
|
self.assertEqual(claims[0].destination, "elsewhere/IPL.bin")
|
||||||
|
|
||||||
def test_the_mode_follows_a_core_alias_not_only_the_profile_name(self):
|
def test_the_mode_follows_a_core_alias_not_only_the_profile_name(self):
|
||||||
profile = {"ares_core": {"cores": ["ares"],
|
profile = {"ares_core": {"type": "standalone + libretro",
|
||||||
|
"cores": ["ares"],
|
||||||
"files": [{"name": "IPL.bin",
|
"files": [{"name": "IPL.bin",
|
||||||
"path": "GC/JAP/IPL.bin",
|
"path": "GC/JAP/IPL.bin",
|
||||||
"mode": "standalone"}]}}
|
"mode": "standalone"}]}}
|
||||||
@@ -438,6 +440,16 @@ class TestBuildModeDecidesWhoClaims(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(len(conflicts), 1)
|
self.assertEqual(len(conflicts), 1)
|
||||||
|
|
||||||
|
def test_a_profile_without_a_standalone_build_keeps_its_core_layout(self):
|
||||||
|
profile = {"ares": {"type": "libretro", "cores": ["ares"],
|
||||||
|
"files": [{"name": "IPL.bin",
|
||||||
|
"path": "GC/JAP/IPL.bin",
|
||||||
|
"standalone_path": "elsewhere/IPL.bin"}]}}
|
||||||
|
claims = slots.profile_claims(
|
||||||
|
profile, REGIONS_DB, standalone_cores={"ares"}
|
||||||
|
)
|
||||||
|
self.assertEqual(claims[0].destination, "GC/JAP/IPL.bin")
|
||||||
|
|
||||||
|
|
||||||
class TestProvenEvidence(unittest.TestCase):
|
class TestProvenEvidence(unittest.TestCase):
|
||||||
"""What counts as proof that a claim is about content, not about a name."""
|
"""What counts as proof that a claim is about content, not about a name."""
|
||||||
|
|||||||
Reference in new issue
Block a user