mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-16 16:33:27 -05:00
fix: pick the archive holding a core's members
This commit is contained in:
1 parent
a491d9728a
commit
1699caaca2
16 files changed
+461
-270
No files matched your search
@@ -599,6 +599,48 @@ def _rank_tail_candidates(
|
||||
)
|
||||
|
||||
|
||||
def archive_members(archive: str, files: list[dict]) -> list[dict]:
|
||||
"""The members a profile reads inside *archive*, with what it requires."""
|
||||
return [
|
||||
{"crc32": str(f["crc32"]).lower(), "required": bool(f.get("required", True))}
|
||||
for f in files
|
||||
if f.get("archive") == archive and f.get("crc32")
|
||||
]
|
||||
|
||||
|
||||
def archive_entry(archive: str, files: list[dict]) -> dict:
|
||||
"""The entry a profile's archive resolves through.
|
||||
|
||||
Its own declaration when it has one (it carries the container's hashes),
|
||||
and the members it reads inside, so a copy of the name that lacks a ROM
|
||||
the core requires is not taken for the archive.
|
||||
"""
|
||||
own = next(
|
||||
(f for f in files if f.get("name") == archive and not f.get("archive")),
|
||||
{"name": archive},
|
||||
)
|
||||
members = archive_members(archive, files)
|
||||
return {**own, "members": members} if members else own
|
||||
|
||||
|
||||
def _holding_members(paths: list[str], members: list[dict]) -> list[str]:
|
||||
"""The same-named archives holding the most required members, then the most members.
|
||||
|
||||
FBNeo reads neocd.bin and 000-lo.lo inside neocdz.zip; the primary copy,
|
||||
a set without 000-lo.lo, won on the name while a variant held both.
|
||||
"""
|
||||
def held(path: str) -> tuple[int, int]:
|
||||
crcs = zip_member_crcs(path)
|
||||
return (
|
||||
sum(1 for m in members if m["required"] and m["crc32"] in crcs),
|
||||
sum(1 for m in members if m["crc32"] in crcs),
|
||||
)
|
||||
|
||||
scores = {path: held(path) for path in paths}
|
||||
best = max(scores.values(), default=(0, 0))
|
||||
return [path for path in paths if scores[path] == best]
|
||||
|
||||
|
||||
def _by_affinity(paths: list[str], file_entry: dict, dest_hint: str) -> list[str]:
|
||||
"""Same-named candidates, the likeliest first.
|
||||
|
||||
@@ -910,6 +952,8 @@ def resolve_local_file(
|
||||
if candidates:
|
||||
if zipped_file:
|
||||
candidates = [p for p in candidates if ".zip" in os.path.basename(p)]
|
||||
if file_entry.get("members"):
|
||||
candidates = _holding_members(candidates, file_entry["members"])
|
||||
candidates = _by_affinity(candidates, file_entry, dest_hint)
|
||||
primary = [p for p in candidates if "/.variants/" not in p]
|
||||
if primary or candidates:
|
||||
@@ -2010,6 +2054,7 @@ from ziptools import ( # noqa: E402,F401
|
||||
safe_extract_zip,
|
||||
_zip_contents_cache,
|
||||
_BOUNDED_RATIO_METHODS,
|
||||
zip_member_crcs,
|
||||
)
|
||||
from artifacts import ( # noqa: E402,F401
|
||||
write_if_changed,
|
||||
|
||||
@@ -31,6 +31,7 @@ sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import resolve_platform_cores as _platform_cores
|
||||
from common import (
|
||||
apply_target_overrides,
|
||||
archive_entry,
|
||||
profiles_for_systems,
|
||||
write_text_atomic,
|
||||
artifact_lock,
|
||||
@@ -1299,20 +1300,11 @@ def generate_emulator_pack(
|
||||
if _has_path_conflict(archive_dest, seen_destinations, seen_parents):
|
||||
continue
|
||||
|
||||
# Prefer the profile's own entry for the archive (carries
|
||||
# hashes for exact resolution when several dumps share a name)
|
||||
archive_entry = next(
|
||||
(
|
||||
f
|
||||
for f in files
|
||||
if f.get("name") == archive_name and not f.get("archive")
|
||||
),
|
||||
{"name": archive_name},
|
||||
)
|
||||
if required_only and _archive_is_optional(archive_entry, archive_name, files):
|
||||
own_entry = archive_entry(archive_name, files)
|
||||
if required_only and _archive_is_optional(own_entry, archive_name, files):
|
||||
continue
|
||||
local_path, status = resolve_file(
|
||||
archive_entry,
|
||||
own_entry,
|
||||
db,
|
||||
bios_dir,
|
||||
zip_contents,
|
||||
@@ -1321,7 +1313,7 @@ def generate_emulator_pack(
|
||||
)
|
||||
if local_path and status not in ("not_found",):
|
||||
if local_path.endswith(".zip"):
|
||||
_add_zip_to_pack(local_path, archive_dest, zf, archive_entry)
|
||||
_add_zip_to_pack(local_path, archive_dest, zf, own_entry)
|
||||
else:
|
||||
_add_pack_member(zf, local_path, archive_dest)
|
||||
seen_destinations.add(archive_dest)
|
||||
|
||||
@@ -7,6 +7,7 @@ this module builds so the report and the pack withdraw the same files."""
|
||||
from __future__ import annotations
|
||||
|
||||
from common import PROFILE_IDENTITY_FIELDS
|
||||
from common import archive_members
|
||||
from common import declared_root, expand_platform_declared_names, platform_declarations
|
||||
from common import load_emulator_profiles
|
||||
from common import resolution_is_hash_exact
|
||||
@@ -497,6 +498,9 @@ def _archive_prefix_extras(
|
||||
"region": archive_entry.get("region"),
|
||||
"variant_group": archive_entry.get("variant_group"),
|
||||
}
|
||||
members = archive_members(archive_name, profile.get("files", []))
|
||||
if members:
|
||||
extra["members"] = members
|
||||
extra["claim_order"] = (3, 0, emu_name)
|
||||
if _take(extra, full_dest, seen_dests, taken):
|
||||
extras.append(extra)
|
||||
@@ -774,6 +778,8 @@ def _report_extra(u: dict, name: str, dest: str) -> dict:
|
||||
"region": u.get("region"),
|
||||
"variant_group": u.get("variant_group"),
|
||||
}
|
||||
if u.get("members"):
|
||||
extra["members"] = u["members"]
|
||||
# Keep every reproducible identity constraint carried by the source
|
||||
# profile. Some profiles expose CRC32/SHA-256 only through their
|
||||
# validation block; dropping those fields here would let a same-named
|
||||
|
||||
+4
-11
@@ -35,6 +35,8 @@ import slots
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from common import (
|
||||
archive_entry,
|
||||
archive_members,
|
||||
profiles_for_systems,
|
||||
PROFILE_IDENTITY_FIELDS,
|
||||
list_available_targets,
|
||||
@@ -577,6 +579,7 @@ def find_undeclared_files(
|
||||
"expected": {},
|
||||
"archive_file_count": 0,
|
||||
"archive_required_count": 0,
|
||||
"members": archive_members(archive, profile.get("files", [])),
|
||||
}
|
||||
entry = archive_entries[archive_key]
|
||||
entry["archive_file_count"] += 1
|
||||
@@ -1523,18 +1526,8 @@ def verify_emulator(
|
||||
archive = file_entry.get("archive")
|
||||
if archive and archive not in seen_archives:
|
||||
seen_archives.add(archive)
|
||||
# Prefer the profile's own entry for the archive (carries
|
||||
# hashes for exact resolution when several dumps share a name)
|
||||
archive_entry = next(
|
||||
(
|
||||
f
|
||||
for f in files
|
||||
if f.get("name") == archive and not f.get("archive")
|
||||
),
|
||||
{"name": archive},
|
||||
)
|
||||
local_path, resolve_status = resolve_local_file(
|
||||
archive_entry,
|
||||
archive_entry(archive, files),
|
||||
db,
|
||||
zip_contents,
|
||||
data_dir_registry=data_registry,
|
||||
|
||||
@@ -5,6 +5,7 @@ capping a real romset: the largest pack is already several gigabytes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
@@ -36,6 +37,26 @@ def check_inside_zip(container: str, file_name: str, expected_md5: str) -> str:
|
||||
except (zipfile.BadZipFile, OSError, KeyError):
|
||||
return "error"
|
||||
|
||||
@functools.lru_cache(maxsize=4096)
|
||||
def _member_crcs(path: str, mtime: float) -> frozenset[str]:
|
||||
try:
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
return frozenset(
|
||||
format(info.CRC, "08x") for info in archive.infolist() if not info.is_dir()
|
||||
)
|
||||
except (zipfile.BadZipFile, OSError):
|
||||
return frozenset()
|
||||
|
||||
|
||||
def zip_member_crcs(path: str) -> frozenset[str]:
|
||||
"""CRC-32 of each member, read from the central directory."""
|
||||
try:
|
||||
mtime = os.path.getmtime(path)
|
||||
except OSError:
|
||||
return frozenset()
|
||||
return _member_crcs(path, mtime)
|
||||
|
||||
|
||||
_zip_contents_cache: tuple[frozenset[tuple[str, float]], dict] | None = None
|
||||
|
||||
def build_zip_contents_index(db: dict, max_entry_size: int = 512 * 1024 * 1024) -> dict:
|
||||
|
||||
Reference in new issue
Block a user