mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 13:33:24 -05:00
fix: exporters keep what the maintainer wrote
This commit is contained in:
1 parent
3baec1794b
commit
8fb65b8e97
4 files changed
+133
-20
No files matched your search
@@ -62,10 +62,29 @@ class Exporter(BaseExporter):
|
||||
# The data block is a fraction of the file; the rest is the checker.
|
||||
return True
|
||||
|
||||
def _bios_files(self, files: list[NativeFile]) -> str:
|
||||
return ", ".join(self._bios_items(files))
|
||||
def _bios_files(self, files: list[NativeFile], before: dict | None = None) -> str:
|
||||
return ", ".join(self._bios_items(files, before))
|
||||
|
||||
def _bios_items(self, files: list[NativeFile]) -> list[str]:
|
||||
@staticmethod
|
||||
def _runner_keys(item: dict | None) -> list[str]:
|
||||
"""The emulator and core checkBios reads through coreExists().
|
||||
|
||||
Not in our model, so they travel from the maintainer's own entry: a
|
||||
rewritten item without them made the checker treat the BIOS as
|
||||
wanted by every build (vectrex, tvc, macintosh, bk).
|
||||
"""
|
||||
return [
|
||||
f'"{key}": "{item[key]}"'
|
||||
for key in ("emulator", "core")
|
||||
if item and isinstance(item.get(key), str)
|
||||
]
|
||||
|
||||
def _bios_items(self, files: list[NativeFile], before: dict | None = None) -> list[str]:
|
||||
originals = {
|
||||
str(item.get("file", "")): item
|
||||
for item in (before or {}).get("biosFiles", [])
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
parts: list[str] = []
|
||||
for fe in files:
|
||||
# The platform states an unhashed file as an empty md5 rather
|
||||
@@ -80,14 +99,25 @@ class Exporter(BaseExporter):
|
||||
zipped = fe.native("zipped_file", "")
|
||||
if zipped:
|
||||
item.append(f'"zippedFile": "{zipped}"')
|
||||
item.extend(self._runner_keys(originals.get(path)))
|
||||
parts.append("{ " + ", ".join(item) + " }")
|
||||
return parts
|
||||
|
||||
def _entry_line(self, system: NativeSystem, files: list[NativeFile]) -> str:
|
||||
@staticmethod
|
||||
def _own_entry(system: NativeSystem, before: dict | None) -> dict:
|
||||
"""The maintainer's entry for this system, out of the parsed pair."""
|
||||
inner = (before or {}).get(system.native_id)
|
||||
return inner if isinstance(inner, dict) else {}
|
||||
|
||||
def _entry_line(
|
||||
self, system: NativeSystem, files: list[NativeFile], before: dict | None = None
|
||||
) -> str:
|
||||
own = self._own_entry(system, before)
|
||||
runner = "".join(f"{key}, " for key in self._runner_keys(own))
|
||||
return (
|
||||
f'{_INDENT}"{system.native_id}": '
|
||||
f'{{ "name": "{self.display_name(system)}", '
|
||||
f'"biosFiles": [ {self._bios_files(files)} ] }},'
|
||||
f'{{ "name": "{self.display_name(system)}", {runner}'
|
||||
f'"biosFiles": [ {self._bios_files(files, own)} ] }},'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -127,14 +157,17 @@ class Exporter(BaseExporter):
|
||||
) -> list[str]:
|
||||
"""One entry, keeping the layout and the notes it was written with."""
|
||||
trailing, leading = self._item_notes(original)
|
||||
items = self._bios_items(files)
|
||||
before = self._parse_entry(original)
|
||||
own = self._own_entry(system, before)
|
||||
items = self._bios_items(files, own)
|
||||
one_line = len(original) == 1 and not trailing and not leading
|
||||
if one_line:
|
||||
return [self._entry_line(system, files)]
|
||||
return [self._entry_line(system, files, before)]
|
||||
|
||||
runner = "".join(f"{key}, " for key in self._runner_keys(own))
|
||||
head = (
|
||||
f'{_INDENT}"{system.native_id}": '
|
||||
f'{{ "name": "{self.display_name(system)}", "biosFiles": ['
|
||||
f'{{ "name": "{self.display_name(system)}", {runner}"biosFiles": ['
|
||||
)
|
||||
pad = " " * (len(_INDENT) + 4)
|
||||
lines = [head]
|
||||
@@ -244,8 +277,8 @@ class Exporter(BaseExporter):
|
||||
continue
|
||||
written.add(key)
|
||||
original_lines = body[start:end + 1]
|
||||
replacement = self._entry_line(*pair)
|
||||
before = self._parse_entry(original_lines)
|
||||
replacement = self._entry_line(*pair, before)
|
||||
after = self._parse_entry([replacement])
|
||||
if before is not None and before == after:
|
||||
# Nothing changed: keep the maintainer's own lines, comments
|
||||
|
||||
@@ -82,8 +82,15 @@ class Exporter(BaseExporter):
|
||||
for fe in system.files:
|
||||
# A SHA1 BizHawk declares is BizHawk's own code, the authority
|
||||
# for its cores: the truth's MSX.rom came from FBNeo and would
|
||||
# have made MSXHawk refuse the dump it accepts.
|
||||
if fe.hash("sha1") and not _hash_values(fe.platform or {}, "sha1"):
|
||||
# have made MSXHawk refuse the dump it accepts. An entry the
|
||||
# database never declared (can_add refuses it) names no call
|
||||
# of its own either: by name it would rewrite the homonym of
|
||||
# another system.
|
||||
if (
|
||||
fe.platform is not None
|
||||
and fe.hash("sha1")
|
||||
and not _hash_values(fe.platform, "sha1")
|
||||
):
|
||||
seen.setdefault(fe.name.casefold(), []).append(fe)
|
||||
resolved: dict[str, NativeFile] = {}
|
||||
for name, entries in seen.items():
|
||||
|
||||
@@ -176,8 +176,8 @@ class Exporter(BaseExporter):
|
||||
return nested, "bios"
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _merge(existing: object, ours: list[OrderedDict]) -> list[OrderedDict]:
|
||||
@classmethod
|
||||
def _merge(cls, existing: object, ours: list[OrderedDict]) -> list[OrderedDict]:
|
||||
"""Correct the component's own list; never replace it.
|
||||
|
||||
Assigning our entries wholesale dropped every file RetroDECK declares
|
||||
@@ -206,6 +206,9 @@ class Exporter(BaseExporter):
|
||||
for system in _systems_of(entry):
|
||||
key = (str(entry.get("filename", "")), system)
|
||||
seen_keys[key] = seen_keys.get(key, 0) + 1
|
||||
# A hash no revision shares is a revision the platform does not
|
||||
# know: appended, not counted corrected against entries kept as
|
||||
# they were (the 64DD IPL declared twice, both contradicted).
|
||||
for entry in entries:
|
||||
keys = _matching_keys(entry, by_key)
|
||||
other_revision = (
|
||||
@@ -213,18 +216,17 @@ class Exporter(BaseExporter):
|
||||
and seen_keys.get(keys[0], 0) > 1
|
||||
and not _md5_set(entry.get("md5")) & _md5_set(by_key[keys[0]].get("md5"))
|
||||
)
|
||||
if other_revision:
|
||||
merged.append(OrderedDict(entry))
|
||||
corrected.update(keys)
|
||||
continue
|
||||
if not keys:
|
||||
if other_revision or not keys:
|
||||
merged.append(OrderedDict(entry))
|
||||
continue
|
||||
declared = entry.get("system")
|
||||
combined = OrderedDict(entry)
|
||||
# Only what the format says it corrects: the maintainer's
|
||||
# description and search paths are theirs (neogeo.zip lost its
|
||||
# four roms directories to a path that does not exist).
|
||||
combined.update(
|
||||
(field, value) for field, value in by_key[keys[0]].items()
|
||||
if not (field == "system" and declared)
|
||||
if field in cls.carries() and not (field == "system" and declared)
|
||||
)
|
||||
merged.append(combined)
|
||||
corrected.update(keys)
|
||||
|
||||
@@ -355,5 +355,76 @@ class RetroPieProposals(unittest.TestCase):
|
||||
self.assertIsNone(RetroPie._insertion_point(alternatives))
|
||||
self.assertIsNotNone(RetroPie._insertion_point(enumeration))
|
||||
|
||||
|
||||
class BizHawkRewritesOnlyWhatItDeclares(unittest.TestCase):
|
||||
def test_a_truth_only_homonym_does_not_rewrite_a_declared_call(self):
|
||||
"""opera's panafz10-norsa.bin, absent from BizHawk's list, would have
|
||||
rewritten the 3DO call of the same name by index on the name alone."""
|
||||
sha_bizhawk, sha_other = "4" * 40, "e" * 40
|
||||
declared = NativeFile("bios.bin", "bios.bin", "SYSB", platform={"sha1": sha_bizhawk})
|
||||
truth_only = NativeFile("bios.bin", "bios.bin", "sys-a", truth={"sha1": sha_other})
|
||||
exporter = BizHawk()
|
||||
source = f'File("{sha_bizhawk.upper()}", 10, "bios.bin")\n'
|
||||
produced = exporter.render(
|
||||
{"SYSB": NativeSystem("SYSB", files=[declared]),
|
||||
"sys-a": NativeSystem("sys-a", files=[truth_only])},
|
||||
None, {exporter.native_filename(): source},
|
||||
)
|
||||
self.assertIn(sha_bizhawk.upper(), produced[exporter.native_filename()])
|
||||
self.assertNotIn(sha_other.upper(), produced[exporter.native_filename()])
|
||||
|
||||
|
||||
class RetroDeckCorrectsOnlyWhatItCarries(unittest.TestCase):
|
||||
def test_paths_and_description_stay_the_maintainers(self):
|
||||
"""neogeo.zip lost its four search directories to a path that does
|
||||
not exist, and 26 descriptions were rewritten."""
|
||||
from collections import OrderedDict # noqa: PLC0415
|
||||
|
||||
existing = [{"filename": "neogeo.zip", "system": "neogeo", "md5": A,
|
||||
"paths": ["$roms_path/neogeo", "$roms_path/fbneo", "$bios_path"],
|
||||
"description": "Neo Geo BIOS"}]
|
||||
ours = [OrderedDict([("filename", "neogeo.zip"), ("md5", B), ("system", "neogeo"),
|
||||
("description", "generated"), ("paths", "$bios_path/roms/neogeo")])]
|
||||
merged = RetroDeck._merge(existing, ours)
|
||||
self.assertEqual(merged[0]["md5"], B)
|
||||
self.assertEqual(merged[0]["paths"], ["$roms_path/neogeo", "$roms_path/fbneo", "$bios_path"])
|
||||
self.assertEqual(merged[0]["description"], "Neo Geo BIOS")
|
||||
|
||||
def test_a_hash_no_revision_shares_is_appended(self):
|
||||
"""IPL.n64 declared twice, both contradicted: counted corrected,
|
||||
written nowhere."""
|
||||
from collections import OrderedDict # noqa: PLC0415
|
||||
|
||||
existing = [{"filename": "IPL.n64", "system": "n64dd", "md5": A},
|
||||
{"filename": "IPL.n64", "system": "n64dd", "md5": B}]
|
||||
ours = [OrderedDict([("filename", "IPL.n64"), ("md5", C), ("system", "n64dd")])]
|
||||
merged = RetroDeck._merge(existing, ours)
|
||||
self.assertEqual([e["md5"] for e in merged], [A, B, C])
|
||||
|
||||
|
||||
class BatoceraKeepsTheRunnerKeys(unittest.TestCase):
|
||||
def test_emulator_and_core_travel_with_a_rewritten_entry(self):
|
||||
"""checkBios skips a BIOS whose emulator or core is not installed;
|
||||
rewritten without the keys, vectrex's MAME BIOS was wanted by every
|
||||
build."""
|
||||
from exporter.batocera_exporter import Exporter as Batocera # noqa: PLC0415
|
||||
|
||||
fe = NativeFile("bios.bin", "bios.bin", "vectrex", platform={"md5": A},
|
||||
truth={"md5": B}, corrections=["md5"])
|
||||
fe.native_data = {"native_path": "bios/bios.bin"}
|
||||
system = NativeSystem("vectrex", files=[fe])
|
||||
original = (
|
||||
' "vectrex": { "name": "Vectrex", "emulator": "libretro", "core": "mame", '
|
||||
'"biosFiles": [ { "md5": "' + A + '", "file": "bios/bios.bin", '
|
||||
'"emulator": "libretro", "core": "mame" } ] },'
|
||||
)
|
||||
exporter = Batocera()
|
||||
line = exporter._entry_line(system, [fe], exporter._parse_entry([original]))
|
||||
parsed = exporter._parse_entry([line])["vectrex"]
|
||||
self.assertEqual((parsed["emulator"], parsed["core"]), ("libretro", "mame"))
|
||||
self.assertEqual(parsed["biosFiles"][0]["core"], "mame")
|
||||
self.assertEqual(parsed["biosFiles"][0]["md5"], B)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user