mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 21:43:23 -05:00
147 lines
5.1 KiB
Python
147 lines
5.1 KiB
Python
"""Exporter for RomM's known_bios_files.json.
|
|
|
|
Keys are "<igdb slug>:<filename>". RomM verifies a firmware file with
|
|
`file_size_bytes == int(entry.get("size", 0))` and then one hash among md5,
|
|
sha1 and crc, so an entry without a size can never match and an entry
|
|
without a hash can never match either. Neither is written.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from collections import OrderedDict
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from scraper.romm_scraper import SLUG_MAP
|
|
|
|
from .base_exporter import BaseExporter
|
|
from .baseline import NativeFile, NativeSystem, Report
|
|
|
|
SOURCE_URL = (
|
|
"https://raw.githubusercontent.com/rommapp/romm/master/backend/models"
|
|
"/fixtures/known_bios_files.json"
|
|
)
|
|
|
|
|
|
class Exporter(BaseExporter):
|
|
"""Write RomM's known_bios_files.json, corrected."""
|
|
|
|
# (system, filename) keys the platform itself declares, set by render.
|
|
_platform_keys: frozenset[tuple[str, str]] = frozenset()
|
|
|
|
@staticmethod
|
|
def platform_name() -> str:
|
|
return "romm"
|
|
|
|
@staticmethod
|
|
def native_filename() -> str:
|
|
return "known_bios_files.json"
|
|
|
|
@staticmethod
|
|
def carries() -> frozenset[str]:
|
|
return frozenset({"size", "crc32", "md5", "sha1"})
|
|
|
|
@staticmethod
|
|
def native_sources() -> dict[str, str]:
|
|
return {"known_bios_files.json": SOURCE_URL}
|
|
|
|
@staticmethod
|
|
def _verifiable(fe: NativeFile) -> bool:
|
|
return bool(fe.size()) and any(
|
|
fe.hash(h) for h in ("md5", "sha1", "crc32")
|
|
)
|
|
|
|
@staticmethod
|
|
def _known_platform(native_id: str) -> bool:
|
|
"""RomM keys by IGDB platform slug, and only looks up its own.
|
|
|
|
A key spelled with one of our slugs (capcom-cps3, snk-neogeo-mvs)
|
|
matches nothing on their side, so it is reported rather than
|
|
written.
|
|
"""
|
|
return native_id in SLUG_MAP
|
|
|
|
def writable(self, fe: NativeFile, require: str = "") -> bool: # noqa: ARG002
|
|
"""What RomM already ships stays; the conditions gate additions.
|
|
|
|
An entry of theirs that could never verify is still theirs, and the
|
|
round trip is not the place to decide otherwise. An addition whose
|
|
slug:filename key the platform already holds cannot be written: the
|
|
fixture is a dict, and the second write replaced RomM's Dreamcast
|
|
boot.bin with fbneo's 480-byte file of the same name.
|
|
"""
|
|
if fe.platform is not None:
|
|
return True
|
|
if (fe.native_system, fe.name) in self._platform_keys:
|
|
return False
|
|
return self._verifiable(fe) and self._known_platform(fe.native_system)
|
|
|
|
def render(
|
|
self,
|
|
systems: dict[str, NativeSystem],
|
|
report: Report,
|
|
originals: dict[str, str],
|
|
scraped: dict | None = None,
|
|
) -> dict[str, str]:
|
|
output: OrderedDict[str, dict] = OrderedDict()
|
|
self._platform_keys = {
|
|
(system.native_id, fe.name)
|
|
for system in systems.values()
|
|
for fe in system.files
|
|
if fe.platform is not None
|
|
}
|
|
|
|
for system in sorted(systems.values(), key=lambda s: s.native_id):
|
|
for fe in sorted(system.files, key=lambda f: f.name):
|
|
if not self.writable(fe):
|
|
continue
|
|
entry: OrderedDict[str, str] = OrderedDict()
|
|
# The fixture states every value as a string, size included.
|
|
entry["size"] = str(fe.size())
|
|
crc = fe.hash("crc32")
|
|
if crc:
|
|
entry["crc"] = crc
|
|
md5 = fe.hash("md5")
|
|
if md5:
|
|
entry["md5"] = md5
|
|
sha1 = fe.hash("sha1")
|
|
if sha1:
|
|
entry["sha1"] = sha1
|
|
output[f"{system.native_id}:{fe.name}"] = entry
|
|
|
|
text = json.dumps(output, indent=2, ensure_ascii=False) + "\n"
|
|
return {self.native_filename(): text}
|
|
|
|
def validate(
|
|
self,
|
|
systems: dict[str, NativeSystem],
|
|
produced: dict[str, str],
|
|
) -> list[str]:
|
|
try:
|
|
data = json.loads(produced[self.native_filename()])
|
|
except json.JSONDecodeError as exc:
|
|
return [f"the JSON does not parse: {exc}"]
|
|
|
|
issues: list[str] = []
|
|
for key, entry in data.items():
|
|
slug = key.split(":", 1)[0] if ":" in key else ""
|
|
if not slug:
|
|
issues.append(f"key without a platform slug: {key}")
|
|
elif not self._known_platform(slug):
|
|
issues.append(f"platform slug RomM does not know: {slug}")
|
|
if not entry.get("size"):
|
|
issues.append(f"entry without a size, never verifiable: {key}")
|
|
if not any(entry.get(h) for h in ("md5", "sha1", "crc")):
|
|
issues.append(f"entry without a hash, never verifiable: {key}")
|
|
|
|
for system in systems.values():
|
|
for fe in system.files:
|
|
if not self.writable(fe):
|
|
continue
|
|
if f"{system.native_id}:{fe.name}" not in data:
|
|
issues.append(f"absent: {system.native_id}/{fe.name}")
|
|
return issues
|