diff --git a/platforms/retrobat.yml b/platforms/retrobat.yml index eed5e187e..7cbb89ea0 100644 --- a/platforms/retrobat.yml +++ b/platforms/retrobat.yml @@ -2576,6 +2576,7 @@ systems: md5: 21038400dc633070a78ad53090c53017 native_system: ps2 native_path: bios/pcsx2/bios/ps2-0230a-20080220.bin + group: ps2 native_name: PS2 - name: ps2-0230e-20080220.bin destination: pcsx2/bios/ps2-0230e-20080220.bin @@ -2583,6 +2584,7 @@ systems: md5: dc69f0643a3030aaa4797501b483d6c4 native_system: ps2 native_path: bios/pcsx2/bios/ps2-0230e-20080220.bin + group: ps2 native_name: PS2 - name: ps2-0230j-20080220.bin destination: pcsx2/bios/ps2-0230j-20080220.bin @@ -2590,6 +2592,7 @@ systems: md5: 80ac46fa7e77b8ab4366e86948e54f83 native_system: ps2 native_path: bios/pcsx2/bios/ps2-0230j-20080220.bin + group: ps2 native_name: PS2 - name: ps2-0250e-20100415.bin destination: pcsx2/bios/ps2-0250e-20100415.bin @@ -2597,6 +2600,7 @@ systems: md5: 93ea3bcee4252627919175ff1b16a1d9 native_system: ps2 native_path: bios/pcsx2/bios/ps2-0250e-20100415.bin + group: ps2 native_name: PS2 - name: SCPH30004R.bin destination: pcsx2/bios/SCPH30004R.bin @@ -2604,6 +2608,7 @@ systems: md5: 28922c703cc7d2cf856f177f2985b3a9 native_system: ps2 native_path: bios/pcsx2/bios/SCPH30004R.bin + group: ps2 native_name: PS2 - name: scph39001.bin destination: pcsx2/bios/scph39001.bin @@ -2611,30 +2616,35 @@ systems: md5: d5ce2c7d119f563ce04bc04dbc3a323e native_system: ps2 native_path: bios/pcsx2/bios/scph39001.bin + group: ps2 native_name: PS2 - name: ps3_ps2_emu_bios.bin destination: pcsx2/bios/ps3_ps2_emu_bios.bin required: true native_system: ps2 native_path: bios/pcsx2/bios/ps3_ps2_emu_bios.bin + group: ps2 native_name: PS2 - name: SCPH-39004_BIOS_V7_EUR_160.BIN destination: pcsx2/bios/SCPH-39004_BIOS_V7_EUR_160.BIN required: true native_system: ps2 native_path: bios/pcsx2/bios/SCPH-39004_BIOS_V7_EUR_160.BIN + group: ps2 native_name: PS2 - name: SCPH-39001_BIOS_V7_USA_160.BIN destination: pcsx2/bios/SCPH-39001_BIOS_V7_USA_160.BIN required: true native_system: ps2 native_path: bios/pcsx2/bios/SCPH-39001_BIOS_V7_USA_160.BIN + group: ps2 native_name: PS2 - name: SCPH-70000_BIOS_V12_JAP_200.BIN destination: pcsx2/bios/SCPH-70000_BIOS_V12_JAP_200.BIN required: true native_system: ps2 native_path: bios/pcsx2/bios/SCPH-70000_BIOS_V12_JAP_200.BIN + group: ps2 native_name: PS2 name: PS2 sony-playstation: diff --git a/schemas/platform.schema.json b/schemas/platform.schema.json index 73d7206d6..28786b8ca 100644 --- a/schemas/platform.schema.json +++ b/schemas/platform.schema.json @@ -518,6 +518,10 @@ "type": "string", "description": "Display name the platform gives its own system" }, + "group": { + "type": "string", + "description": "RetroBat: files sharing a group are alternatives. One held is enough and the others are reported OPTIONAL" + }, "md5_list": { "type": "boolean", "description": "Batocera: the platform writes md5 as a list of strings, any of which matches. md5 holds the items comma-joined, in order" diff --git a/scripts/packverify.py b/scripts/packverify.py index 60486be1a..99efcfcd2 100644 --- a/scripts/packverify.py +++ b/scripts/packverify.py @@ -774,6 +774,14 @@ def verify_pack_against_platform( baseline_groups.setdefault(expected, []).append(fe) baseline_checked = len(baseline_groups) + # RetroBat's group: one packed file of it is enough, as verify counts it. + packed_groups = { + (system_of[id(fe)], fe["group"]) + for expected, declarations in baseline_groups.items() + if expected in zip_set or expected.lower() in zip_lower + for fe in declarations + if fe.get("group") + } exclusion_index = build_zip_contents_index(db) if db is not None else {} categories = EntryCategories(config, emu_profiles, data_registry) for expected, declarations in baseline_groups.items(): @@ -795,6 +803,12 @@ def verify_pack_against_platform( ): baseline_excluded += 1 continue + elif all( + fe.get("group") and (system_of[id(fe)], fe["group"]) in packed_groups + for fe in declarations + ): + baseline_absent += 1 + continue elif db is not None and all( categories.category(fe, fe.get("destination", fe.get("name", "")), system_of[id(fe)]) != BIOS diff --git a/scripts/scraper/retrobat_scraper.py b/scripts/scraper/retrobat_scraper.py index 720ec6189..7ae5a605a 100644 --- a/scripts/scraper/retrobat_scraper.py +++ b/scripts/scraper/retrobat_scraper.py @@ -217,6 +217,9 @@ class Scraper(BaseScraper): sys_name = sys_data.get("name", "") if isinstance(sys_data, dict) else "" if sys_name: native["native_name"] = sys_name + # One held file of a group is enough (Program.cs): verify reads it. + if bios.get("group"): + native["group"] = str(bios["group"]) requirements.append( BiosRequirement( @@ -224,7 +227,7 @@ class Scraper(BaseScraper): system=SYSTEM_SLUG_MAP.get(sys_key, sys_key), md5=md5 or None, destination=file_path, - required=True, + required=not bios.get("optional", False), native_id=sys_key, native_path=declared_path, native=native, diff --git a/scripts/verify.py b/scripts/verify.py index dcd9eb37a..93d905229 100644 --- a/scripts/verify.py +++ b/scripts/verify.py @@ -1424,6 +1424,25 @@ def verify_platform( ): file_severity[dest] = sev + # RetroBat's group: one file of it held is enough, and the others are + # reported OPTIONAL (batocera-systems/Program.cs). None held, each stays + # required. + members: dict[tuple[str, str], list[str]] = {} + for sys_id, system in config.get("systems", {}).items(): + for file_entry in system.get("files", []): + if file_entry.get("group"): + dest = _dest_key(file_entry, sys_id) + if dest in file_status: + members.setdefault((sys_id, file_entry["group"]), []).append(dest) + for dests in members.values(): + if any(file_status[d] == Status.OK for d in dests): + for dest in dests: + if file_status[dest] == Status.MISSING: + file_required[dest] = False + file_severity[dest] = compute_severity(Status.MISSING, False, mode) + for result in results_at.get(dest, []): + result["required"] = False + shipped, occupied = _builder_placement( packed_order, bool(config.get("case_insensitive_fs", False)) ) diff --git a/tests/test_e2e.py b/tests/test_e2e.py index d876b56d4..b63076f1b 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -915,6 +915,40 @@ class TestE2E(unittest.TestCase): c = result["severity_counts"] self.assertGreater(c[Severity.WARNING], 0) + def test_24b_a_held_group_member_makes_the_others_optional(self): + """RetroBat's group: one file held is enough (batocera-systems/Program.cs).""" + from common import _platform_config_cache + + with open(os.path.join(self.platforms_dir, "test_md5.yml")) as fh: + config = yaml.safe_load(fh) + held_md5 = next( + f["md5"] for f in config["systems"]["sys-renamed"]["files"] + ) + config["systems"] = { + "sys-group": {"files": [ + {"name": "correct_hash.bin", "destination": "g/held.bin", + "md5": held_md5, "required": True, "group": "one"}, + {"name": "absent_alt.bin", "destination": "g/absent_alt.bin", + "md5": "0" * 32, "required": True, "group": "one"}, + {"name": "absent_a.bin", "destination": "g/absent_a.bin", + "md5": "1" * 32, "required": True, "group": "none"}, + {"name": "absent_b.bin", "destination": "g/absent_b.bin", + "md5": "2" * 32, "required": True, "group": "none"}, + ]}, + } + with open(os.path.join(self.platforms_dir, "grouped_md5.yml"), "w") as fh: + yaml.dump(config, fh) + _platform_config_cache.clear() + result = verify_platform( + load_platform_config("grouped_md5", self.platforms_dir), self.db, self.emulators_dir + ) + by_name = {d["name"]: d for d in result["details"]} + self.assertEqual(by_name["correct_hash.bin"]["status"], Status.OK) + self.assertIs(by_name["absent_alt.bin"]["required"], False) + self.assertIs(by_name["absent_a.bin"]["required"], True) + self.assertIs(by_name["absent_b.bin"]["required"], True) + self.assertEqual(result["severity_counts"][Severity.CRITICAL], 2) + def test_25_verify_sha1_platform(self): config = load_platform_config("test_sha1", self.platforms_dir) result = verify_platform(config, self.db, self.emulators_dir)