From f957b5f893fa09a84e1fe18a809b1e77534e5f3b Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Tue, 20 Oct 2026 17:21:16 +0200 Subject: [PATCH] fix: open no System.dat section libretro lacks libretro titles each section with the system as it spells it, "Commodore - Amiga". An addition for a system it has no section for opened one under our slug: 21 sections came out as commodore-c64, quake, laseractive or xrick, which no libretro DAT carries. An addition now lands only in a section the original holds and is otherwise counted as left out, so the export keeps the original's 66 sections. A platform entry is always written, under its own section. --- scripts/exporter/systemdat_exporter.py | 23 +++++++++++++++-- tests/test_e2e.py | 6 ++++- tests/test_export_counts.py | 4 ++- tests/test_exporters.py | 34 ++++++++++++++++++++++++-- 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/scripts/exporter/systemdat_exporter.py b/scripts/exporter/systemdat_exporter.py index da795380..6e403873 100644 --- a/scripts/exporter/systemdat_exporter.py +++ b/scripts/exporter/systemdat_exporter.py @@ -150,6 +150,7 @@ class Exporter(BaseExporter): quoted = frozenset(_QUOTED_NAME.findall(original)) ours: dict[str, dict[str, str]] = {} + added: dict[str, set[str]] = {} for system, files in self.exportable(systems): rendered: dict[str, str] = {} for fe in files: @@ -170,6 +171,8 @@ class Exporter(BaseExporter): if sha1: parts.append(f"sha1 {sha1}") rendered.setdefault(name, f"\trom ( {' '.join(parts)} )") + if fe.platform is None: + added.setdefault(system.native_id, set()).add(name) if rendered: ours[system.native_id] = rendered @@ -194,13 +197,29 @@ class Exporter(BaseExporter): body.append(line) body.extend(rendered.values()) section(comment, body) + # A section is titled with the system as libretro spells it, and we + # know no title for one it does not have: 20 sections came out under + # our slugs (commodore-c64, quake, laseractive, xrick). + self._untitled = { + id(fe) + for system, files in self.exportable(systems) + if system.native_id in ours + for fe in files + if fe.platform is None + } for comment in sorted(ours): - section(comment, list(ours[comment].values())) + own = [line for name, line in ours[comment].items() + if name not in added.get(comment, set())] + if own: + section(comment, own) lines.append(")") lines.append("") return {self.native_filename(): "\n".join(lines)} + def lands(self, fe: NativeFile) -> bool: + return self.writable(fe) and id(fe) not in getattr(self, "_untitled", set()) + def validate( self, systems: dict[str, NativeSystem], @@ -213,7 +232,7 @@ class Exporter(BaseExporter): issues: list[str] = [] for system in systems.values(): for fe in system.files: - if not any(fe.hash(h) for h in ("crc32", "md5", "sha1")): + if not self.lands(fe) or not any(fe.hash(h) for h in ("crc32", "md5", "sha1")): continue if self._rom_name(fe) not in exported: issues.append(f"absent from the DAT: {system.native_id}/{fe.name}") diff --git a/tests/test_e2e.py b/tests/test_e2e.py index a0a26a47..f0d158bb 100644 --- a/tests/test_e2e.py +++ b/tests/test_e2e.py @@ -4226,7 +4226,11 @@ class TestE2E(unittest.TestCase): from exporter.baseline import build_native_model systems, report = build_native_model(truth, {"systems": {}}) - produced = SystemDatExporter().render(systems, report, {}, {"systems": {}}) + original = ('clrmamepro (\n\tname "System"\n)\n\ngame (\n\tname "System"\n' + '\tcomment "commodore-plus4"\n)\n') + produced = SystemDatExporter().render( + systems, report, {"System.dat": original}, {"systems": {}} + ) self.assertIn("crc 70295038", produced["System.dat"]) # --------------------------------------------------------------- diff --git a/tests/test_export_counts.py b/tests/test_export_counts.py index 8b6f388a..25346058 100644 --- a/tests/test_export_counts.py +++ b/tests/test_export_counts.py @@ -275,7 +275,9 @@ class PureDataExportsKeepWhatWeDoNotModel(unittest.TestCase): for api, digest in (("vulkan", "a"), ("opengl", "b")) ] systems = {"Sony - PlayStation 2": NativeSystem("Sony - PlayStation 2", files=shaders)} - produced = SystemDat().render(systems, Report(), {})["System.dat"] + original = ('clrmamepro (\n\tname "System"\n)\n\ngame (\n\tname "System"\n' + '\tcomment "Sony - PlayStation 2"\n)\n') + produced = SystemDat().render(systems, Report(), {"System.dat": original})["System.dat"] self.assertIn("name pcsx2/resources/shaders/vulkan/convert.glsl md5 " + "a" * 32, produced) self.assertIn("name pcsx2/resources/shaders/opengl/convert.glsl md5 " + "b" * 32, produced) self.assertEqual(SystemDat().validate(systems, {"System.dat": produced}), []) diff --git a/tests/test_exporters.py b/tests/test_exporters.py index 1171a053..ff4bdc11 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -1206,6 +1206,38 @@ class RommKeepsItsFixture(unittest.TestCase): self.assertEqual(len(changed), 2, changed) +class SystemDatNamesNoSectionItself(unittest.TestCase): + """libretro titles each section with the system as it spells it, + "Commodore - Amiga". An addition for a system it has no section for + opened one under our slug: 20 such sections, commodore-c64, quake, + laseractive.""" + + def test_an_addition_without_a_libretro_section_is_left_out(self): + amiga = "Commodore - Amiga" + original = ( + 'clrmamepro (\n\tname "System"\n)\n\ngame (\n\tname "System"\n' + f'\tcomment "{amiga}"\n' + f"\trom ( name kick34005.A500 size 262144 md5 {'2' * 32} )\n" + ")\n" + ) + scraped = {"systems": {"commodore-amiga": {"native_id": amiga, "files": [ + {"name": "kick34005.A500", "destination": "kick34005.A500", + "native_system": amiga, "md5": "2" * 32, "size": 262144}]}}} + truth = {"systems": { + "commodore-amiga": {"files": [{"name": "kick40068.A1200", "md5": "3" * 32}]}, + "laseractive": {"files": [{"name": "pac-n1.bin", "crc32": "a8cb694c"}]}, + }} + systems, report = build_native_model(truth, scraped) + exporter = discover_exporters()["retroarch"]() + produced = exporter.render(systems, report, {"System.dat": original}, None)["System.dat"] + self.assertNotIn('comment "laseractive"', produced) + self.assertNotIn("pac-n1.bin", produced) + self.assertIn("kick40068.A1200", produced) + self.assertEqual(exporter.validate(systems, {"System.dat": produced}), []) + added = systems["laseractive"].files[0] + self.assertFalse(exporter.lands(added)) + + class SystemDatKeepsSpaceIndentedLines(unittest.TestCase): """libretro indents 39 rom lines with four spaces. Read with a tab only, they left the base and came back last in their section, re-indented.""" @@ -1508,8 +1540,6 @@ class BatoceraExport(unittest.TestCase): exporter = discover_exporters()[platform] self.assertFalse(exporter().writable(foreign)) self.assertTrue(exporter().writable(own)) - # A format that finds files by path has no system key to miss. - self.assertTrue(discover_exporters()["retroarch"]().writable(foreign)) produced = discover_exporters()["batocera"]().render( systems, report, {"batocera-systems": self.SKELETON}, None )