From 83a4b21d308d18fc825ba74da0d77e3615462d48 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 21 Oct 2026 09:57:20 +0200 Subject: [PATCH] fix: keep platform hashes cores disagree on When two cores declare different values for one file, the truth keeps the first and marks the entry hash_conflict. Its hashes pair an entry and never replace the platform's: armsx2's GameIndex.yaml no longer overwrites System.dat's line for LRPS2. --- scripts/exporter/baseline.py | 11 ++++++++++- scripts/truth.py | 3 +++ tests/test_exporters.py | 24 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/scripts/exporter/baseline.py b/scripts/exporter/baseline.py index e6f28bef..c5a2c122 100644 --- a/scripts/exporter/baseline.py +++ b/scripts/exporter/baseline.py @@ -45,8 +45,17 @@ def checked_hashes(entry: dict | None) -> dict | None: MT32_CONTROL.ROM against nine sha1, and the one md5 beside them replaced System.dat's, another accepted revision. Such a value still pairs a renamed file, it never corrects or fills one. + + `hash_conflict` marks an entry whose cores declare different values: the + truth kept the first core's, so its hashes describe one revision among + those the cores accept. armsx2 and yaps2 disagree on GameIndex.yaml, and + armsx2's revision replaced System.dat's line for LRPS2. """ - if not entry or entry.get("validation") is None: + if not entry: + return entry + if entry.get("hash_conflict"): + return {k: v for k, v in entry.items() if k not in HASH_FIELDS} + if entry.get("validation") is None: return entry raw = entry["validation"] if isinstance(raw, dict): diff --git a/scripts/truth.py b/scripts/truth.py index 794e7575..d9739ca3 100644 --- a/scripts/truth.py +++ b/scripts/truth.py @@ -316,6 +316,9 @@ def _merge_hashes( existing[h] = [*o_list, *added] continue if not {str(v).lower() for v in t_list} & {str(v).lower() for v in o_list}: + # The value kept is one revision among those the cores accept: + # it pairs an entry, it never overrules one (checked_hashes). + existing["hash_conflict"] = sorted({*existing.get("hash_conflict", ()), h}) # Once per process: a run over every platform met the same pair # once per platform and opened its output on six copies. key = (file_entry["name"], h, str(ours), str(theirs), emu_name) diff --git a/tests/test_exporters.py b/tests/test_exporters.py index 0b039059..6504e890 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -324,6 +324,30 @@ class Reconciliation(unittest.TestCase): self.assertEqual(systems["coco"].files[0].hash("md5"), "a" * 32) self.assertEqual(report.hashes_corrected, ["coco/bas12.rom md5"]) + def test_a_hash_the_cores_disagree_on_corrects_nothing(self): + """armsx2 and yaps2 declare different revisions of GameIndex.yaml. + The truth kept armsx2's, and the export wrote it over System.dat's + line for LRPS2 as a hash and size correction.""" + import contextlib # noqa: PLC0415 + + import truth as truth_module # noqa: PLC0415 + + entry = {"name": "GameIndex.yaml", "sha1": "1" * 40, "size": 2690355} + with contextlib.redirect_stderr(io.StringIO()): + truth_module._merge_hashes( + entry, {"name": "GameIndex.yaml", "sha1": "2" * 40}, "yaps2" + ) + self.assertEqual(entry["hash_conflict"], ["sha1"]) + truth = {"systems": {"sony-playstation-2": {"files": [entry]}}} + scraped = {"systems": {"sony-playstation-2": {"native_id": "ps2", "files": [ + {"name": "GameIndex.yaml", "destination": "pcsx2/resources/GameIndex.yaml", + "native_system": "ps2", "sha1": "3" * 40, "size": 1737799}, + ]}}} + systems, report = build_native_model(truth, scraped) + line = systems["ps2"].files[0] + self.assertEqual((line.hash("sha1"), line.size()), ("3" * 40, 1737799)) + self.assertEqual(report.hashes_corrected, []) + def test_the_diff_names_no_mismatch_the_code_does_not_check(self): from truth import _diff_system truth_sys = {"files": [{"name": "scph1001.bin", "md5": "a" * 32,