fix: export only what the platform declares

This commit is contained in:
Abdessamad Derraz committed 2026-10-10 11:15:25 +02:00
1 parent b5f9095633
commit e26ee44229
9 files changed
+145 -5

No files matched your search

+14
View File
@@ -384,6 +384,7 @@ systems:
- name: qsound.zip - name: qsound.zip
destination: qsound.zip destination: qsound.zip
required: true required: true
curated: true
native_id: Arcade native_id: Arcade
core: fbneo core: fbneo
manufacturer: Various manufacturer: Various
@@ -1907,24 +1908,31 @@ systems:
- name: gc-ntsc-12.bin - name: gc-ntsc-12.bin
destination: dolphin-emu/Sys/GC/USA/IPL.bin destination: dolphin-emu/Sys/GC/USA/IPL.bin
required: false required: false
curated: true
- name: gc-pal-12.bin - name: gc-pal-12.bin
destination: dolphin-emu/Sys/GC/EUR/IPL.bin destination: dolphin-emu/Sys/GC/EUR/IPL.bin
required: false required: false
curated: true
- name: gc-ntsc-12.bin - name: gc-ntsc-12.bin
destination: dolphin-emu/Sys/GC/JAP/IPL.bin destination: dolphin-emu/Sys/GC/JAP/IPL.bin
required: false required: false
curated: true
- name: dsp_coef.bin - name: dsp_coef.bin
destination: dolphin-emu/Sys/GC/dsp_coef.bin destination: dolphin-emu/Sys/GC/dsp_coef.bin
required: true required: true
curated: true
- name: dsp_rom.bin - name: dsp_rom.bin
destination: dolphin-emu/Sys/GC/dsp_rom.bin destination: dolphin-emu/Sys/GC/dsp_rom.bin
required: true required: true
curated: true
- name: font_western.bin - name: font_western.bin
destination: dolphin-emu/Sys/GC/font_western.bin destination: dolphin-emu/Sys/GC/font_western.bin
required: false required: false
curated: true
- name: font_japanese.bin - name: font_japanese.bin
destination: dolphin-emu/Sys/GC/font_japanese.bin destination: dolphin-emu/Sys/GC/font_japanese.bin
required: false required: false
curated: true
native_id: Nintendo - GameCube native_id: Nintendo - GameCube
core: dolphin core: dolphin
manufacturer: Nintendo manufacturer: Nintendo
@@ -1997,15 +2005,19 @@ systems:
- name: dsi_bios7.bin - name: dsi_bios7.bin
destination: dsi_bios7.bin destination: dsi_bios7.bin
required: true required: true
curated: true
- name: dsi_bios9.bin - name: dsi_bios9.bin
destination: dsi_bios9.bin destination: dsi_bios9.bin
required: true required: true
curated: true
- name: dsi_firmware.bin - name: dsi_firmware.bin
destination: dsi_firmware.bin destination: dsi_firmware.bin
required: true required: true
curated: true
- name: dsi_nand.bin - name: dsi_nand.bin
destination: dsi_nand.bin destination: dsi_nand.bin
required: true required: true
curated: true
native_id: Nintendo - Nintendo DS native_id: Nintendo - Nintendo DS
core: desmume core: desmume
manufacturer: Nintendo manufacturer: Nintendo
@@ -2554,6 +2566,7 @@ systems:
- name: segasp.zip - name: segasp.zip
destination: dc/segasp.zip destination: dc/segasp.zip
required: true required: true
curated: true
native_id: Sega - Dreamcast-based Arcade native_id: Sega - Dreamcast-based Arcade
sega-game-gear: sega-game-gear:
files: files:
@@ -4731,6 +4744,7 @@ systems:
- name: data.zip - name: data.zip
destination: xrick/data.zip destination: xrick/data.zip
required: true required: true
curated: true
core: xrick core: xrick
manufacturer: Other manufacturer: Other
docs: "https://docs.libretro.com/library/xrick/" docs: "https://docs.libretro.com/library/xrick/"
+7
View File
@@ -787,6 +787,7 @@ systems:
crc32: 51a12d90 crc32: 51a12d90
size: 32768 size: 32768
native_system: turbografx-cd native_system: turbografx-cd
mirror_of: tg16
- name: gexpress.pce - name: gexpress.pce
destination: turbografx-cd/gexpress.pce destination: turbografx-cd/gexpress.pce
required: true required: true
@@ -795,6 +796,7 @@ systems:
crc32: 51a12d90 crc32: 51a12d90
size: 32768 size: 32768
native_system: turbografx-cd native_system: turbografx-cd
mirror_of: tg16
- name: syscard1.pce - name: syscard1.pce
destination: turbografx-cd/syscard1.pce destination: turbografx-cd/syscard1.pce
required: true required: true
@@ -803,6 +805,7 @@ systems:
crc32: 3f9f95a4 crc32: 3f9f95a4
size: 262144 size: 262144
native_system: turbografx-cd native_system: turbografx-cd
mirror_of: tg16
- name: syscard2.pce - name: syscard2.pce
destination: turbografx-cd/syscard2.pce destination: turbografx-cd/syscard2.pce
required: true required: true
@@ -811,6 +814,7 @@ systems:
crc32: 283b74e0 crc32: 283b74e0
size: 262144 size: 262144
native_system: turbografx-cd native_system: turbografx-cd
mirror_of: tg16
- name: syscard2u.pce - name: syscard2u.pce
destination: turbografx-cd/syscard2u.pce destination: turbografx-cd/syscard2u.pce
required: true required: true
@@ -819,6 +823,7 @@ systems:
crc32: ff2a5ec3 crc32: ff2a5ec3
size: 262144 size: 262144
native_system: turbografx-cd native_system: turbografx-cd
mirror_of: tg16
- name: syscard3.pce - name: syscard3.pce
destination: turbografx-cd/syscard3.pce destination: turbografx-cd/syscard3.pce
required: true required: true
@@ -827,6 +832,7 @@ systems:
crc32: 6d9a73ef crc32: 6d9a73ef
size: 262144 size: 262144
native_system: turbografx-cd native_system: turbografx-cd
mirror_of: tg16
- name: syscard3u.pce - name: syscard3u.pce
destination: turbografx-cd/syscard3u.pce destination: turbografx-cd/syscard3u.pce
required: true required: true
@@ -835,6 +841,7 @@ systems:
crc32: 2b5b75fe crc32: 2b5b75fe
size: 262144 size: 262144
native_system: turbografx-cd native_system: turbografx-cd
mirror_of: tg16
nec-pc-98: nec-pc-98:
files: files:
- name: 2608_bd.wav - name: 2608_bd.wav
+8
View File
@@ -280,6 +280,14 @@
"type": "string", "type": "string",
"description": "System id the platform itself uses for this file" "description": "System id the platform itself uses for this file"
}, },
"mirror_of": {
"type": "string",
"description": "Native system whose entry this one repeats; the platform's own file does not carry the copy"
},
"curated": {
"type": "boolean",
"description": "Added by our scraper from the cores' source, not declared in the platform's own file"
},
"native_name": { "native_name": {
"type": "string", "type": "string",
"description": "Display name the platform gives its own system" "description": "Display name the platform gives its own system"
+5 -1
View File
@@ -159,7 +159,11 @@ def load_platform_config(platform_name: str, platforms_dir: str = "platforms") -
key = (gf.get("name"), gf.get("destination", gf.get("name"))) key = (gf.get("name"), gf.get("destination", gf.get("name")))
dest_lower = gf.get("destination", gf.get("name", "")).lower() dest_lower = gf.get("destination", gf.get("name", "")).lower()
if key not in existing and dest_lower not in existing_lower: if key not in existing and dest_lower not in existing_lower:
system.setdefault("files", []).append(gf) # Marked as ours: the platform's own file does not
# carry it, and an export must not count it kept.
system.setdefault("files", []).append(
{**gf, "shared_group": group_name}
)
existing.add(key) existing.add(key)
# Merge metadata from _registry.yml. The registry is our curated source; # Merge metadata from _registry.yml. The registry is our curated source;
+28 -1
View File
@@ -205,7 +205,34 @@ def load_inputs(
scraped = load_platform_config(platform, platforms_dir) scraped = load_platform_config(platform, platforms_dir)
except (FileNotFoundError, OSError): except (FileNotFoundError, OSError):
scraped = None scraped = None
return truth, scraped return truth, _native_layer(scraped)
def _native_layer(config: dict | None) -> dict | None:
"""The platform config without what we add to it.
A shared group's files join the config for the pack, but the platform's
file does not carry them: counted kept, they were written into
libretro's System.dat (N88EXT0-3.ROM) as if libretro declared them. What
a core needs from a group reaches the export through the truth, as an
addition the format's gates apply to. A scraper's mirror of an entry
under another system (RomM's tg16 firmware under turbografx-cd), and a
file our scraper adds from the cores' source (Dolphin's DSP ROMs in
RetroArch's config), are not in the platform's file either.
"""
if config is None:
return None
systems = {
sys_id: {
**system,
"files": [
f for f in system.get("files", [])
if not ({"shared_group", "mirror_of", "curated"} & f.keys())
],
}
for sys_id, system in config.get("systems", {}).items()
}
return {**config, "systems": systems}
def refresh_cache( def refresh_cache(
+5
View File
@@ -35,6 +35,9 @@ class BiosRequirement:
# is not always derivable from ours: libretro writes iplromco.dat bare # is not always derivable from ours: libretro writes iplromco.dat bare
# but ep128emu/roms/cpc464.rom with its directory. # but ep128emu/roms/cpc464.rom with its directory.
native_path: str | None = None native_path: str | None = None
# The native system whose entry this one repeats under another system.
# The platform's own file does not carry the copy: an export skips it.
mirror_of: str | None = None
# Fields the platform declares that have no equivalent in our model. # Fields the platform declares that have no equivalent in our model.
# Kept verbatim so the native file can be written back unchanged. # Kept verbatim so the native file can be written back unchanged.
native: dict[str, object] = field(default_factory=dict) native: dict[str, object] = field(default_factory=dict)
@@ -95,6 +98,8 @@ def requirement_entry(req: BiosRequirement) -> dict:
entry["native_system"] = req.native_id entry["native_system"] = req.native_id
if req.native_path and req.native_path != req.name: if req.native_path and req.native_path != req.name:
entry["native_path"] = req.native_path entry["native_path"] = req.native_path
if req.mirror_of:
entry["mirror_of"] = req.mirror_of
for key in sorted(req.native): for key in sorted(req.native):
value = req.native[key] value = req.native[key]
if value not in (None, "", [], {}): if value not in (None, "", [], {}):
+9 -3
View File
@@ -279,9 +279,14 @@ class Scraper(BaseScraper):
"docs": "https://docs.libretro.com/library/xrick/", "docs": "https://docs.libretro.com/library/xrick/",
}, },
} }
# Every entry below is ours, not System.dat's: marked curated so an
# export of System.dat does not take it for libretro's.
for sys_id, sys_data in EXTRA_SYSTEMS.items(): for sys_id, sys_data in EXTRA_SYSTEMS.items():
if sys_id not in systems: if sys_id not in systems:
systems[sys_id] = sys_data systems[sys_id] = {
**sys_data,
"files": [{**f, "curated": True} for f in sys_data["files"]],
}
# Arcade BIOS present in the repo but absent from System.dat. # Arcade BIOS present in the repo but absent from System.dat.
# FBNeo expects them in system/ or system/fbneo/. # FBNeo expects them in system/ or system/fbneo/.
@@ -310,7 +315,7 @@ class Scraper(BaseScraper):
existing = {f["name"] for f in systems["arcade"].get("files", [])} existing = {f["name"] for f in systems["arcade"].get("files", [])}
for ef in EXTRA_ARCADE_FILES: for ef in EXTRA_ARCADE_FILES:
if ef["name"] not in existing: if ef["name"] not in existing:
systems["arcade"]["files"].append(ef) systems["arcade"]["files"].append({**ef, "curated": True})
# segasp.zip for Sega System SP (Flycast) # segasp.zip for Sega System SP (Flycast)
if "sega-dreamcast-arcade" in systems: if "sega-dreamcast-arcade" in systems:
@@ -323,6 +328,7 @@ class Scraper(BaseScraper):
"name": "segasp.zip", "name": "segasp.zip",
"destination": "dc/segasp.zip", "destination": "dc/segasp.zip",
"required": True, "required": True,
"curated": True,
} }
) )
@@ -442,7 +448,7 @@ class Scraper(BaseScraper):
for ef in extra_files: for ef in extra_files:
key = (ef["name"], ef.get("destination", ef["name"])) key = (ef["name"], ef.get("destination", ef["name"]))
if key not in existing: if key not in existing:
systems[sys_id]["files"].append(ef) systems[sys_id]["files"].append({**ef, "curated": True})
existing.add(key) existing.add(key)
# ep128emu shared group for Enterprise # ep128emu shared group for Enterprise
+1
View File
@@ -175,6 +175,7 @@ class Scraper(BaseScraper):
destination=f"{slug}/{filename}", destination=f"{slug}/{filename}",
required=True, required=True,
native_id=slug, native_id=slug,
mirror_of=None if slug == igdb_slug else igdb_slug,
) )
) )
+68
View File
@@ -0,0 +1,68 @@
"""An export starts from the platform's own file, not from what we add to it.
load_platform_config folds _shared.yml groups into a platform for the pack,
the RomM scraper repeats tg16's firmware under turbografx-cd, and the
libretro scraper adds files it traced in the cores' source. None of them is
in the platform's file. The export counted them kept and wrote them into
libretro's System.dat (N88EXT0-3.ROM) as if libretro declared them.
"""
from __future__ import annotations
import sys
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import common # noqa: E402
from export_native import load_inputs # noqa: E402
class TheNativeLayerIsThePlatformsOwn(unittest.TestCase):
def setUp(self):
common._platform_config_cache.clear()
common._shared_yml_cache.clear()
self.addCleanup(common._platform_config_cache.clear)
self.addCleanup(common._shared_yml_cache.clear)
def test_shared_and_mirrored_entries_are_left_out(self):
with tempfile.TemporaryDirectory() as tmp:
platforms = Path(tmp)
(platforms / "_shared.yml").write_text(
"shared_groups:\n quasi88:\n"
" - name: N88EXT0.ROM\n destination: quasi88/N88EXT0.ROM\n"
)
(platforms / "demo.yml").write_text(
"platform: Demo\nsystems:\n"
" nec-pc-88:\n includes: [quasi88]\n files:\n"
" - name: n88.rom\n destination: quasi88/n88.rom\n"
" tg16:\n files:\n"
" - name: syscard3.pce\n destination: tg16/syscard3.pce\n"
" - name: syscard3.pce\n destination: turbografx-cd/syscard3.pce\n"
" mirror_of: tg16\n"
" nintendo-gamecube:\n files:\n"
" - name: dsp_rom.bin\n destination: dolphin-emu/Sys/GC/dsp_rom.bin\n"
" curated: true\n"
)
pack_view = common.load_platform_config("demo", str(platforms))
_truth, native = load_inputs("demo", platforms, str(platforms))
self.assertEqual(
[f["name"] for f in pack_view["systems"]["nec-pc-88"]["files"]],
["n88.rom", "N88EXT0.ROM"],
)
self.assertEqual(
[f["name"] for f in native["systems"]["nec-pc-88"]["files"]], ["n88.rom"]
)
self.assertEqual(
[f["destination"] for f in native["systems"]["tg16"]["files"]],
["tg16/syscard3.pce"],
)
self.assertEqual(native["systems"]["nintendo-gamecube"]["files"], [])
if __name__ == "__main__":
unittest.main()