From b550b9780c8c0424655fda3eaedd93b61b476f80 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 6 Nov 2026 08:12:25 +0100 Subject: [PATCH] fix: judge content-found cores by any dump A core with found_by: content reads no file by the destination's name. Any dump its profile accepts for the system settles the slot: Batocera, KNULLI and ROCKNIX ship scph1001.bin v2.0, which DuckStation and rustation accept under scph1001_v20.bin. --- scripts/slots.py | 32 +++++++++++++++++++++++++++++++- tests/test_slots.py | 19 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/scripts/slots.py b/scripts/slots.py index 8e7f826cd..f3908a631 100644 --- a/scripts/slots.py +++ b/scripts/slots.py @@ -43,7 +43,12 @@ from common import ( variant_base_name, ) from packresolve import preferred_entries -from validation import outside_gap_scope, read_from_save_dir, with_archive_members +from validation import ( + opens_by_name, + outside_gap_scope, + read_from_save_dir, + with_archive_members, +) if TYPE_CHECKING: from packextras import ExtraPlacement @@ -85,6 +90,9 @@ class Claim: entry: dict = field(default_factory=dict) local_path: str | None = None status: str = "" + # A core that identifies its firmware by content reads no file by this + # name: any dump it accepts for the system answers for it. + accepts: tuple[dict, ...] = () @property def is_proven(self) -> bool: @@ -210,6 +218,12 @@ def profile_claims( zip_contents, data_dir_registry, ) ) + labels = [ + f for f in profile.get("files") or [] + if isinstance(f, dict) + and not opens_by_name(f, profile, is_standalone) + and _lists_hashes(f) + ] for entry in profile.get("files") or []: if not isinstance(entry, dict): continue @@ -260,6 +274,13 @@ def profile_claims( ) if status == "name_exact" and _size_the_code_reads(entry, local): status = SIZE_EXACT + accepts: tuple[dict, ...] = () + if not opens_by_name(entry, profile, is_standalone): + systems = set(entry_system_ids(entry, profile)) + accepts = tuple( + f for f in labels + if not systems or systems & set(entry_system_ids(f, profile)) + ) claims.append( Claim( origin="profile", @@ -269,6 +290,7 @@ def profile_claims( entry=entry, local_path=local, status=status, + accepts=accepts, ) ) return claims @@ -746,6 +768,10 @@ def _satisfies(profile: Claim, platform: Claim, db: dict) -> bool: They agree when they resolve to the same bytes, when the platform's own hash list accepts the profile's file, or when the profile's accepts the platform's. A contradiction needs both lists to reject the other side. + A core found by content (found_by: content) reads no file by the + destination's name, so any dump it accepts for the system settles the + slot: DuckStation takes Batocera's scph1001.bin v2.0, which its profile + labels scph1001_v20.bin. """ if profile.local_path == platform.local_path: return True @@ -764,6 +790,10 @@ def _satisfies(profile: Claim, platform: Claim, db: dict) -> bool: and _accepted_by_all(profile.local_path, [platform.entry], db) ): return True + if platform.local_path and any( + _accepted_by_all(platform.local_path, [label], db) for label in profile.accepts + ): + return True return bool( platform.local_path and _lists_hashes(profile.entry) diff --git a/tests/test_slots.py b/tests/test_slots.py index 44afbe8e0..0acf3c653 100644 --- a/tests/test_slots.py +++ b/tests/test_slots.py @@ -111,6 +111,25 @@ class TestConflicts(unittest.TestCase): conflicts = slots.find_conflicts(config, self._profile("GC/JAP/IPL.bin"), REGIONS_DB) self.assertEqual([c.destination for c in conflicts], ["bios/GC/JAP/IPL.bin"]) + def test_a_core_found_by_content_takes_any_dump_it_accepts(self): + """DuckStation identifies its BIOS by content, so the name of its + scph1001.bin entry is a label. Batocera ships the v2.0 dump there, + which the profile accepts under scph1001_v20.bin: no conflict.""" + files = [ + {"name": "IPL.bin", "path": "GC/JAP/IPL.bin", "md5": "n" * 32}, + {"name": "IPL_usa.bin", "md5": "m" * 32}, + ] + by_name = slots.find_conflicts( + self._config("m" * 32), {"dolphin": {"files": files}}, REGIONS_DB + ) + self.assertEqual(len(by_name), 1) + by_content = slots.find_conflicts( + self._config("m" * 32), + {"dolphin": {"found_by": "content", "files": files}}, + REGIONS_DB, + ) + self.assertEqual(by_content, []) + def test_wrong_region_under_a_region_slot_is_reported(self): conflicts = slots.find_conflicts( self._config("m" * 32), self._profile("GC/JAP/IPL.bin"), REGIONS_DB