fix: another tree's casing does not hold a file

dosbox-x's et4000.bin matched only pcem's et4000.BIN. resolve_local_file, asked as dosbox-x, refuses another emulator's tree in its case-insensitive step, so the pack and installer leave it out, while the gap report and verify's core files resolved without the asker and counted it held. Both now resolve as the profile, and the manifests of Batocera and KNULLI no longer flag it held.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 08:36:42 +01:00
1 parent 21dd14c851
commit 4377cbb2af
6 files changed
+85 -16

No files matched your search

+1 -2
View File
@@ -1841,8 +1841,7 @@
"reason": "not_found",
"cores": [
"dosbox-x"
],
"held": true
]
},
{
"dest": "extras.wad",
+1 -2
View File
@@ -1598,8 +1598,7 @@
"reason": "not_found",
"cores": [
"dosbox-x"
],
"held": true
]
},
{
"dest": "extras.wad",
+22 -4
View File
@@ -26,7 +26,9 @@ sys.path.insert(0, os.path.dirname(__file__))
from common import (
platform_arg,
ProfileSelectionError,
_foreign_tree,
_norm_system_id,
_owner_tokens,
archive_entry,
directory_members,
expand_directory_entries,
@@ -215,8 +217,14 @@ def _resolve_source(
none of the hashes the entry declares: a name never wins over a declared
hash, and zquestclassic's ending.mid read as held because Hydra Castle
Labyrinth has another file of that name.
Another casing of the name in another emulator's tree proves nothing, as
in resolve_local_file's case-insensitive step: dosbox-x's et4000.bin read
as held through pcem's et4000.BIN, which the pack and the installer
refuse it.
"""
declared = _declared_content(file_entry or {})
owners = _owner_tokens(file_entry or {})
def _can_be(sha1s: list | str) -> bool:
if not file_entry or db_files is None:
@@ -233,6 +241,14 @@ def _resolve_source(
"""Whether the files indexed under *name* can be this entry."""
return _can_be(by_name.get(name) or [])
def _folded_hit(name: str) -> bool:
"""_name_hit for a name matched in another casing."""
return _can_be([
sha1 for sha1 in by_name.get(name) or []
if db_files is None
or not _foreign_tree(db_files.get(sha1, {}).get("path", ""), owners)
])
# bios/ via database.json by_name
if fname in by_name and _name_hit(fname):
return "bios"
@@ -241,10 +257,10 @@ def _resolve_source(
if basename and basename in by_name and _name_hit(basename):
return "bios"
key = fname.lower()
if key in by_name_lower and _name_hit(by_name_lower[key]):
if key in by_name_lower and _folded_hit(by_name_lower[key]):
return "bios"
if basename:
if basename.lower() in by_name_lower and _name_hit(
if basename.lower() in by_name_lower and _folded_hit(
by_name_lower[basename.lower()]
):
return "bios"
@@ -257,7 +273,7 @@ def _resolve_source(
if canonical and canonical != fname:
if canonical in by_name and _name_hit(canonical):
return "bios"
if canonical.lower() in by_name_lower and _name_hit(
if canonical.lower() in by_name_lower and _folded_hit(
by_name_lower[canonical.lower()]
):
return "bios"
@@ -901,7 +917,9 @@ def _cross_reference_profile(
source = next(
(
found for version in versions[slot]
if (found := entry_source(version, index)) is not None
if (found := entry_source(
{**version, "source_profile": emu_name}, index,
)) is not None
),
"missing",
)
+3 -3
View File
@@ -9416,7 +9416,7 @@ def _hash_spans(value: str | list, copy: bool = False) -> str:
def _availability_check(
db: dict, data_names: set[str] | None
db: dict, data_names: set[str] | None, owner: str = "",
) -> Callable[[dict], bool]:
"""A predicate answering whether the collection holds a file.
@@ -9447,7 +9447,7 @@ def _availability_check(
# under the name belongs to another game or release.
if not f.get("name") or f.get("unsourceable"):
return False
return entry_source(f, index) is not None
return entry_source({**f, "source_profile": owner} if owner else f, index) is not None
return _file_available
@@ -10180,7 +10180,7 @@ def emulator_pages(
lines.append("")
lines.extend(markup.aside("info", "Why no files", [_escape_tags(str(exclusion))])[:-1])
else:
_file_available = _availability_check(db, data_names)
_file_available = _availability_check(db, data_names, name)
lines.extend(["## Files", ""])
lines.extend(_emulator_file_summary(files))
+10 -5
View File
@@ -667,13 +667,18 @@ def find_undeclared_files(
# Resolution: name, then path basename
# storage: release names where the bytes are served from, not
# that the collection holds them: resolved like any file.
# Resolved as the asker, as the builder's ExtraResolver does:
# another casing of the name in another emulator's tree is
# not this emulator's file (dosbox-x's et4000.bin and pcem's
# et4000.BIN).
owned = {**f, "source_profile": emu_name}
if shipped_on_mismatch and (f.get("md5") or f.get("sha1")):
# The entry states what its content should be and the builder
# drops a copy that contradicts it, so content decides here
# too. A name match would not do: generic names collide across
# systems, and answering yes on one describes a pack that will
# not contain the file.
_lp, _st = resolve_local_file(f, db, dest_hint=dest)
_lp, _st = resolve_local_file(owned, db, dest_hint=dest)
in_repo = _lp is not None and _st != "hash_mismatch"
else:
in_repo = _name_in_index(
@@ -693,8 +698,8 @@ def find_undeclared_files(
# rejects them all. NanoBoyAdvance's 16 KB bios.bin met
# only PCem's and QEMU's PC BIOS images of that name.
# The fallback below then decides as for any mismatch.
_lp, _st = resolve_local_file(f, db, dest_hint=dest)
if _st == "hash_mismatch":
_lp, _st = resolve_local_file(owned, db, dest_hint=dest)
if _st == "hash_mismatch" or _lp is None:
in_repo = False
if not in_repo:
# Hash fallback: the repo may hold the content under a
@@ -707,7 +712,7 @@ def find_undeclared_files(
# frontend would reject it, the builder leaves it out, and
# calling it available here would describe a pack that
# does not contain it.
_lp, _st = resolve_local_file(f, db, dest_hint=dest)
_lp, _st = resolve_local_file(owned, db, dest_hint=dest)
in_repo = _st != "not_found" and _lp is not None
if in_repo and _st == "hash_mismatch" and shipped_on_mismatch:
in_repo = False
@@ -717,7 +722,7 @@ def find_undeclared_files(
# held for it, the rule the builder's lookup applies
# (packextras.ExtraResolver). The Apple IIGS' 128 KB
# ROM is not Basilisk II's ROM.
_lp, _st = resolve_local_file(f, db, dest_hint=dest)
_lp, _st = resolve_local_file(owned, db, dest_hint=dest)
if core_size_refusal(
f, _lp, same_slot_entries(profile.get("files"), fname, dest)
):
+48
View File
@@ -185,5 +185,53 @@ class RetailDashboardTree(unittest.TestCase):
self.assertEqual(status, "path_exact", path)
class TheGapReportAsksAsTheEmulator(unittest.TestCase):
"""dosbox-x's et4000.bin, no hash: pcem's et4000.BIN was its only
casing. The resolver, asked as dosbox-x, refuses it and the pack and the
installer leave the file out, while the gap report, verify's core files
and the manifest's held flag counted it available."""
_hold = ForeignTreeNames._hold
def setUp(self):
ForeignTreeNames.setUp(self)
self._hold("bios/Other/pcem/roms/et4000.BIN", b"E" * 64)
self.entry = {"name": "et4000.bin", "required": True}
def _source(self, owner: str) -> str | None:
from cross_reference import entry_source
by_name = self.db["indexes"]["by_name"]
index = {
"by_name": by_name, "by_name_lower": {k.lower(): k for k in by_name},
"by_path_suffix": {}, "by_md5": {}, "by_sha256": {}, "by_crc32": {},
"db_files": self.db["files"], "data_names": None,
}
return entry_source({**self.entry, "source_profile": owner}, index)
def test_another_tree_casing_is_not_held(self):
self.assertIsNone(self._source("dosbox-x"))
entry = {**self.entry, "source_profile": "dosbox-x"}
self.assertEqual(resolve_local_file(entry, self.db), (None, "not_found"))
def test_the_tree_owner_holds_it(self):
self.assertEqual(self._source("pcem"), "bios")
def test_core_files_follow_the_resolver(self):
from verify import find_undeclared_files
profiles = {"dosbox-x": {
"emulator": "DOSBox-X", "type": "libretro", "systems": ["dos"],
"files": [self.entry],
}}
config = {"platform": "P", "verification_mode": "md5", "cores": ["dosbox-x"],
"systems": {"dos": {"files": []}}}
(found,) = [
u for u in find_undeclared_files(config, "emulators", self.db, profiles)
if u["name"] == "et4000.bin"
]
self.assertFalse(found["in_repo"])
if __name__ == "__main__":
unittest.main()