From 62a4f19ce39258d7ae778742135f1d54e427b986 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:02:30 +0200 Subject: [PATCH] fix: keep ares labels off other dumps' names ares finds firmware by sha256 (desktop-ui/settings/firmware.cpp:116-136) and names no file. Its FDS entry (RP2C33-01, crc32 1c7ae5d5) was labelled disksys.rom, the name nestopia, fceumm and four more cores read for RP2C33A-01A: ROCKNIX and EmuDeck got ares's revision at disksys.rom. It now carries MAME's member name rp2c33-01.bin. The LaserActive v1.05 entry, the PAC-S10, PAC-N10, PAC-N1, PCE-LP1 and Neo Geo Pocket aliases are names RetroBat's launcher passes (Ares.Generator.cs:435-555): mega-ld.cpp:16-17 holds only the v1.04 US and v1.02 Japan hashes. RetroBat declares each name in its own list. AHashScanLabelNamesNoOtherDump compares every found_by: content label with other profiles' entries of that name through the hash indexes. --- emulators/ares.yml | 18 +------- tests/test_audit_regressions.py | 73 +++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 16 deletions(-) diff --git a/emulators/ares.yml b/emulators/ares.yml index 170c3351..f0d9ac96 100644 --- a/emulators/ares.yml +++ b/emulators/ares.yml @@ -112,12 +112,12 @@ files: source_ref: "desktop-ui/emulator/colecovision.cpp:12" # --- Famicom Disk System --- - - name: disksys.rom + - name: rp2c33-01.bin system: famicom-disk-system required: true sha256: "fdc1a76e654feea993fcb38366e05ee5f4eb641f86fe6bebaeefd412e112dd72" validation: [sha256] - description: "Famicom Disk System BIOS" + description: "Famicom Disk System BIOS, RP2C33-01 revision" source_ref: "desktop-ui/emulator/famicom-disk-system.cpp:19" # --- Game Boy Advance --- @@ -202,7 +202,6 @@ files: # --- Mega LD (LaserActive SEGA PAC) --- - name: mega_ld_bios_U.bin - aliases: ["[BIOS] LaserActive PAC-S10 (US) (v1.04).bin"] system: mega-ld region: [north-america] required: true @@ -220,14 +219,6 @@ files: description: "LaserActive SEGA PAC BIOS (Japan), v1.02" source_ref: "desktop-ui/emulator/mega-ld.cpp:17" - - name: "[BIOS] LaserActive PAC-S1 (Japan) (v1.05).bin" - system: mega-ld - region: [japan] - required: false - description: "LaserActive SEGA PAC BIOS (Japan), v1.05" - note: "Absent from the firmware table, so the scan by hash never assigns it. A path written into the Japanese BIOS slot of settings.bml is read as is, with no hash check. RetroBat writes this name there first and falls back to v1.02." - source_ref: "desktop-ui/emulator/mega-ld.cpp:17, desktop-ui/emulator/mega-ld.cpp:54, desktop-ui/settings/firmware.cpp:109, desktop-ui/emulator/emulator.cpp:188-197" - # --- MSX --- - name: msx-japanese.rom system: msx @@ -270,7 +261,6 @@ files: # --- Neo Geo Pocket --- - name: ngp_bios.rom - aliases: ["[BIOS] SNK Neo Geo Pocket (Japan, Europe) (En,Ja).ngp"] system: neo-geo-pocket required: true sha256: "0293555b21c4fac516d25199df7809b26beeae150e1d4504a050db32264a6ad7" @@ -280,7 +270,6 @@ files: # --- Neo Geo Pocket Color --- - name: ngpc_bios.rom - aliases: ["[BIOS] SNK Neo Geo Pocket (Japan, Europe) (En,Ja).ngp"] system: neo-geo-pocket-color required: true sha256: "8fb845a2f71514cec20728e2f0fecfade69444f8d50898b92c2259f1ba63e10d" @@ -350,7 +339,6 @@ files: # --- PC Engine LD (LaserActive NEC PAC) --- - name: pac-n10.pce - aliases: ["[BIOS] LaserActive PAC-N10 (US) (v1.02).bin"] system: pc-engine-ld required: true sha256: "0e87a3385a27b3a4cac51934819b7eefa5b3d690768d2495633838488cd0e2e4" @@ -359,7 +347,6 @@ files: source_ref: "desktop-ui/emulator/pc-engine-ld.cpp:18" - name: pac-n1.pce - aliases: ["[BIOS] LaserActive PAC-N1 (Japan) (v1.02).bin"] system: pc-engine-ld required: true sha256: "459325690a458baebd77495c91e37c4dddfdd542ba13a821ce954e5bb245627f" @@ -368,7 +355,6 @@ files: source_ref: "desktop-ui/emulator/pc-engine-ld.cpp:19" - name: pce-lp1.pce - aliases: ["[BIOS] LaserActive PCE-LP1 (Japan) (v1.02).bin"] system: pc-engine-ld required: true sha256: "3f43b3b577117d84002e99cb0baeb97b0d65b1d70b4adadc68817185c6a687f0" diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index d12efd68..63279a06 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -5438,6 +5438,79 @@ class AHashScanningCoreContradictsNoPlatform(unittest.TestCase): self.assertEqual(claimed, []) +class AHashScanLabelNamesNoOtherDump(unittest.TestCase): + """A core that finds its firmware by content names no file, so each + entry name is a label. ares labelled its Famicom Disk System revision + (RP2C33-01, crc32 1c7ae5d5) disksys.rom, the name six NES cores read for + the RP2C33A-01A dump: on ROCKNIX the manifest put ares's revision at + disksys.rom for all of them. A label that another profile gives a + different dump is refused, the two dumps compared through the + collection's hash indexes.""" + + # Names the core's own code gives the image, not labels. + CODE_NAMED = { + ("bizhawk", "MSX.rom"): + "FirmwareDatabase.cs:362 names the image MSX.rom", + ("swanstation", "ps1_rom.bin"): + "the whole PS3 image is the file the PS3 itself names ps1_rom.bin, " + "beetle_psx reads its first 512 KB under the same name", + } + + @staticmethod + def _identity(entry: dict, db: dict) -> tuple[set, set]: + raw: set = set() + sha1s: set = set() + indexes = db["indexes"] + for field, index in (("sha1", None), ("md5", "by_md5"), + ("crc32", "by_crc32"), ("sha256", "by_sha256")): + value = entry.get(field) + values = value if isinstance(value, list) else [value] if value else [] + for item in values: + for digest in str(item).lower().split(","): + digest = digest.strip() + if not digest: + continue + raw.add((field, digest)) + sha1 = digest if index is None else indexes[index].get(digest) + if sha1 in db["files"]: + sha1s.add(sha1) + return raw, sha1s + + def test_labels_are_not_other_dumps_names(self): + import json + + from scripts.common import load_emulator_profiles + + if not (ROOT / "database.json").exists(): + self.skipTest("database.json is not built") + db = json.loads((ROOT / "database.json").read_text(encoding="utf-8")) + profiles = load_emulator_profiles(str(ROOT / "emulators")) + by_name: dict[str, list[tuple[str, dict]]] = {} + for emulator, profile in profiles.items(): + for entry in profile.get("files") or []: + for name in [entry.get("name"), *(entry.get("aliases") or [])]: + if name: + by_name.setdefault(str(name).casefold(), []).append((emulator, entry)) + clashes = [] + for emulator, profile in profiles.items(): + if profile.get("found_by") != "content": + continue + for entry in profile.get("files") or []: + raw, sha1s = self._identity(entry, db) + if not sha1s: + continue + for name in [entry.get("name"), *(entry.get("aliases") or [])]: + if (emulator, name) in self.CODE_NAMED: + continue + for other, other_entry in by_name.get(str(name).casefold(), []): + if other == emulator: + continue + other_raw, other_sha1s = self._identity(other_entry, db) + if other_sha1s and not raw & other_raw and not sha1s & other_sha1s: + clashes.append(f"{emulator} {name} is {other}'s name for another dump") + self.assertEqual(sorted(set(clashes)), []) + + class ReportsHoldTheDataTrees(unittest.TestCase): """Run on their own, the README, site and slot reports resolved against data/ without the shared hold, so a concurrent refresh could swap a tree