fix: serve a data dir file at its platform path

AmberELEC and EmuELEC declare blueMSX's Machines/<model>/config.ini and
Shared Roms/RS232.ROM with no hash. The name step served msx-emu's
ColecoVision config.ini in 192 machine folders and the 16 KB RS232.ROM,
while data/bluemsx held blueMSX's own file at that very path. A platform
declaration with no hash now reads its destination's tail in the data
directories before the name step. Profile entries keep the foreign-tree
redirect, so Dolphin's dsp_coef.bin dump still outranks the free copy.

Manifests: amberelec and emuelec omit 192 config.ini entries (held in
data/bluemsx only, so the installer has no URL) and serve colecodb.xml,
MSX2+/config.ini, MSXturboR/config.ini and RS232.ROM from blueMSX's bytes.
This commit is contained in:
Abdessamad Derraz committed 2026-11-05 19:21:26 +01:00
1 parent c624a820f0
commit 3b0112001e
5 files changed
+7214 -4264

No files matched your search

+54
View File
@@ -9,6 +9,7 @@ from __future__ import annotations
from collections.abc import Callable, Iterable
from collections import Counter
import filecmp
import hashlib
import functools
import json
@@ -889,6 +890,38 @@ def _resolve_in_data_dirs(
return None, mismatch
def _data_dir_tail(
dest_hint: str, data_dir_registry: dict, file_entry: dict, db: dict
) -> str | None:
"""The cached data-directory file at the destination's own tail.
The full destination first, then without its leading segment, never
down to the bare name. A shortened tail two caches hold with different
bytes proves nothing. The size and Adler-32 the code checks still bound
the file, as on every name or path step without a hash.
"""
parts = dest_hint.split("/")
for start in range(min(2, len(parts) - 1)):
tail = "/".join(parts[start:])
hits = [
os.path.join(cache_dir, tail)
for cache_dir in (
dd_entry.get("local_cache", "") for dd_entry in data_dir_registry.values()
)
if cache_dir and os.path.isfile(os.path.join(cache_dir, tail))
]
if start and any(
not filecmp.cmp(hits[0], other, shallow=False) for other in hits[1:]
):
continue
for candidate in hits:
if name_match_size_ok(
file_entry, os.path.getsize(candidate)
) and adler32_fits(file_entry, candidate, db):
return candidate
return None
# Basename index per data-directory cache. Walking the whole tree again for
# every unresolved name made resolution O(names x tree) on each pack build.
_data_dir_index: dict[str, dict[str, list[tuple[int, str]]]] = {}
@@ -1504,6 +1537,27 @@ def resolve_local_file(
if status:
return path, status
# 2c. The same tail in a data directory. by_path_suffix indexes bios/
# only, so AmberELEC's Machines/MSX - Arabic/config.ini fell through to
# the name step and got msx-emu's ColecoVision config.ini, while
# data/bluemsx held blueMSX's own at that very path. A platform
# declaration is resolved without a hint, and its destination is that
# path. The descent and the ambiguity rule are those of the bios/ tails.
# A profile's entry is not asked here: it names its owner, the name step
# sends it to its data directories when another emulator's copy answers,
# and a dump it declares outranks the free one its tree bundles at the
# same path (Dolphin's dsp_coef.bin over Sys/GC's).
tail_hint = dest_hint or str(file_entry.get("destination") or "")
if (
tail_hint
and data_dir_registry
and not has_strong_hash
and not _owner_tokens(file_entry)
):
hit = _data_dir_tail(tail_hint, data_dir_registry, file_entry, db)
if hit:
return hit, "path_exact"
# 3. No MD5 = any file with that name or alias (existence check)
def _size_ok(match_sha1: str) -> bool:
record = files_db.get(match_sha1, {})