mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 12:33:53 -06:00
fix: read crc32 past a copier header in slots
Geargrafx drops a 512-byte header before its crc32 (src/media.cpp:754-773 at 7c447b0b), and its profile lists both sizes. slots compared the crc32 of the whole file, so EmuELEC's headered syscard3.pce read as contested.
This commit is contained in:
1 parent
6bf62d5746
commit
74a4a036ea
2 files changed
+64
No files matched your search
@@ -20,6 +20,7 @@ feeds the report this module prints.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import zlib
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -892,10 +893,37 @@ def _accepted_by_all(path: str, entries: list[dict], db: dict) -> bool:
|
||||
for field_name in ("md5", "sha1", "sha256", "crc32"):
|
||||
declared = _declared_hashes(entry, field_name)
|
||||
if declared and str(record.get(field_name, "")).lower() not in declared:
|
||||
if field_name == "crc32" and _headerless_crc32(path, record, entry) in declared:
|
||||
continue
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
COPIER_HEADER = 512
|
||||
|
||||
|
||||
def _headerless_crc32(path: str, record: dict, entry: dict) -> str | None:
|
||||
"""The crc32 of a file past its 512-byte copier header, when the entry
|
||||
declares both the file's size and the size without the header.
|
||||
|
||||
Geargrafx drops the header when the file is 512 bytes over the expected
|
||||
size and computes the crc32 on the rest (src/media.cpp:754-773). The
|
||||
profile lists both sizes. EmuELEC's headered syscard3.pce (crc32
|
||||
64f78e3c) is the 6d9a73ef image the profile names.
|
||||
"""
|
||||
sizes = entry.get("size")
|
||||
declared = sizes if isinstance(sizes, list) else [sizes]
|
||||
size = record.get("size")
|
||||
if not isinstance(size, int) or not {size, size - COPIER_HEADER} <= set(declared):
|
||||
return None
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
f.seek(COPIER_HEADER)
|
||||
return f"{zlib.crc32(f.read()):08x}"
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def _same_file_family(paths: list[str]) -> bool:
|
||||
"""Whether the paths are one file and its own pinned variants.
|
||||
|
||||
|
||||
@@ -992,6 +992,42 @@ class TwoPathsHoldingOneContentAgree(unittest.TestCase):
|
||||
self.assertFalse(slots._satisfies(different, platform, db))
|
||||
|
||||
|
||||
class ACopierHeaderIsNotAnotherImage(unittest.TestCase):
|
||||
"""Geargrafx drops a 512-byte header before the crc32 (src/media.cpp:
|
||||
754-773). EmuELEC's headered syscard3.pce was reported contested, though
|
||||
the core reads it as the 6d9a73ef image its profile lists."""
|
||||
|
||||
def test_headered_file_satisfies_the_profile(self):
|
||||
import zlib # noqa: PLC0415
|
||||
|
||||
image = b"S" * 1024
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
headered = Path(tmp, "headered.pce")
|
||||
headered.write_bytes(b"\0" * 512 + image)
|
||||
plain = Path(tmp, "plain.pce")
|
||||
plain.write_bytes(image)
|
||||
crc = f"{zlib.crc32(image):08x}"
|
||||
db = {"files": {
|
||||
"a" * 40: {"path": str(headered), "size": 1536,
|
||||
"crc32": f"{zlib.crc32(headered.read_bytes()):08x}"},
|
||||
"b" * 40: {"path": str(plain), "size": 1024, "crc32": crc},
|
||||
}}
|
||||
platform = Claim("platform", "syscard3.pce", "syscard3.pce",
|
||||
local_path=str(headered), status="md5_exact")
|
||||
profile = Claim(
|
||||
"profile", "syscard3.pce", "syscard3.pce", emulator="geargrafx",
|
||||
local_path=str(plain), status="crc32_exact",
|
||||
entry={"name": "syscard3.pce", "crc32": [crc], "size": [1024, 1536]},
|
||||
)
|
||||
self.assertTrue(slots._satisfies(profile, platform, db))
|
||||
one_size = Claim(
|
||||
"profile", "syscard3.pce", "syscard3.pce", emulator="geargrafx",
|
||||
local_path=str(plain), status="crc32_exact",
|
||||
entry={"name": "syscard3.pce", "crc32": [crc], "size": 1024},
|
||||
)
|
||||
self.assertFalse(slots._satisfies(one_size, platform, db))
|
||||
|
||||
|
||||
class TheSlotsSummaryClosesTheRun(unittest.TestCase):
|
||||
"""The totals came before the collision section and ignored it, and the
|
||||
help said the default run fails on conflicts the pack should settle,
|
||||
|
||||
Reference in new issue
Block a user