mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-16 16:33:27 -05:00
fix: claim the archives a core reads by members
This commit is contained in:
1 parent
9faa3a3f79
commit
598c930771
6 files changed
+176
-31
No files matched your search
+9
-9
@@ -17,9 +17,9 @@
|
||||
],
|
||||
"standalone_copies": [],
|
||||
"total_files": 5869,
|
||||
"total_size": 6160101174,
|
||||
"total_size": 6161070712,
|
||||
"pack_files": 8512,
|
||||
"pack_size": 6200633701,
|
||||
"pack_size": 6201603239,
|
||||
"total_omitted": 0,
|
||||
"omitted_files": [],
|
||||
"core_names": {
|
||||
@@ -1528,10 +1528,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "fbneo/nmk004.zip",
|
||||
"sha1": "489256f5e2001070d2ad94c90d255282c71ed274",
|
||||
"sha256": "6ca3142ebe127f5d2cb755c53740a92d9bf1574bde8daa32642de2991af89aaa",
|
||||
"sha1": "9f84f563df14ea1a1bcaaf05b720c3f5545e5a83",
|
||||
"sha256": "13d503ea6c7e7b9a2249837cf915bff6df862b7035f5c99b18240088ec10854a",
|
||||
"size": 3556,
|
||||
"repo_path": "bios/Arcade/Arcade/nmk004.zip",
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/nmk004.zip.f70b6562",
|
||||
"cores": null,
|
||||
"systems": [
|
||||
"arcade"
|
||||
@@ -1841,10 +1841,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "fbneo/neocdz.zip",
|
||||
"sha1": "4f28af31ca0defdd73d80edec2fa296908e624dc",
|
||||
"sha256": "6aa507a5dc33c45d2592bf2bc77eb3e497762221ddd9225f18583a5c55d38667",
|
||||
"size": 214681,
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.4f28af31",
|
||||
"sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6",
|
||||
"sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114",
|
||||
"size": 1184219,
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be",
|
||||
"cores": null,
|
||||
"systems": [
|
||||
"arcade",
|
||||
|
||||
@@ -256,9 +256,9 @@
|
||||
}
|
||||
],
|
||||
"total_files": 5869,
|
||||
"total_size": 6160101174,
|
||||
"total_size": 6161070712,
|
||||
"pack_files": 8512,
|
||||
"pack_size": 6200633701,
|
||||
"pack_size": 6201603239,
|
||||
"total_omitted": 0,
|
||||
"omitted_files": [],
|
||||
"core_names": {
|
||||
@@ -1767,10 +1767,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "fbneo/nmk004.zip",
|
||||
"sha1": "489256f5e2001070d2ad94c90d255282c71ed274",
|
||||
"sha256": "6ca3142ebe127f5d2cb755c53740a92d9bf1574bde8daa32642de2991af89aaa",
|
||||
"sha1": "9f84f563df14ea1a1bcaaf05b720c3f5545e5a83",
|
||||
"sha256": "13d503ea6c7e7b9a2249837cf915bff6df862b7035f5c99b18240088ec10854a",
|
||||
"size": 3556,
|
||||
"repo_path": "bios/Arcade/Arcade/nmk004.zip",
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/nmk004.zip.f70b6562",
|
||||
"cores": null,
|
||||
"systems": [
|
||||
"arcade"
|
||||
@@ -2080,10 +2080,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "fbneo/neocdz.zip",
|
||||
"sha1": "4f28af31ca0defdd73d80edec2fa296908e624dc",
|
||||
"sha256": "6aa507a5dc33c45d2592bf2bc77eb3e497762221ddd9225f18583a5c55d38667",
|
||||
"size": 214681,
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.4f28af31",
|
||||
"sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6",
|
||||
"sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114",
|
||||
"size": 1184219,
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be",
|
||||
"cores": null,
|
||||
"systems": [
|
||||
"arcade",
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
],
|
||||
"standalone_copies": [],
|
||||
"total_files": 9906,
|
||||
"total_size": 8494372405,
|
||||
"total_size": 8495341943,
|
||||
"pack_files": 12549,
|
||||
"pack_size": 8534904932,
|
||||
"pack_size": 8535874470,
|
||||
"total_omitted": 0,
|
||||
"omitted_files": [],
|
||||
"core_names": {
|
||||
@@ -1545,10 +1545,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "fbneo/nmk004.zip",
|
||||
"sha1": "489256f5e2001070d2ad94c90d255282c71ed274",
|
||||
"sha256": "6ca3142ebe127f5d2cb755c53740a92d9bf1574bde8daa32642de2991af89aaa",
|
||||
"sha1": "9f84f563df14ea1a1bcaaf05b720c3f5545e5a83",
|
||||
"sha256": "13d503ea6c7e7b9a2249837cf915bff6df862b7035f5c99b18240088ec10854a",
|
||||
"size": 3556,
|
||||
"repo_path": "bios/Arcade/Arcade/nmk004.zip",
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/nmk004.zip.f70b6562",
|
||||
"cores": null,
|
||||
"systems": [
|
||||
"arcade"
|
||||
@@ -1858,10 +1858,10 @@
|
||||
},
|
||||
{
|
||||
"dest": "fbneo/neocdz.zip",
|
||||
"sha1": "4f28af31ca0defdd73d80edec2fa296908e624dc",
|
||||
"sha256": "6aa507a5dc33c45d2592bf2bc77eb3e497762221ddd9225f18583a5c55d38667",
|
||||
"size": 214681,
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.4f28af31",
|
||||
"sha1": "985d935c06fe278ce39d7690aa6c53df88954cd6",
|
||||
"sha256": "4919ba2a7650b24d1bf2c9107c89a80d93b50703a0a371fa5113c791bd03e114",
|
||||
"size": 1184219,
|
||||
"repo_path": "bios/Arcade/Arcade/.variants/neocdz.zip.ba20a7be",
|
||||
"cores": null,
|
||||
"systems": [
|
||||
"arcade",
|
||||
|
||||
@@ -623,6 +623,12 @@ def archive_entry(archive: str, files: list[dict]) -> dict:
|
||||
return {**own, "members": members} if members else own
|
||||
|
||||
|
||||
def holds_required_members(path: str, members: list[dict]) -> bool:
|
||||
"""Whether an archive holds every member a profile requires inside it."""
|
||||
crcs = zip_member_crcs(path)
|
||||
return all(m["crc32"] in crcs for m in members if m["required"])
|
||||
|
||||
|
||||
def _holding_members(paths: list[str], members: list[dict]) -> list[str]:
|
||||
"""The same-named archives holding the most required members, then the most members.
|
||||
|
||||
|
||||
+81
-4
@@ -20,8 +20,10 @@ from dataclasses import dataclass, field
|
||||
|
||||
import nativemode
|
||||
from common import (
|
||||
archive_entry,
|
||||
build_zip_contents_index,
|
||||
check_inside_zip,
|
||||
holds_required_members,
|
||||
load_data_dir_registry,
|
||||
parse_md5_list,
|
||||
resolution_is_hash_exact,
|
||||
@@ -34,7 +36,11 @@ from validation import outside_gap_scope
|
||||
# A profile entry can prove a slot without declaring a hash: Dolphin names no
|
||||
# checksum for the GameCube boot ROM because its source names none, and the
|
||||
# proof is then the path the repository stores the file at.
|
||||
PROVEN_STATUSES = frozenset({"path_exact"})
|
||||
PROVEN_STATUSES = frozenset({"path_exact", "members_held"})
|
||||
|
||||
# A core that reads ROMs inside an archive proves the archive by its members:
|
||||
# FBNeo's neocdz.zip is any copy holding neocd.bin and 000-lo.lo by crc.
|
||||
MEMBERS_HELD = "members_held"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -169,6 +175,12 @@ def profile_claims(
|
||||
if profile.get("type") in ("launcher", "alias"):
|
||||
continue
|
||||
is_standalone = runs_standalone(emu_name, profile, standalone_cores)
|
||||
claims.extend(
|
||||
_archive_claims(
|
||||
emu_name, profile, is_standalone, db, base_dest, placement,
|
||||
zip_contents, data_dir_registry,
|
||||
)
|
||||
)
|
||||
for entry in profile.get("files") or []:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
@@ -178,9 +190,8 @@ def profile_claims(
|
||||
if outside_gap_scope(entry, is_standalone):
|
||||
continue
|
||||
# A member of a romset is not a file at the BIOS root: the archive
|
||||
# holding it is what occupies a destination, and it is declared
|
||||
# separately. Claiming the member put FBNeo's msx.zip contents
|
||||
# loose at the platform's root.
|
||||
# holding it occupies the destination (_archive_claims). Claiming
|
||||
# the member put FBNeo's msx.zip contents loose at the root.
|
||||
if entry.get("archive"):
|
||||
continue
|
||||
dest = (
|
||||
@@ -221,6 +232,69 @@ def profile_claims(
|
||||
return claims
|
||||
|
||||
|
||||
def _archive_claims(
|
||||
emu_name: str,
|
||||
profile: dict,
|
||||
is_standalone: bool,
|
||||
db: dict,
|
||||
base_dest: str,
|
||||
placement,
|
||||
zip_contents: dict | None,
|
||||
data_dir_registry: dict | None,
|
||||
) -> list[Claim]:
|
||||
"""The archives a core reads its ROMs from, where the builder places them.
|
||||
|
||||
The archive sits at the root and, for a core that reads a directory of
|
||||
its own (archive_prefix), under it as well. The claim is proven when the
|
||||
copy holds every member the core requires.
|
||||
"""
|
||||
files = [
|
||||
entry for entry in profile.get("files") or []
|
||||
if isinstance(entry, dict) and not outside_gap_scope(entry, is_standalone)
|
||||
]
|
||||
prefix = profile.get("archive_prefix", "")
|
||||
claims: list[Claim] = []
|
||||
for archive in sorted({entry["archive"] for entry in files if entry.get("archive")}):
|
||||
entry = archive_entry(archive, files)
|
||||
members = entry.get("members")
|
||||
if not members:
|
||||
continue
|
||||
system = next(f.get("system") for f in files if f.get("archive") == archive)
|
||||
for dest in (archive, f"{prefix}/{archive}") if prefix else (archive,):
|
||||
if placement is None:
|
||||
full = f"{base_dest}/{dest}" if base_dest else dest
|
||||
else:
|
||||
placed = placement.place({"profile": emu_name, "system": system}, dest)
|
||||
if placed is None:
|
||||
continue
|
||||
full = placed[1]
|
||||
local, status = resolve_local_file(
|
||||
{**entry, "source_profile": emu_name},
|
||||
db,
|
||||
zip_contents,
|
||||
dest_hint=dest,
|
||||
data_dir_registry=data_dir_registry,
|
||||
)
|
||||
if (
|
||||
local
|
||||
and not resolution_is_hash_exact(status)
|
||||
and holds_required_members(local, members)
|
||||
):
|
||||
status = MEMBERS_HELD
|
||||
claims.append(
|
||||
Claim(
|
||||
origin="profile",
|
||||
destination=full,
|
||||
name=archive,
|
||||
emulator=emu_name,
|
||||
entry=entry,
|
||||
local_path=local,
|
||||
status=status,
|
||||
)
|
||||
)
|
||||
return claims
|
||||
|
||||
|
||||
def _claim_rank(claim: Claim) -> int:
|
||||
"""How strongly a platform claim speaks for what the pack ships."""
|
||||
if resolution_is_hash_exact(claim.status):
|
||||
@@ -526,6 +600,9 @@ def _satisfies(profile: Claim, platform: Claim, db: dict) -> bool:
|
||||
"""
|
||||
if profile.local_path == platform.local_path:
|
||||
return True
|
||||
members = profile.entry.get("members")
|
||||
if members and platform.local_path:
|
||||
return holds_required_members(platform.local_path, members)
|
||||
if (
|
||||
profile.local_path
|
||||
and _lists_hashes(platform.entry)
|
||||
|
||||
@@ -646,5 +646,67 @@ class StrongestProfileClaimWins(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class AnArchiveIsClaimedByItsMembers(unittest.TestCase):
|
||||
"""System.dat pins fbneo/neocdz.zip to a copy holding neocd.bin alone;
|
||||
FBNeo also requires 000-lo.lo inside it. Members were skipped by the
|
||||
claims, so nothing contested the pin and RetroArch shipped an archive
|
||||
the core cannot boot from."""
|
||||
|
||||
def setUp(self):
|
||||
import hashlib
|
||||
import zipfile
|
||||
import zlib
|
||||
|
||||
roms = {"neocd.bin": b"bios" * 8, "000-lo.lo": b"zoom" * 8}
|
||||
entries = {}
|
||||
self.paths = {}
|
||||
for label, members in (("short", ["neocd.bin"]), ("full", list(roms))):
|
||||
path = Path(f"bios/Arc/{label}/neocdz.zip")
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(path, "w") as zf:
|
||||
for member in members:
|
||||
zf.writestr(member, roms[member])
|
||||
data = path.read_bytes()
|
||||
entries[hashlib.sha1(data).hexdigest()] = {
|
||||
"path": str(path), "md5": hashlib.md5(data).hexdigest(), "size": len(data),
|
||||
}
|
||||
self.paths[label] = (str(path), hashlib.md5(data).hexdigest())
|
||||
self.addCleanup(shutil.rmtree, "bios/Arc", True)
|
||||
self.db = _db(entries)
|
||||
self.profiles = {"fbneo": {
|
||||
"emulator": "FBNeo", "type": "libretro", "systems": ["snk-neogeo-cd"],
|
||||
"archive_prefix": "fbneo",
|
||||
"files": [
|
||||
{"name": name, "archive": "neocdz.zip", "system": "snk-neogeo-cd",
|
||||
"required": True, "crc32": format(zlib.crc32(data), "08x")}
|
||||
for name, data in roms.items()
|
||||
],
|
||||
}}
|
||||
|
||||
def _config(self, mode: str, pinned: str) -> dict:
|
||||
return {
|
||||
"platform": "P", "verification_mode": mode, "base_destination": "system",
|
||||
"cores": ["fbneo"],
|
||||
"systems": {"snk-neogeo-cd": {"files": [{
|
||||
"name": "neocdz.zip", "destination": "fbneo/neocdz.zip",
|
||||
"md5": self.paths[pinned][1],
|
||||
}]}},
|
||||
}
|
||||
|
||||
def test_existence_serves_the_copy_the_core_boots_from(self):
|
||||
overrides = slots.pack_overrides(self._config("existence", "short"), self.profiles, self.db)
|
||||
self.assertEqual(overrides, {"system/fbneo/neocdz.zip": self.paths["full"][0]})
|
||||
|
||||
def test_a_content_check_keeps_the_pin_and_reports_it(self):
|
||||
config = self._config("md5", "short")
|
||||
self.assertEqual(slots.pack_overrides(config, self.profiles, self.db), {})
|
||||
conflicts = slots.find_conflicts(config, self.profiles, self.db, "system")
|
||||
self.assertEqual([c.destination for c in conflicts], ["system/fbneo/neocdz.zip"])
|
||||
|
||||
def test_a_pin_holding_the_members_is_agreement(self):
|
||||
config = self._config("md5", "full")
|
||||
self.assertEqual(slots.find_conflicts(config, self.profiles, self.db, "system"), [])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user