feat: aliases support in resolve and db generation

generate_db.py now reads aliases from emulator YAMLs and indexes
them in database.json by_name. resolve_local_file in common.py
tries all alias names when the primary name fails to match.

beetle_psx alt_names renamed to aliases (was not indexed before).
snes9x BS-X.bios, np2kai FONT.ROM/ide.rom/pci.rom fallbacks,
all now formally declared as aliases and indexed.

verify --all and generate_pack --all pass with 0 regressions.
This commit is contained in:
Abdessamad Derraz
2026-03-19 08:15:13 +01:00
parent 71b127efb5
commit f3db61162c
3 changed files with 243 additions and 14 deletions

View File

@@ -153,6 +153,8 @@ def resolve_local_file(
md5_raw = file_entry.get("md5", "")
name = file_entry.get("name", "")
zipped_file = file_entry.get("zipped_file")
aliases = file_entry.get("aliases", [])
names_to_try = [name] + [a for a in aliases if a != name]
md5_list = [m.strip().lower() for m in md5_raw.split(",") if m.strip()] if md5_raw else []
files_db = db.get("files", {})
@@ -180,14 +182,15 @@ def resolve_local_file(
if os.path.exists(path):
return path, "md5_exact"
# 3. No MD5 = any file with that name (existence check)
# 3. No MD5 = any file with that name or alias (existence check)
if not md5_list:
candidates = []
for match_sha1 in by_name.get(name, []):
if match_sha1 in files_db:
path = files_db[match_sha1]["path"]
if os.path.exists(path):
candidates.append(path)
for try_name in names_to_try:
for match_sha1 in by_name.get(try_name, []):
if match_sha1 in files_db:
path = files_db[match_sha1]["path"]
if os.path.exists(path) and path not in candidates:
candidates.append(path)
if candidates:
if zipped_file:
candidates = [p for p in candidates if ".zip" in os.path.basename(p)]
@@ -195,15 +198,18 @@ def resolve_local_file(
if primary or candidates:
return (primary[0] if primary else candidates[0]), "exact"
# 5. Name fallback with md5_composite + direct MD5 per candidate
# 5. Name + alias fallback with md5_composite + direct MD5 per candidate
md5_set = set(md5_list)
candidates = []
for match_sha1 in by_name.get(name, []):
if match_sha1 in files_db:
entry = files_db[match_sha1]
path = entry["path"]
if os.path.exists(path):
candidates.append((path, entry.get("md5", "")))
seen_paths = set()
for try_name in names_to_try:
for match_sha1 in by_name.get(try_name, []):
if match_sha1 in files_db:
entry = files_db[match_sha1]
path = entry["path"]
if os.path.exists(path) and path not in seen_paths:
seen_paths.add(path)
candidates.append((path, entry.get("md5", "")))
if candidates:
if zipped_file:

View File

@@ -277,6 +277,37 @@ def _collect_all_aliases(files: dict) -> dict:
except (ImportError, ConnectionError, OSError):
pass
# Collect aliases from emulator YAMLs (aliases field on file entries)
emulators_dir = Path("emulators")
if emulators_dir.is_dir():
try:
import yaml
for emu_file in emulators_dir.glob("*.yml"):
try:
with open(emu_file) as f:
emu_config = yaml.safe_load(f) or {}
except (yaml.YAMLError, OSError):
continue
for file_entry in emu_config.get("files", []):
entry_aliases = file_entry.get("aliases", [])
if not entry_aliases:
continue
entry_name = file_entry.get("name", "")
sha1 = file_entry.get("sha1", "")
md5 = file_entry.get("md5", "")
matched = None
if sha1 and sha1 in files:
matched = sha1
elif md5 and md5 in md5_to_sha1:
matched = md5_to_sha1[md5]
elif entry_name and entry_name in name_to_sha1:
matched = name_to_sha1[entry_name]
if matched:
for alias_name in entry_aliases:
_add_alias(alias_name, matched)
except ImportError:
pass
# Identical content named differently across platforms/cores
KNOWN_ALIAS_GROUPS = [
# ColecoVision - all these are the same 8KB BIOS