feat: let an archive join a set

Kronos reads kronos/stvbios.zip, else stvbios.zip at the root of the
system directory, and a ST-V game stops without either
(libretro.c:1608-1617, 1676-1681 at the pin). The archive under
archive_prefix and the root copy are one need, but only loose entries
could be members, so the required-only pack carried both.

An archive's own entry now joins a set like a loose file. verify
--emulator and the emulator pack settle archive units with the loose
entries, the required-only pick covers them, find_undeclared_files
and cross_reference record them, and the unit under archive_prefix
ranks before the same name at the root. Its members still settle the
archive by their contents. Kronos' stvbios.zip is grouped stv-bios.

No other profile reads one archive under two paths: odin and
tgpulse_next_m1 place their own entries at the prefix path, and
flycast's naomi.zip has one path per build.
This commit is contained in:
Abdessamad Derraz committed 2026-11-09 18:26:05 +01:00
1 parent ee98949d2c
commit 58735286a7
8 files changed
+201 -55

No files matched your search

+23 -12
View File
@@ -1786,6 +1786,16 @@ def generate_emulator_pack(
if archive:
archives.add(archive)
# What each destination came to, settled by set once the
# profile is read: a member another member fills is not missing,
# and a set nobody holds is reported once (alternatives.py). An
# archive unit is a member like a loose file.
outcomes: list[tuple[list[dict], bool, Callable[[], None] | None]] = []
spares = emulator_required_spares(
emu_name, profile, files, standalone, region_drops, db,
validation_index, zip_contents, data_registry,
) if required_only else set()
# Pack archives as units
archive_prefix = profile.get("archive_prefix", "")
for archive_name in sorted(archives):
@@ -1796,6 +1806,8 @@ def generate_emulator_pack(
own_entry = archive_entry(archive_name, files)
if required_only and archive_is_optional(own_entry, archive_name, files):
continue
if archive_dest in spares:
continue
local_path, status = resolve_file(
own_entry,
db,
@@ -1814,7 +1826,11 @@ def generate_emulator_pack(
if not (taken and local_path and _same_bytes(
local_path, packed_at.get(archive_dest.lower())
)):
report_missing(archive_name, archive_dest)
outcomes.append(([own_entry], False, functools.partial(
report_missing, archive_name, archive_dest
)))
else:
outcomes.append(([own_entry], True, None))
continue
if local_path and status not in ("not_found",):
if local_path.endswith(".zip"):
@@ -1828,35 +1844,30 @@ def generate_emulator_pack(
total_files += 1
if record is not None:
record.files.append(archive_dest)
outcomes.append(([own_entry], True, None))
else:
report_missing(archive_name, archive_dest)
outcomes.append(([own_entry], False, functools.partial(
report_missing, archive_name, archive_dest
)))
# Pack individual files (skip archive members). The entries of one
# profile on one destination are the releases the code accepts
# there (raze's seven SW.GRP): the first one the core accepts
# fills the slot, as verify --emulator credits it, and the slot
# has one outcome however many alternatives it lists.
spares = emulator_required_spares(
emu_name, profile, files, standalone, region_drops, db,
validation_index, zip_contents, data_registry,
) if required_only else set()
slots: dict[str, list[dict]] = {}
for fe in files:
if required_only and (fe.get("required") is False or id(fe) in spares):
if required_only and fe.get("required") is False:
continue
if fe.get("archive"):
continue
dest = _resolve_destination(fe, standalone)
if not dest:
if not dest or dest in spares:
continue
if region_drops and dest in region_drops:
continue
slots.setdefault(dest, []).append(fe)
# What each destination came to, settled by set once the loop
# is over: a member another member fills is not missing, and a
# set nobody holds is reported once (alternatives.py).
outcomes: list[tuple[list[dict], bool, Callable[[], None] | None]] = []
for dest, alternatives in slots.items():
# Name order, first file wins, case folded: verify replays
# the rule and reports a later profile's file as missing.