diff --git a/scripts/exporter/recalbox_exporter.py b/scripts/exporter/recalbox_exporter.py
index 0647bcb8..490ae90b 100644
--- a/scripts/exporter/recalbox_exporter.py
+++ b/scripts/exporter/recalbox_exporter.py
@@ -222,6 +222,19 @@ class Exporter(BaseExporter):
return False
return super().states(fe, field_name)
+ def lands(self, fe: NativeFile) -> bool:
+ """An element of theirs the render cannot rewrite is still in the
+ file, written back as Recalbox wrote it."""
+ return self.writable(fe) or id(fe) in getattr(self, "_held_whole", {})
+
+ def withheld(self) -> list[str]:
+ """Each element kept as written because the truth names no md5."""
+ return [
+ f"{label} keeps its md5: the truth accepts another dump and "
+ "names no md5 for it, which es_bios.xsd requires"
+ for label in sorted(getattr(self, "_held_whole", {}).values())
+ ]
+
@classmethod
def writable(cls, fe: NativeFile, require: str = "") -> bool:
"""es_bios.xsd makes md5 and core required: without them, no element.
@@ -272,6 +285,21 @@ class Exporter(BaseExporter):
for s in root.iter("system")
]
+ # Elements of theirs written back as they stand, by (platform, path).
+ held = {
+ (platform, " ".join(child.get("path", "").split()))
+ for platform, _, children in theirs
+ for child in children
+ if isinstance(child, dict)
+ }
+ self._held_whole = {
+ id(fe): f"{system.native_id}/{self._path(fe)}"
+ for system in systems.values()
+ for fe in system.files
+ if fe.platform is not None
+ and not self.writable(fe)
+ and (system.native_id, " ".join(self._path(fe).split())) in held
+ }
ours: dict[str, tuple[NativeSystem, dict[str, tuple[NativeFile, str]]]] = {}
for system in systems.values():
writable = [fe for fe in system.files if self.writable(fe)]
diff --git a/tests/test_exporters.py b/tests/test_exporters.py
index 3c7f4dfc..72025477 100644
--- a/tests/test_exporters.py
+++ b/tests/test_exporters.py
@@ -1169,6 +1169,32 @@ class RecalboxExport(unittest.TestCase):
[("cdimono1.zip", False, True)],
)
+ def test_an_element_kept_as_written_is_not_counted_lost(self):
+ """The truth takes the entry on sha1 and size and names no md5: the
+ element is written back whole, and the run counted it a platform
+ entry the format cannot keep."""
+ scraped = {"systems": {"x": {"native_id": "x", "files": [{
+ "name": "a.bin", "destination": "x/a.bin", "md5": "a" * 32, "size": 100,
+ "native_system": "x", "core": "libretro/foo", "required": True,
+ }]}}}
+ truth = {"systems": {"x": {"files": [{
+ "name": "a.bin", "path": "x/a.bin", "sha1": "b" * 40, "size": 200,
+ "required": True, "_cores": ["foo"],
+ }]}}}
+ element = f' '
+ original = (
+ '\n\n'
+ f' \n{element}\n \n\n'
+ )
+ systems, report = build_native_model(truth, scraped)
+ exporter = discover_exporters()["recalbox"]()
+ produced = exporter.render(systems, report, {"es_bios.xml": original}, None)
+ self.assertIn(element, produced["es_bios.xml"])
+ fe = next(fe for s in systems.values() for fe in s.files)
+ self.assertTrue(exporter.lands(fe))
+ self.assertEqual(landed_changes(exporter, systems).records, [])
+ self.assertEqual(len(exporter.withheld()), 1)
+
def test_a_truncated_export_is_reported(self):
exporter, systems, _ = self._render()
empty = {