mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-11 05:53:23 -05:00
117 lines
4.4 KiB
Python
117 lines
4.4 KiB
Python
"""One file per slot: keep the BIOS a core would actually load.
|
|
|
|
A slot is a (system, region) pair holding several interchangeable BIOS. Region
|
|
filtering narrows a pack to one territory; a system can still ship three US
|
|
PlayStation BIOS that differ only by revision, which is what leaves the choice
|
|
open in the frontend.
|
|
|
|
The winner is only ever taken from an ordered search list the core's code
|
|
actually walks, recorded as `search_rank:` on the file entry: rank 1 is tried
|
|
first, and the first file found wins. PicoDrive's biosfiles_us/eu/jp arrays are
|
|
the shape this describes.
|
|
|
|
`priority:` is deliberately not read. Its meaning is disputed: the field
|
|
reference calls it a tie-breaker where higher wins, while DuckStation's own
|
|
selection compares the numbers the other way, and its values rank PS2 images
|
|
above the plain PlayStation BIOS. Selecting on it dropped scph5501, the very
|
|
file most US setups load.
|
|
|
|
Where no ordered list is declared, the group is reported undecidable and every
|
|
candidate is kept: picking one would be the arbitrary selection this exists to
|
|
remove, and it could discard the file the core would have loaded.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
BIOS_CATEGORY = "bios"
|
|
|
|
|
|
def build_slot_index(profiles: dict) -> dict[str, dict]:
|
|
"""Map a file key to its slot metadata, keyed by path then name."""
|
|
index: dict[str, dict] = {}
|
|
for emu_name, profile in sorted(profiles.items()):
|
|
if profile.get("type") in ("launcher", "alias"):
|
|
continue
|
|
for f in profile.get("files") or []:
|
|
if not isinstance(f, dict):
|
|
continue
|
|
if f.get("category", BIOS_CATEGORY) != BIOS_CATEGORY:
|
|
continue
|
|
name = f.get("name", "")
|
|
path = f.get("path") or ""
|
|
for key in {path, name} - {""}:
|
|
entry = index.setdefault(
|
|
key, {"rank": None, "emulators": []}
|
|
)
|
|
rank = f.get("search_rank")
|
|
if rank is not None:
|
|
current = entry["rank"]
|
|
entry["rank"] = (
|
|
rank if current is None else min(current, rank)
|
|
)
|
|
if emu_name not in entry["emulators"]:
|
|
entry["emulators"].append(emu_name)
|
|
return index
|
|
|
|
|
|
def lookup_slot(index: dict[str, dict], destination: str, name: str) -> dict | None:
|
|
"""Look a candidate up, trying the destination then its suffixes then name."""
|
|
if destination:
|
|
entry = index.get(destination)
|
|
if entry:
|
|
return entry
|
|
parts = destination.split("/")
|
|
for i in range(1, len(parts)):
|
|
entry = index.get("/".join(parts[i:]))
|
|
if entry:
|
|
return entry
|
|
return index.get(name)
|
|
|
|
|
|
def resolve_slot_drops(
|
|
groups: dict[str, list[tuple[str, str]]],
|
|
index: dict[str, dict],
|
|
) -> tuple[set[str], list[str]]:
|
|
"""Destinations to skip, and the groups no declared order can decide.
|
|
|
|
Returns (drops, undecidable). A group is decided only when every candidate
|
|
declares a search rank and exactly one holds the first rank; anything else
|
|
keeps every candidate.
|
|
"""
|
|
keep: set[str] = set()
|
|
drop: set[str] = set()
|
|
undecidable: list[str] = []
|
|
|
|
for group_id, members in groups.items():
|
|
candidates: list[tuple[str, int | None]] = []
|
|
for destination, name in members:
|
|
entry = lookup_slot(index, destination, name)
|
|
if entry is None:
|
|
keep.add(destination)
|
|
continue
|
|
candidates.append((destination, entry["rank"]))
|
|
|
|
if len(candidates) < 2:
|
|
keep.update(dest for dest, _p in candidates)
|
|
continue
|
|
|
|
# Every candidate must carry a rank: a set where some members are
|
|
# unranked cannot be ordered, and dropping the unranked ones would
|
|
# discard exactly the file the core may load.
|
|
if any(p is None for _d, p in candidates):
|
|
undecidable.append(group_id)
|
|
keep.update(dest for dest, _p in candidates)
|
|
continue
|
|
|
|
best = min(p for _d, p in candidates)
|
|
winners = [dest for dest, p in candidates if p == best]
|
|
if len(winners) != 1:
|
|
undecidable.append(group_id)
|
|
keep.update(dest for dest, _p in candidates)
|
|
continue
|
|
|
|
keep.add(winners[0])
|
|
drop.update(dest for dest, _p in candidates if dest != winners[0])
|
|
|
|
return drop - keep, sorted(undecidable)
|