From 78e6134c754a941ea938e95816c71b4f25dc8861 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Mon, 9 Nov 2026 21:55:42 +0100 Subject: [PATCH] feat: resolve each platform to its own amiberry A platform that builds Amiberry itself runs that revision under the name, on every board it builds: RetroPie 5.7.2 and 6.3.3, Recalbox 8.2.2, Batocera 7.1.1-332, KNULLI 7.1.1. The 8.5.0 profile is the buildbot libretro core. It now names the ROM table's own file names and hashes the keyboard MCU images. --- emulators/amiberry.yml | 32 +++++++++++++- scripts/common.py | 58 ++++++++++++++---------- tests/test_standalone_links.py | 81 +++++++++++++++++++++++----------- 3 files changed, 120 insertions(+), 51 deletions(-) diff --git a/emulators/amiberry.yml b/emulators/amiberry.yml index 0a693b7f7..4ced4a42f 100644 --- a/emulators/amiberry.yml +++ b/emulators/amiberry.yml @@ -1,5 +1,5 @@ emulator: Amiberry -type: standalone + libretro +type: libretro core_classification: official_port source: "https://github.com/BlitterStudio/amiberry" upstream: "https://github.com/tonioni/WinUAE" @@ -1551,10 +1551,12 @@ files: crc32: "f53a0fca" sha1: "efe17ca588c44a7f0f8c62be20f23278cfe06727" validation: [sha1] + aliases: ["blizzard_scsi_kit_iv.rom"] found_by: content note: > Blizzard SCSI Kit IV, ROM id 94. SCSI module ROM of the Blizzard 1230-IV and 1260 board, read when the module is enabled. + The table names blizzard_scsi_kit_iv.rom as its file name. source_ref: "source src/rommgr.cpp:543-544, source src/expansion.cpp:5587-5592, source src/cpuboard.cpp:2882, source src/cpuboard.cpp:2645-2660" - name: "warp-engine.rom" @@ -2229,6 +2231,18 @@ files: so the table SHA-1 is not the hash of the file. source_ref: "source src/rommgr.cpp:860-861, source src/expansion.cpp:6856-6858, source src/cpuboard.cpp:2927-2983, source src/rommgr.cpp:3152-3184, source src/cfgfile.cpp:10132-10142, source src/rommgr.cpp:1797-1810" + - name: "menu500.aca" + path: "roms/menu500.aca" + standalone_path: "menu500.aca" + system: commodore-amiga + required: false + min_size: 524288 + found_by: name + note: > + ACA 500, ROM id 137. The table gives it no hash: the ROM scan names it by + its file name, menu500.aca, and the ACA 500 board opens it by that name. + source_ref: "source src/rommgr.cpp:875-876, source src/rommgr.cpp:1656-1665, source src/rommgr.cpp:3241-3244, source src/cpuboard.cpp:2937-2942" + - name: "blizzardppc_040.rom" path: "roms/blizzardppc_040.rom" standalone_path: "blizzardppc_040.rom" @@ -2671,10 +2685,12 @@ files: crc32: "00447765" sha1: "2644e7cbba5cc54e880b4b4249fd1dd12eb4a3bc" validation: [sha1] + aliases: ["fastlanez3.rom"] found_by: content note: > Fastlane, ROM id 102. Boot ROM of the Fastlane controller. Without it the controller has no boot ROM. + The table names fastlanez3.rom as its file name. source_ref: "source src/rommgr.cpp:545-546, source src/expansion.cpp:5993-5999, source src/ncr9x_scsi.cpp:2003" - name: "oktagon-2008.rom" @@ -2686,10 +2702,12 @@ files: crc32: "bb0d2f6a" sha1: "56c441fa37d193393081b2e8ceae823bc7e97e49" validation: [sha1] + aliases: ["oktagon2008.rom"] found_by: content note: > Oktagon 2008, ROM id 103. Boot ROM of the Oktagon 2008 controller. Without it the controller has no boot ROM. + The table names oktagon2008.rom as its file name. source_ref: "source src/rommgr.cpp:547-548, source src/expansion.cpp:5609-5614, source src/ncr9x_scsi.cpp:2093" - name: "dkb-rapidfire-spitfire-v1.31.rom" @@ -4259,6 +4277,10 @@ files: system: commodore-amiga required: false size: 2048 + crc32: "4a3fc332" + sha1: "83b21d0c8b93fc9b9b3b287fde4ec8f3badac5a2" + validation: [sha1] + found_by: content note: "6500-1/6570-036 keyboard MCU ROM for A500/A600/A1000 models" source_ref: "source src/kbmcu/keyboard_mcu_6500_1.cpp:385-408, source src/rommgr.cpp:137-138" @@ -4267,6 +4289,10 @@ files: system: commodore-amiga required: false size: 8192 + crc32: "2a77eec4" + sha1: "301ec6a69404457d912c89e3fc54095eda9f0e93" + validation: [sha1] + found_by: content note: "68HC05 keyboard MCU ROM for A1200" source_ref: "source src/kbmcu/keyboard_mcu_6805.cpp:544-567, source src/rommgr.cpp:139-140" @@ -4275,6 +4301,10 @@ files: system: commodore-amiga required: false size: 2048 + crc32: "c23d280e" + sha1: "b5c4b8c38b9e4a864abc871d048bd1c3a02c8432" + validation: [sha1] + found_by: content note: "D8039HLC keyboard MCU ROM for A2000" source_ref: "source src/kbmcu/keyboard_mcu_d8039hlc.cpp:250-273, source src/rommgr.cpp:141-142" diff --git a/scripts/common.py b/scripts/common.py index 437160737..4913d8ce3 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -3180,18 +3180,30 @@ def resolve_platform_cores( """ cores_config = config.get("cores") platform_key = str(config.get("_key") or "") - # The names a build the platform compiles itself answers to: there the - # name means that build, and a standalone + libretro profile of another - # revision under the same name is not what the platform runs (ROCKNIX's - # amiberry is 5.6.5). A libretro-only profile of the name stays: RetroPie - # builds minivmac37 standalone beside the minivmac core. - own_names = { - n - for name, p in profiles.items() + # The builds the platform compiles itself, by the names they answer to. + # Such a name means that build there, and another revision of the same + # project is not what the platform runs: ROCKNIX and RetroPie build only + # standalone Amiberry, Recalbox its standalone and libretro Amiberry 8.2.2. + # Only a platform listing the name both ways (dual_mode_cores) beside a + # standalone-only build of its own runs the buildbot core too (RetroBat). + # A core of another project under the name stays: RetroPie builds + # minivmac37 standalone beside the minivmac core. + own_builds = { + name: p for name, p in profiles.items() if platform_key and platform_key in (p.get("built_by") or []) - for n in (name, *(str(c) for c in p.get("cores") or [])) } + def replaced_by_own(name: str, p: dict, libretro_only: bool = False) -> bool: + if platform_key in (p.get("built_by") or []): + return False + names = {name, *(str(c) for c in p.get("cores") or [])} + return any( + (not libretro_only or "libretro" in str(q.get("type", ""))) + and q.get("source") == p.get("source") + and names & {key, *(str(c) for c in q.get("cores") or [])} + for key, q in own_builds.items() + ) + def every_libretro() -> set[str]: # A build only some platforms compile (built_by) is absent from the # buildbot RetroArch installs from, so it is reached by name, or as one @@ -3205,11 +3217,7 @@ def resolve_platform_cores( not p.get("built_by") or (platform_key and platform_key in p["built_by"]) ) - and ( - platform_key in (p.get("built_by") or []) - or "standalone" not in p.get("type", "") - or not own_names & {name, *(str(c) for c in p.get("cores") or [])} - ) + and not replaced_by_own(name, p) } if cores_config == "all_libretro": @@ -3224,21 +3232,22 @@ def resolve_platform_cores( index = upstream_profile_index(profiles) platform = str(config.get("_key") or "") - def designated(name: str) -> str | None: + def designated(name: str) -> set[str]: # A build the platform compiles itself (built_by) is what its # name means there: Recalbox's uae4all is Chips-fr/uae4all-rpi, - # RetroPie's the standalone uae4all2 under the same name. - own = sorted( + # RetroPie's the standalone uae4all2 under the same name. A + # platform building one name in several revisions, one per board, + # runs all of them: RetroPie's amiberry is 5.7.2 and 6.3.3 on x86. + own = { p for p in index.get(name, ()) if platform and platform in (profiles[p].get("built_by") or []) - ) - return own[0] if own else preferred_profile(index, name) + } + if own: + return own + preferred = preferred_profile(index, name) + return {preferred} if preferred is not None else set() - result = { - profile - for c in core_set - if (profile := designated(c)) is not None - } + result = {profile for c in core_set for profile in designated(c)} # A name the platform launches both ways designates its libretro # build too. Batocera, Recalbox and RetroBat run pcsx2 standalone and # as LRPS2, two profiles: the key match kept the standalone one and @@ -3249,6 +3258,7 @@ def resolve_platform_cores( for profile in index.get(str(c), set()) if "libretro" in str(profiles[profile].get("type", "")) and not profiles[profile].get("built_by") + and not replaced_by_own(profile, profiles[profile], libretro_only=True) } # Support "all_libretro" as a list element: combines all libretro # profiles with explicitly listed standalone cores (e.g. RetroDECK diff --git a/tests/test_standalone_links.py b/tests/test_standalone_links.py index 8ae47f00e..b67ff4a90 100644 --- a/tests/test_standalone_links.py +++ b/tests/test_standalone_links.py @@ -621,44 +621,59 @@ class XroarOnEachPlatform(unittest.TestCase): class AmiberryOnEachPlatform(unittest.TestCase): - """The standalone Amiberry scans rom_path for its ROMs. Batocera, KNULLI - and RetroPie set rom_path to bios/amiga.""" + """The standalone Amiberry scans rom_path for its ROMs. Batocera and KNULLI + set it to bios/amiga, Recalbox to bios/amiga/bios.""" - BOUND = ("batocera", "knulli", "retropie") + BOUND = { + "batocera": ("amiberry-7.1.1-332", "amiga"), + "knulli": ("amiberry-7.1.1", "amiga"), + "recalbox": ("amiberry-8.2.2", "amiga/bios"), + } @classmethod def setUpClass(cls) -> None: - cls.profile = load_emulator_profiles(str(REPO_ROOT / "emulators"))["amiberry"] + cls.profiles = load_emulator_profiles(str(REPO_ROOT / "emulators")) - def test_rom_path_is_bios_amiga(self) -> None: - for platform in self.BOUND: + def test_rom_path_is_the_platform_folder(self) -> None: + for platform, (name, folder) in self.BOUND.items(): with self.subTest(platform=platform): cfg = load_platform_config(platform, str(REPO_ROOT / "platforms")) self.assertEqual( - standalone_bindings_for("amiberry", self.profile, standalone_set(cfg)), - {"": ("", "amiga")}, + standalone_bindings_for(name, self.profiles[name], standalone_set(cfg)), + {"": ("", folder)}, ) - def test_every_rom_path_entry_lands_in_bios_amiga(self) -> None: - for platform in self.BOUND: + def test_every_rom_path_entry_lands_in_that_folder(self) -> None: + for platform, (name, folder) in self.BOUND.items(): + profile = self.profiles[name] cfg = load_platform_config(platform, str(REPO_ROOT / "platforms")) - (view, _standalone), = platform_layouts( - "amiberry", self.profile, standalone_set(cfg) - ) - for own, entry in zip(self.profile["files"], view["files"]): - layout = own.get("standalone_path") - if not layout: + views = platform_layouts(name, profile, standalone_set(cfg)) + ((view, _standalone),) = [(v, s) for v, s in views if s] + for own, entry in zip(profile["files"], view["files"]): + if own.get("mode") == "libretro": continue + layout = own.get("standalone_path") or own.get("path") or own["name"] with self.subTest(platform=platform, name=entry["name"]): - self.assertEqual(entry["standalone_path"], f"amiga/{layout}") - self.assertNotIn("/", layout.removeprefix("mt32-roms/")) + self.assertNotIn("unplaced", entry) + self.assertEqual(entry["standalone_path"], f"{folder}/{layout}") class AmiberryBuildEachPlatformShips(unittest.TestCase): - """ROCKNIX, AmberELEC and EmuELEC compile older Amiberry revisions: on each - the name amiberry is that build, never the 8.5.0 profile.""" + """Each platform resolves the name amiberry to the build its own package + compiles. RetroBat keeps the 8.5.0 libretro core beside its standalone.""" - OWN = {"rocknix": "amiberry-5.6.5", "amberelec": "amiberry-7.1.1", "emuelec": "amiberry-8.0.0"} + OWN = { + "rocknix": {"amiberry-5.6.5"}, + "amberelec": {"amiberry-7.1.1"}, + "emuelec": {"amiberry-8.0.0"}, + "batocera": {"amiberry-7.1.1-332"}, + "knulli": {"amiberry-7.1.1"}, + "recalbox": {"amiberry-8.2.2"}, + "retrobat": {"amiberry", "amiberry-8.3.0"}, + "retropie": {"amiberry-5.7.2", "amiberry-6.3.3"}, + "emudeck": {"amiberry"}, + "retrodeck": {"amiberry"}, + } @classmethod def setUpClass(cls) -> None: @@ -673,12 +688,26 @@ class AmiberryBuildEachPlatformShips(unittest.TestCase): def test_each_platform_resolves_its_own_build(self) -> None: for platform, own in self.OWN.items(): with self.subTest(platform=platform): - self.assertEqual(self.resolved(platform), {own}) + self.assertEqual(self.resolved(platform), own) - def test_the_others_keep_the_main_profile(self) -> None: - for platform in ("batocera", "knulli", "retrobat", "retropie", "recalbox"): - with self.subTest(platform=platform): - self.assertEqual(self.resolved(platform), {"amiberry"}) + def test_retropie_reads_kickstarts_below_its_start_folder(self) -> None: + # amiberry 5.7 and 6.3 read /kickstarts, which RetroPie + # links to bios/amiga. Its whdboot and roms folders stay below it. + cfg = load_platform_config("retropie", str(REPO_ROOT / "platforms")) + for name in ("amiberry-5.7.2", "amiberry-6.3.3"): + profile = self.profiles[name] + self.assertEqual( + standalone_bindings_for(name, profile, standalone_set(cfg)), + {"kickstarts": ("", "amiga")}, + ) + ((view, _standalone),) = platform_layouts(name, profile, standalone_set(cfg)) + for entry in view["files"]: + with self.subTest(name=name, file=entry["name"]): + if entry["path"].startswith("kickstarts/"): + self.assertEqual(entry["standalone_path"], + "amiga/" + entry["path"][len("kickstarts/"):]) + else: + self.assertEqual(entry.get("unplaced"), "outside_bios_folder") def test_rocknix_reads_kickstarts_through_the_copied_link(self) -> None: # amiberry 5.6.5 reads /kickstarts. ROCKNIX runs it in