From f4010bfd185bff0aca5168cec35eefe35ba2cb76 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:02:16 +0200 Subject: [PATCH 1/8] docs: state what each .info declares, not a grade Ten profile notes graded a .info (correct, accurate, still, missing, omits). Each now says what the file declares and what the code reads. test_no_grade_on_another_projects_file reads every profile line that names a .info, core-info or System.dat. --- emulators/bsnes_cplusplus98.yml | 2 +- emulators/fbalpha2012_neogeo.yml | 4 ++-- emulators/openlara.yml | 2 +- emulators/prosystem.yml | 3 ++- emulators/retrodream.yml | 4 ++-- emulators/rvvm.yml | 2 +- emulators/skyemu.yml | 2 +- emulators/squirreljme.yml | 2 +- emulators/uw8.yml | 2 +- emulators/vice_x64dtv.yml | 2 +- tests/test_prose_style.py | 21 +++++++++++++++++++++ wiki/testing-guide.md | 6 ++++-- 12 files changed, 38 insertions(+), 14 deletions(-) diff --git a/emulators/bsnes_cplusplus98.yml b/emulators/bsnes_cplusplus98.yml index c62ab2fd..02b427ca 100644 --- a/emulators/bsnes_cplusplus98.yml +++ b/emulators/bsnes_cplusplus98.yml @@ -29,7 +29,7 @@ notes: | Core continues without DSP firmware but coprocessor games won't function. exclusion_note: > - firmware_count=0 in .info is correct. DSP firmware (NEC uPD7725/uPD96050, + The .info declares firmware_count=0. DSP firmware (NEC uPD7725/uPD96050, Hitachi CX4) is loaded from the ROM directory via game XML markup, not from system_dir. RetroArch cannot pre-populate firmware in the ROM directory. Upstream byuu.org is defunct, and this libretro port is the preserved v085 code. diff --git a/emulators/fbalpha2012_neogeo.yml b/emulators/fbalpha2012_neogeo.yml index 1a8ab1ef..e760d3c0 100644 --- a/emulators/fbalpha2012_neogeo.yml +++ b/emulators/fbalpha2012_neogeo.yml @@ -52,8 +52,8 @@ notes: | neogeo.zip is a parent ROM set, loaded from g_rom_dir alongside game ZIPs. g_system_dir is set (src/burner/libretro/libretro.cpp:3016) but never used for file loading. - The .info note "BIOS files must be inside the ROM directory" is accurate here: - neogeo.zip must be in the same directory as game ROM ZIPs. + neogeo.zip is read from the same directory as the game ROM ZIPs, as the .info + note "BIOS files must be inside the ROM directory" states. files: [] diff --git a/emulators/openlara.yml b/emulators/openlara.yml index 7946ae9f..85d1b26e 100644 --- a/emulators/openlara.yml +++ b/emulators/openlara.yml @@ -26,6 +26,6 @@ notes: > subdirectories as .ogg/.mp3/.wav files or cdaudio.wad (src/gameflow.h:1272-1356). System directory used only for shader cache at {system_dir}/openlara/cache/ (src/platform/libretro/main.cpp:177-197). No BIOS, firmware, or engine data files required in - system directory. .info omits sat from supported_extensions (code declares it). + system directory. The code declares the sat extension, which the .info supported_extensions does not list. files: [] diff --git a/emulators/prosystem.yml b/emulators/prosystem.yml index 5f4ee4c6..20095165 100644 --- a/emulators/prosystem.yml +++ b/emulators/prosystem.yml @@ -29,7 +29,8 @@ notes: | directory, it is loaded automatically. No hash or size validation is performed on the BIOS file. - The .info declares firmware_count=1 (U only), missing the PAL BIOS. + The .info declares one firmware, the U BIOS. The code also opens the E BIOS + for PAL cartridges (core/libretro.c:581-582). files: - name: "7800 BIOS (U).rom" diff --git a/emulators/retrodream.yml b/emulators/retrodream.yml index 3ac394cf..3154bfe2 100644 --- a/emulators/retrodream.yml +++ b/emulators/retrodream.yml @@ -20,8 +20,8 @@ notes: | The fork sets appdir to the RetroArch system directory root (retro_host.c:216-217), unlike the upstream which appends /dc - (upstream retro_host.c:196-198). The .info still declares dc/ prefix paths - matching the upstream, not this fork. + (upstream retro_host.c:196-198). The .info declares dc/ prefix paths, the + layout the upstream reads. boot.bin is validated against four known MD5 hashes (boot.c:25-30). Validation failure logs a warning but the core continues via HLE. diff --git a/emulators/rvvm.yml b/emulators/rvvm.yml index 43fa07c6..465326f3 100644 --- a/emulators/rvvm.yml +++ b/emulators/rvvm.yml @@ -76,5 +76,5 @@ notes: OpenSBI: https://github.com/riscv-software-src/opensbi/releases. Pre-built images for RVVM: https://github.com/LekKit/RVVM/wiki/Running info_firmware_count: > - .info declares 0 firmware. Correct: the core loads firmware via + .info declares 0 firmware. The core loads firmware via user-specified paths in the .rvvm config, not from system_dir. diff --git a/emulators/skyemu.yml b/emulators/skyemu.yml index 4b4bd378..a6e0e972 100644 --- a/emulators/skyemu.yml +++ b/emulators/skyemu.yml @@ -23,7 +23,7 @@ notes: | stub firmware (NDS), and HLE boot skip (GB/GBC). BIOS loading controlled by core options (default: on). Files loaded from system_dir/SkyEmu/ subdirectory. Standalone validates exact BIOS file size, while libretro only rejects files exceeding buffer. - .info missing gb_bios.bin (gb.h:1603). + The .info does not list gb_bios.bin, which the code reads (gb.h:1603). files: - name: cgb_boot.bin diff --git a/emulators/squirreljme.yml b/emulators/squirreljme.yml index 6afddb4f..3cb49e82 100644 --- a/emulators/squirreljme.yml +++ b/emulators/squirreljme.yml @@ -17,7 +17,7 @@ notes: | All entries are optional: without a class library, the core runs a built-in modeless stars demo but cannot execute J2ME software. The .info declares firmware_count=8 but the code defines 10 search names - (squirreljme-{version}-test.jar and squirreljme-test.jar are missing from .info). + (the two not in the .info are squirreljme-{version}-test.jar and squirreljme-test.jar). files: - name: "squirreljme-0.3.0-fast.jar" diff --git a/emulators/uw8.yml b/emulators/uw8.yml index e7ad6e8c..fec80a14 100644 --- a/emulators/uw8.yml +++ b/emulators/uw8.yml @@ -14,4 +14,4 @@ notes: > WebAssembly-based fantasy console. 320x240 screen, 32-color palette, 256KB memory. The WASM runtime (platform + loader modules) is compiled to C via wasm2c and embedded in the binary. No external files required. - firmware_count=0 in .info is correct. + The .info declares firmware_count=0. diff --git a/emulators/vice_x64dtv.yml b/emulators/vice_x64dtv.yml index 9c8453d9..6df7689e 100644 --- a/emulators/vice_x64dtv.yml +++ b/emulators/vice_x64dtv.yml @@ -17,7 +17,7 @@ notes: > DTV has its own color system (viciidtv-color.c), and external palette loading is disabled. JiffyDOS is not supported (excluded from build conditionals). The .info declares 4 JiffyDOS firmware (firmware_count=4) but the code - never loads them for this core. The .info also omits dtvrom.bin. + never loads them for this core. The .info does not list dtvrom.bin. files: - name: "dtvrom.bin" diff --git a/tests/test_prose_style.py b/tests/test_prose_style.py index 6893f5fb..1451ba62 100644 --- a/tests/test_prose_style.py +++ b/tests/test_prose_style.py @@ -225,6 +225,18 @@ class ProfilesStateFactsAboutOtherProjects(unittest.TestCase): re.IGNORECASE, ) + # A line that names another project's file grades it when it calls the + # file correct or accurate, says it still declares something, or that an + # entry is missing from it or omitted. prosystem, retrodream, + # fbalpha2012_neogeo and seven more profiles wrote it that way. + OTHER_FILE = re.compile(r"(\.info\b|core-info|System\.dat)", re.IGNORECASE) + GRADES = re.compile( + r"\b(?:is (?:correct|accurate|inaccurate|outdated|stale)|accurate here|" + r"correct:|still (?:declares|lists)|omits|missing (?:the|from))|" + r"\.info missing", + re.IGNORECASE, + ) + def test_profile_prose_and_comments(self): offenders = [ f"{path.relative_to(ROOT)}: {line.strip()[:100]}" @@ -234,6 +246,15 @@ class ProfilesStateFactsAboutOtherProjects(unittest.TestCase): ] self.assertEqual(offenders, [], "\n".join(offenders[:40])) + def test_no_grade_on_another_projects_file(self): + offenders = [ + f"{path.relative_to(ROOT)}: {line.strip()[:100]}" + for path in sorted((ROOT / "emulators").glob("*.yml")) + for line in path.read_text(encoding="utf-8").splitlines() + if self.OTHER_FILE.search(line) and self.GRADES.search(line) + ] + self.assertEqual(offenders, [], "\n".join(offenders[:40])) + class NoClauseSemicolons(unittest.TestCase): def _check(self, files: list[Path], extract) -> None: diff --git a/wiki/testing-guide.md b/wiki/testing-guide.md index da47320f..8376d164 100644 --- a/wiki/testing-guide.md +++ b/wiki/testing-guide.md @@ -266,8 +266,10 @@ to the way the project writes: number with its criterion says more. - Headings in sentence case, never repeating the site name. - In emulator profiles, no verdict on another project's file: "phantom", - "misleading", "bogus", "typo", "has errors", "incorrectly says". The note - states what that file declares and what the code reads. + "misleading", "bogus", "typo", "has errors", "incorrectly says", and on a + line naming a .info, core-info or System.dat no "is correct", "is + accurate", "still declares", "omits" or "missing". The note states what + that file declares and what the code reads. Formatting stays: bold labels, lists, tables and admonitions make a page easier to use. When markup shows raw on the site, the rendering is fixed and 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 2/8] 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 From e255348f527679b991dffbd929f5d71fd70a59ab 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 3/8] fix: read mame hiscore.dat where the cores open it mame2000 opens hiscore.dat, history.dat and mameinfo.dat from IMAMEBASEPATH, the content directory (src/libretro/fileio.c:1002-1014, libretro.c:1044-1052). mame2014 and mame2015 open hiscore.dat through emu_file with no search path, the working directory (src/emu/hiscore.c:330-337, fileio.c:141-143). With no load_from the entries claimed system/ and the packs shipped files neither core opens there. ANoteOutsideTheSystemDirCarriesLoadFrom holds libretro notes and load_from together. --- emulators/mame2000.yml | 15 +++++++++------ emulators/mame2014.yml | 1 + emulators/mame2015.yml | 1 + tests/test_audit_regressions.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/emulators/mame2000.yml b/emulators/mame2000.yml index b840fe1e..a29ae3bc 100644 --- a/emulators/mame2000.yml +++ b/emulators/mame2000.yml @@ -34,15 +34,16 @@ notes: | files: - name: hiscore.dat + load_from: content_dir required: false category: game_data system: arcade sha1: "21ddda0c61bd4f1cc04fb9d09e16d403d620f9e8" md5: "9b262149ec70cbda741a8ba55784f8e1" size: 36182 - source_ref: "src/hiscore.c:18,132-146,252-254, src/cpuintrf.c:664-666" + source_ref: "src/hiscore.c:18,132-146,252-254, src/cpuintrf.c:664-666, src/libretro/fileio.c:1002-1014, src/libretro/libretro.c:1044-1052" note: >- - high score definitions, loaded from ROM directory. metadata/hiscore.dat + high score definitions, opened from IMAMEBASEPATH, the directory of the loaded content. metadata/hiscore.dat of the core repository. is_mem_range reads only "cpu:addr:len:start:end" lines under a "gamename:" header. @@ -55,18 +56,20 @@ files: note: "cheat definitions, loaded from system_dir/mame2000/cheat/" - name: history.dat + load_from: content_dir required: false category: game_data system: arcade - source_ref: "src/datafile.c:52,281,541" - note: "game history database, loaded from ROM directory" + source_ref: "src/datafile.c:52,281,541, src/libretro/fileio.c:1002-1014" + note: "game history database, opened from IMAMEBASEPATH, the directory of the loaded content" - name: mameinfo.dat + load_from: content_dir required: false category: game_data system: arcade - source_ref: "src/datafile.c:53,281,568" - note: "game technical info database, loaded from ROM directory" + source_ref: "src/datafile.c:53,281,568, src/libretro/fileio.c:1002-1014" + note: "game technical info database, opened from IMAMEBASEPATH, the directory of the loaded content" - name: neogeo.zip required: true diff --git a/emulators/mame2014.yml b/emulators/mame2014.yml index 835cee5b..4fd821d0 100644 --- a/emulators/mame2014.yml +++ b/emulators/mame2014.yml @@ -102,6 +102,7 @@ notes: | files: # Data files - name: hiscore.dat + load_from: working_directory required: false category: game_data system: arcade diff --git a/emulators/mame2015.yml b/emulators/mame2015.yml index 816735dc..2c45a9cf 100644 --- a/emulators/mame2015.yml +++ b/emulators/mame2015.yml @@ -91,6 +91,7 @@ notes: | files: # Data files - name: hiscore.dat + load_from: working_directory installed_on: [recalbox] required: false category: game_data diff --git a/tests/test_audit_regressions.py b/tests/test_audit_regressions.py index 63279a06..4ec515b7 100644 --- a/tests/test_audit_regressions.py +++ b/tests/test_audit_regressions.py @@ -5511,6 +5511,36 @@ class AHashScanLabelNamesNoOtherDump(unittest.TestCase): self.assertEqual(sorted(set(clashes)), []) +class ANoteOutsideTheSystemDirCarriesLoadFrom(unittest.TestCase): + """mame2000 opens hiscore.dat, history.dat and mameinfo.dat from the + directory of the loaded content (src/libretro/fileio.c:1002-1014), and + mame2014 and mame2015 open hiscore.dat from the working directory + (src/emu/hiscore.c:330-337). Their notes said so, but no load_from did: + the entries claimed system/hiscore.dat and the RetroArch pack shipped a + file neither core opens there. A libretro entry whose note places the + read outside the system directory declares where.""" + + ELSEWHERE = re.compile( + r"\b(?:opened|read|loaded)\b[^.]*?\bfrom (?:the )?(?:working directory|" + r"ROM directory|content directory|directory of the loaded content)" + r"|IMAMEBASEPATH", + re.IGNORECASE, + ) + + def test_prose_and_load_from_agree(self): + offenders = [] + for path in sorted((ROOT / "emulators").glob("*.yml")): + profile = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + if profile.get("type") != "libretro": + continue + for entry in profile.get("files") or []: + if entry.get("path") or entry.get("load_from") or entry.get("mode") == "standalone": + continue + if self.ELSEWHERE.search(str(entry.get("note") or "")): + offenders.append(f"{path.name}: {entry.get('name')}") + self.assertEqual(offenders, []) + + 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 From 4d76f0104e116ac05f1745b9b9da71cb0f382e0b Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:02:39 +0200 Subject: [PATCH 4/8] fix: drop the ZXS16.ROM aliases speccy never opens The loader at speccy 0x3d970 gzopens only the name it is given and clears the model bit when that fails. ZXS16.ROM is passed with mask 1 (0x3dcb2), ZXS48.ROM separately with mask 2 (0x3dcc8), and the binary holds no 48.rom string (Speccy59-Ubuntu-x86-bin). The aliases let a root 48.rom settle the 16K ROM, which the gap report now lists. --- emulators/speccy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/emulators/speccy.yml b/emulators/speccy.yml index 93cb291d..72b6e64c 100644 --- a/emulators/speccy.yml +++ b/emulators/speccy.yml @@ -144,7 +144,6 @@ files: source_ref: "libmain.so LoadBIOS 0x5b2e4-0x5b2f4, Speccy59-Ubuntu-x86-bin speccy 0x3dcbe-0x3dcd1" - name: ZXS16.ROM - aliases: ["ZXS48.ROM", "48.rom"] system: sinclair-zx-spectrum required: true max_size: 16384 From 2a2980d3f2b00d36a019927e2923b9fe57b3517c Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:02:45 +0200 Subject: [PATCH 5/8] fix: drop RetroBat Mega CD names from kega-fusion Fusion.exe fixes only us_scd1_9210.bin, jp_mcd1_9111.bin and eu_mcd1_9210.bin (0x44b662-0x44b74d). bios_CD_U/J/E.bin are the names RetroBat's generator writes into Fusion.ini (KegaFusion.Generator.cs:105-116), and platforms/retrobat.yml declares all three. --- emulators/kega-fusion.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/emulators/kega-fusion.yml b/emulators/kega-fusion.yml index b24f4358..458bd4d8 100644 --- a/emulators/kega-fusion.yml +++ b/emulators/kega-fusion.yml @@ -51,7 +51,6 @@ notes: | files: - name: us_scd1_9210.bin - aliases: [bios_CD_U.bin] system: sega-megacd region: [north-america, latin-america] required: true @@ -60,7 +59,6 @@ files: source_ref: "Fusion.exe 0x44b662-0x44b670 (default path), 0x478877-0x4788d6 (open, message, fread 0x20000)" - name: jp_mcd1_9111.bin - aliases: [bios_CD_J.bin] system: sega-megacd region: [japan] required: true @@ -69,7 +67,6 @@ files: source_ref: "Fusion.exe 0x44b676 (default path), 0x4788f9-0x478959 (open, message, fread 0x20000)" - name: eu_mcd1_9210.bin - aliases: [bios_CD_E.bin] system: sega-megacd region: [europe, oceania] required: true From 9377069269cafadbec920423efea9dc52094edbf Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:02:45 +0200 Subject: [PATCH 6/8] fix: drop 64DD_IPL.bin from mupen64plus_next load_dd_rom builds only /Mupen64plus/IPL.n64 (mupen64plus-core/src/main/main.c:958-961 at 12edd2c7). 64DD_IPL.bin is Batocera's standalone mupen64plus name (mupenConfig.py:188-192), held in batocera.yml files and launchers. standalone_path described a build this libretro profile does not have. --- emulators/mupen64plus_next.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/emulators/mupen64plus_next.yml b/emulators/mupen64plus_next.yml index 8eabe068..d9495e3f 100644 --- a/emulators/mupen64plus_next.yml +++ b/emulators/mupen64plus_next.yml @@ -13,8 +13,6 @@ cores: [mupen64plus_next, mupen64plus_next_develop, mupen64plus_next_gles3, mupe files: - name: "IPL.n64" path: "Mupen64plus/IPL.n64" - standalone_path: "64DD_IPL.bin" - aliases: ["64DD_IPL.bin"] size: 4194304 required: false description: "64DD IPL ROM" From b4a88798dec6263ed19aef4a8d2dcecbb675bce8 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:02:45 +0200 Subject: [PATCH 7/8] fix: drop the mac.rom alias from basiliskii Basilisk II opens rom_path or ROM_FILE_NAME "ROM" (BasiliskII/src/Unix/main_unix.cpp:109, 685) and never mac.rom. RetroPie's basilisk.sh:56 passes $romdir/macintosh/mac.rom with --rom, recorded under launchers: in retropie.yml. --- emulators/basiliskii.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/emulators/basiliskii.yml b/emulators/basiliskii.yml index f23856f4..23cf2b01 100644 --- a/emulators/basiliskii.yml +++ b/emulators/basiliskii.yml @@ -65,7 +65,6 @@ notes: | files: - name: ROM - aliases: [mac.rom] required: true min_size: 524288 max_size: 1048576 From 3ccee1b216290ea8305008a7ff5fbca714cf1818 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:14:15 +0200 Subject: [PATCH 8/8] fix: drop the gba_bios.bin alias of nanoboyadvance NanoBoyAdvance opens general.bios_path, default bios.bin (src/platform/core/src/config.cpp:36 at 3bb6f478). gba_bios.bin is the path ROCKNIX's config.toml sets, recorded under launchers: in rocknix.yml. Without the alias the entry met only PCem's and QEMU's bios.bin, 64 and 128 KB, which BIOSLoader refuses (bios.cpp:17-48). verify counted it held by name, so the launcher layer left it alone and the ROCKNIX pack gained PCem's image. A hashless entry whose every same-named copy fails its size is now not held, and the gba_bios.bin ROCKNIX declares settles it. --- emulators/nanoboyadvance.yml | 6 +----- scripts/verify.py | 11 +++++++++++ tests/test_launcher_layer.py | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/emulators/nanoboyadvance.yml b/emulators/nanoboyadvance.yml index c6edfe09..92546cc9 100644 --- a/emulators/nanoboyadvance.yml +++ b/emulators/nanoboyadvance.yml @@ -30,18 +30,14 @@ notes: | ref: src/platform/core/src/loader/bios.cpp:17-48, src/platform/sdl/main.cpp:177-192, src/nba/src/core.cpp:42-44 - ROCKNIX patches config.toml and keymap.toml to /storage/.config/nanoboyadvance/ - and its config.toml sets bios_path = /storage/roms/bios/gba_bios.bin. - files: - name: "bios.bin" path: "bios.bin" - aliases: ["gba_bios.bin"] system: nintendo-gba required: true size: 16384 validation: [size] config_key: "general.bios_path" description: "Game Boy Advance BIOS" - note: "Any file name through general.bios_path or --bios, or bios.bin beside the executable when neither is set. ROCKNIX points the key at gba_bios.bin in its BIOS directory." + note: "Any file name through general.bios_path or --bios, or bios.bin beside the executable when neither is set." source_ref: "src/platform/core/src/loader/bios.cpp:17-48, src/platform/core/src/config.cpp:36, src/platform/sdl/main.cpp:177-192" diff --git a/scripts/verify.py b/scripts/verify.py index 09947723..d9c5db0d 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -666,6 +666,17 @@ def find_undeclared_files( path_base, by_name, by_path_suffix, data_names, by_name_lower, ) + if in_repo and not any( + f.get(h) for h in ("sha1", "md5", "crc32", "sha256") + ): + # A name alone answers nothing when every copy of that + # name fails the size the code checks: the resolver + # rejects them all. NanoBoyAdvance's 16 KB bios.bin met + # only PCem's and QEMU's PC BIOS images of that name. + # The fallback below then decides as for any mismatch. + _lp, _st = resolve_local_file(f, db, dest_hint=dest) + if _st == "hash_mismatch": + in_repo = False if not in_repo: # Hash fallback: the repo may hold the content under a # different filename (exos21.rom vs exos21.bin). diff --git a/tests/test_launcher_layer.py b/tests/test_launcher_layer.py index dd029a6d..4231a0c8 100644 --- a/tests/test_launcher_layer.py +++ b/tests/test_launcher_layer.py @@ -231,6 +231,25 @@ class LauncherLayerTest(unittest.TestCase): self.assertIn("v103.bin", report) self.assertIn("boot.bin", report) + def test_a_name_whose_copies_fail_the_size_is_not_held(self) -> None: + """NanoBoyAdvance reads a 16 KB bios.bin, and the collection's + bios.bin files are PCem's and QEMU's PC BIOS images. Counted held by + name, the entry escaped the launcher layer, which settles only what + the collection lacks, and the ROCKNIX pack carried PCem's 64 KB image + beside the gba_bios.bin its launcher passes.""" + profile = { + "emulator": "FixEmu", "type": "standalone", "systems": ["test-sys"], + "files": [{"name": "core.bin", "required": True, + "size": len(CONTENT["plain.bin"]), "validation": ["size"]}], + } + with open(os.path.join(self.emulators_dir, "fixemu.yml"), "w") as fh: + yaml.dump(profile, fh) + self.config["launchers"][2]["names"] = ["plain.bin"] + _emulator_profiles_cache.clear() + self.profiles = load_emulator_profiles(self.emulators_dir) + self.assertNotIn("core.bin", {u["name"] for u in self._report()}) + self.assertNotIn("core.bin", self._pack_names()) + def test_no_place_is_filled_twice(self) -> None: added = [u["path"] for u in self._report() if u.get("launcher")] self.assertEqual(sorted(added), ["e.bin", "sub/fix.bin"])