fix: count alias readers in a file's requirers

Ymir reads sega_101.bin or saturn_bios.bin, so the sega_101.bin entry
carries saturn_bios.bin as an alias. kronos, yabause and yabasanshiro
open saturn_bios.bin and fall back to sega_101.bin: they were in that
entry's _cores but not its _required_by, while their saturn_bios.bin
entry had them requiring it. A reader that requires one of an entry's
aliases now requires the entry. No reader is added, and same-name
entries kept apart by path are left alone. No export changes.
This commit is contained in:
Abdessamad Derraz committed 2026-10-21 06:18:32 +02:00
1 parent f19a8f99d3
commit 566d36275f
2 files changed
+78

No files matched your search

+33
View File
@@ -200,6 +200,36 @@ def _same_file(files: list[dict], file_entry: dict, emu_name: str) -> dict | Non
)
def _require_by_slot(files: list[dict]) -> None:
"""A core that requires a name an entry answers for requires the entry.
Merged by name, one core's alternatives land in several entries. Ymir
reads sega_101.bin, mpr-17933.bin or saturn_bios.bin, so the sega_101.bin
entry carries those aliases. kronos, yabause and yabasanshiro open
saturn_bios.bin first and sega_101.bin as a fallback: they sat in that
entry's _cores, out of its _required_by, while their saturn_bios.bin entry
had them requiring it, and a platform line paired through the alias read
them as optional. A core already among the entry's readers that requires
one of its aliases, as another entry of the system, is one of its
requirers. No reader is added.
"""
required_by_name: dict[str, set[str]] = {}
for entry in files:
required_by_name.setdefault(entry["name"].lower(), set()).update(
entry.get("_required_by", ())
)
for entry in files:
# Only the aliases: entries sharing the entry's own name are other
# files that name already kept apart (pak0.pak under each game).
names = {str(alias).lower() for alias in entry.get("aliases") or ()}
names.discard(entry["name"].lower())
requirers = set().union(*(required_by_name.get(n, set()) for n in names))
added = (requirers & entry.get("_cores", set())) - entry.get("_required_by", set())
if added:
entry["_required_by"] = entry.get("_required_by", set()) | added
entry["required"] = True
def _brought(file_entry: dict, emu_name: str) -> set[str]:
"""The core and build that need the file brought, empty when the
emulator installs it. An entry read without layer (a test, a caller of
@@ -666,6 +696,9 @@ def generate_platform_truth(
)
sys_cov["unprofiled"].add(emu_name)
for sys_data in systems.values():
_require_by_slot(sys_data.get("files", []))
# Drop files with no exploitable data AFTER all cores have contributed.
# A file declared by one core without hash/size/path may be enriched by
# another core that has the same entry with data, so the filter must run
+45
View File
@@ -246,6 +246,51 @@ class ACompositeZipMd5IsNoContainerMd5(unittest.TestCase):
self.assertEqual([m["name"] for m in result["hash_mismatch"]], ["neocdz.zip"])
class OneSlotUnderSeveralNames(unittest.TestCase):
"""kronos opens saturn_bios.bin, then sega_101.bin as a fallback. Ymir
reads either name, so the sega_101.bin entry answers for saturn_bios.bin
too. kronos sat in that entry's _cores and out of its _required_by while
its saturn_bios.bin entry had it requiring the file."""
PROFILES = {
"kronos": {
"emulator": "Kronos", "type": "libretro", "systems": ["sega-saturn"],
"files": [
{"name": "saturn_bios.bin", "path": "kronos/saturn_bios.bin",
"required": True, "size": 524288},
{"name": "sega_101.bin", "required": False, "size": 524288},
],
},
"ymir": {
"emulator": "Ymir", "type": "libretro", "systems": ["sega-saturn"],
"files": [
{"name": "sega_101.bin", "required": True, "size": 524288,
"aliases": ["saturn_bios.bin"]},
],
},
"other": {
"emulator": "Other", "type": "libretro", "systems": ["sega-saturn"],
"files": [{"name": "saturn_bios.bin", "required": True, "size": 524288}],
},
}
def _files(self) -> dict[str, dict]:
cores = ["kronos", "ymir", "other"]
model = generate_platform_truth("p", {"cores": cores}, {}, self.PROFILES)
return {f["name"]: f for f in model["systems"]["sega-saturn"]["files"]}
def test_a_reader_requiring_an_alias_requires_the_entry(self):
sega = self._files()["sega_101.bin"]
self.assertEqual(sega["_cores"], ["kronos", "ymir"])
self.assertEqual(sega["_required_by"], ["kronos", "ymir"])
def test_no_reader_is_added(self):
"""other requires saturn_bios.bin but never opens sega_101.bin."""
sega = self._files()["sega_101.bin"]
self.assertNotIn("other", sega["_required_by"])
self.assertTrue(set(sega["_required_by"]) <= set(sega["_cores"]))
class OnlySystemDirectoryEntries(unittest.TestCase):
"""The truth kept files read from the save, content or working directory:
diff_truth listed Cemu's mlc01 certificates, panda3ds's seeddb.bin and