From 533dd7586f67e8298e0efc282f604fce2538d49b Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:55:31 +0200 Subject: [PATCH] fix: scan free bios names only in the bios folder --- scripts/packextras.py | 20 +++++++- tests/test_agnostic_scope.py | 97 ++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 tests/test_agnostic_scope.py diff --git a/scripts/packextras.py b/scripts/packextras.py index f603c0f4..1e4d1f86 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -139,6 +139,7 @@ def _agnostic_scan_extras( by_name: dict, seen_dests: set, extras_prefix: str, + standalone_set: set[str] | None = None, ) -> list[dict]: """Every interchangeable candidate a filename-agnostic core accepts. @@ -203,6 +204,14 @@ def _agnostic_scan_extras( continue if not (f.get("size") or f.get("min_size") or f.get("max_size")): continue + # The free name is the BIOS directory's: a shader, a sound or a + # font the emulator reads from its resources tree has a fixed + # name and must not seed a scan (yaps2 flattened 189 of them to + # the root of the RetroArch pack). + bios_dir = profile.get("bios_directory") + fpath = f.get("path") or "" + if bios_dir and "/" in fpath and not fpath.startswith(bios_dir): + continue path_prefix = resolved_dirs.get(id(f), "") if not path_prefix: @@ -224,7 +233,13 @@ def _agnostic_scan_extras( scan_name = entry.get("name", "") if not scan_name: continue - dest = scan_name + # Into the directory the seed entry names, not the root: + # yaps2 reads its BIOS from pcsx2/bios/. + seed = f.get("path") or "" + if runs_standalone(emu_name, profile, standalone_set or set()): + seed = f.get("standalone_path") or seed + folder = seed.rsplit("/", 1)[0] if "/" in seed else "" + dest = f"{folder}/{scan_name}" if folder else scan_name full_dest = f"{extras_prefix}/{dest}" if extras_prefix else dest if full_dest in seen_dests: continue @@ -526,7 +541,8 @@ def _collect_emulator_extras( extras.extend( _agnostic_scan_extras( - profiles, relevant, db, by_name, seen_dests, extras_prefix + profiles, relevant, db, by_name, seen_dests, extras_prefix, + standalone_set, ) ) diff --git a/tests/test_agnostic_scope.py b/tests/test_agnostic_scope.py new file mode 100644 index 00000000..06e1d891 --- /dev/null +++ b/tests/test_agnostic_scope.py @@ -0,0 +1,97 @@ +"""What a filename-agnostic core's scan may emit, and where. + +yaps2 declares its BIOS under pcsx2/bios/ with a free name and its shaders, +sounds and fonts under pcsx2/resources/ with fixed names. Every sized entry +seeded the scan and every find went to the pack root: 189 resources landed +flat in RetroArch's system/, and the BIOS the core reads from pcsx2/bios/ +was not where it looks. +""" + +from __future__ import annotations + +import hashlib +import os +import sys +import tempfile +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts")) + + +class AgnosticScan(unittest.TestCase): + def test_only_the_bios_directory_seeds_and_finds_keep_its_folder(self): + from packextras import _agnostic_scan_extras + + with tempfile.TemporaryDirectory() as tmp: + previous = os.getcwd() + os.chdir(tmp) + self.addCleanup(os.chdir, previous) + files, by_name = {}, {} + for path, data in ( + ("bios/PS2/scph1.bin", b"a" * 64), + ("bios/PS2/scph2.bin", b"b" * 64), + ("bios/PS2/res/message.wav", b"c" * 8), + ("bios/PS2/res/other.wav", b"d" * 8), + ): + Path(path).parent.mkdir(parents=True, exist_ok=True) + Path(path).write_bytes(data) + sha1 = hashlib.sha1(data).hexdigest() + name = path.rsplit("/", 1)[1] + files[sha1] = {"path": path, "name": name, "size": len(data), "sha1": sha1} + by_name.setdefault(name, []).append(sha1) + db = {"files": files, "indexes": {"by_name": by_name, "by_md5": {}, "by_crc32": {}, + "by_path_suffix": {}}} + profile = {"emulator": "Y", "type": "libretro", "bios_mode": "agnostic", + "bios_directory": "pcsx2/bios/", "systems": ["ps2"], "files": [ + {"name": "scph1.bin", "path": "pcsx2/bios/scph1.bin", "size": 64}, + {"name": "message.wav", "path": "pcsx2/resources/message.wav", "size": 8}, + ]} + extras = _agnostic_scan_extras({"y": profile}, {"y"}, db, by_name, set(), "") + self.assertEqual(sorted(e["destination"] for e in extras), + ["pcsx2/bios/scph1.bin", "pcsx2/bios/scph2.bin"]) + + +class BundledHashIsProof(unittest.TestCase): + """A bundled file with a declared hash is served by those bytes or flagged. + + Four NetherSX2-Turnip entries named their own sha1; the collection held + NetherSX2's copies, which the builder shipped in their place. + """ + + def test_no_bundled_entry_resolves_to_other_bytes(self): + if not (REPO_ROOT / "database.json").is_file(): + self.skipTest("no database.json") + from common import ( + build_zip_contents_index, + load_data_dir_registry, + load_database, + load_emulator_profiles, + resolve_local_file, + ) + + previous = os.getcwd() + os.chdir(REPO_ROOT) + self.addCleanup(os.chdir, previous) + db = load_database("database.json") + zips = build_zip_contents_index(db) + registry = load_data_dir_registry("platforms") + wrong = [] + for name, profile in sorted(load_emulator_profiles("emulators", skip_aliases=False).items()): + for entry in profile.get("files") or []: + if not entry.get("bundled") or entry.get("unsourceable"): + continue + if not any(entry.get(k) for k in ("sha1", "md5", "sha256", "crc32")): + continue + dest = entry.get("path") or entry.get("name") or "" + local, status = resolve_local_file( + {**entry, "source_profile": name}, db, zips, dest_hint=dest, + data_dir_registry=registry) + if local and status == "hash_mismatch": + wrong.append((name, dest, local)) + self.assertEqual(wrong, []) + + +if __name__ == "__main__": + unittest.main()