diff --git a/emulators/advancemame.yml b/emulators/advancemame.yml index 11e2e4482..b1e4dd9c6 100644 --- a/emulators/advancemame.yml +++ b/emulators/advancemame.yml @@ -162,7 +162,9 @@ files: path: "neogeo.zip" required: true category: bios_zip - system: snk-neogeo-mvs + systems: + - snk-neogeo-mvs + - arcade source_ref: "src/drivers/neogeo.c:7941-7955,8555" note: >- Eight MVS BIOS variants selected with misc_bios (euro, euro-s1, us, @@ -230,7 +232,9 @@ files: path: "pgm.zip" required: true category: bios_zip - system: igs-pgm + systems: + - igs-pgm + - arcade source_ref: "src/drivers/pgm.c:4415-4426, src/drivers/pgm.c:6904" contents: - name: pgm_p01s.rom @@ -254,7 +258,9 @@ files: path: "stvbios.zip" required: true category: bios_zip - system: sega-stv + systems: + - sega-stv + - arcade source_ref: "src/drivers/stv.c:3251-3253,4281" note: >- Nine BIOS variants selected with misc_bios (japan, japana, us, japanb, @@ -311,7 +317,9 @@ files: path: "megaplay.zip" required: true category: bios_zip - system: sega-megaplay + systems: + - sega-megaplay + - arcade source_ref: "src/drivers/megaplay.c:793-796,991" note: >- Two BIOS variants selected with misc_bios (ver1, ver2), ver1 by @@ -333,7 +341,9 @@ files: path: "megatech.zip" required: true category: bios_zip - system: sega-megatech + systems: + - sega-megatech + - arcade source_ref: "src/drivers/megatech.c:456-459,868" contents: - name: epr12664.20 @@ -346,7 +356,9 @@ files: path: "decocass.zip" required: true category: bios_zip - system: deco-cassette + systems: + - deco-cassette + - arcade source_ref: "src/drivers/decocass.c:729-732,1215" note: >- Two main CPU BIOS variants selected with misc_bios (bios0, bios1), @@ -399,7 +411,9 @@ files: path: "playch10.zip" required: true category: bios_zip - system: nintendo-playch10 + systems: + - nintendo-playch10 + - arcade source_ref: "src/drivers/playch10.c:1650-1653,1661" note: >- Two BIOS variants selected with misc_bios (dual, single), dual by @@ -451,7 +465,9 @@ files: path: "nss.zip" required: true category: bios_zip - system: nintendo-nss + systems: + - nintendo-nss + - arcade source_ref: "src/drivers/nss.c:526-528,659" contents: - name: spc700.rom @@ -480,7 +496,9 @@ files: path: "skns.zip" required: true category: bios_zip - system: super-kaneko-nova + systems: + - super-kaneko-nova + - arcade source_ref: "src/drivers/suprnova.c:1272-1279,1767" note: >- Five region BIOS ROMs, all loaded into the same region. @@ -516,7 +534,9 @@ files: path: "konamigx.zip" required: true category: bios_zip - system: konami-gx + systems: + - konami-gx + - arcade source_ref: "src/drivers/konamigx.c:2180-2183,3521" contents: - name: 300a01.34k @@ -529,7 +549,9 @@ files: path: "konamigv.zip" required: true category: bios_zip - system: konami-gv + systems: + - konami-gv + - arcade source_ref: "src/drivers/konamigv.c:974-978,1060" contents: - name: 999a01.7e @@ -542,7 +564,9 @@ files: path: "cvs.zip" required: true category: bios_zip - system: century-cvs + systems: + - century-cvs + - arcade source_ref: "src/drivers/cvs.c:422-432,1403" contents: - name: 5b.bin @@ -566,7 +590,9 @@ files: path: "cpzn1.zip" required: true category: bios_zip - system: capcom-zn1 + systems: + - capcom-zn1 + - arcade source_ref: "src/drivers/zn.c:3188-3190, src/drivers/zn.c:4635" contents: - name: coh-1000c.353 @@ -579,7 +605,9 @@ files: path: "cpzn2.zip" required: true category: bios_zip - system: capcom-zn2 + systems: + - capcom-zn2 + - arcade source_ref: "src/drivers/zn.c:3423-3425, src/drivers/zn.c:4653" contents: - name: coh-3002c.353 @@ -592,7 +620,9 @@ files: path: "tps.zip" required: true category: bios_zip - system: tecmo-tps + systems: + - tecmo-tps + - arcade source_ref: "src/drivers/zn.c:3866-3868, src/drivers/zn.c:4700" contents: - name: coh-1002m.353 @@ -605,7 +635,9 @@ files: path: "taitofx1.zip" required: true category: bios_zip - system: taito-fx1 + systems: + - taito-fx1 + - arcade source_ref: "src/drivers/zn.c:4162-4164, src/drivers/zn.c:4733" contents: - name: coh-1000t.353 @@ -618,7 +650,9 @@ files: path: "psarc95.zip" required: true category: bios_zip - system: eighting-zn1 + systems: + - eighting-zn1 + - arcade source_ref: "src/drivers/zn.c:4434-4436, src/drivers/zn.c:4760" contents: - name: coh-1002e.353 @@ -631,7 +665,9 @@ files: path: "atluspsx.zip" required: true category: bios_zip - system: atlus-zn1 + systems: + - atlus-zn1 + - arcade source_ref: "src/drivers/zn.c:4605-4607, src/drivers/zn.c:4772" contents: - name: coh-1001l.353 @@ -644,7 +680,9 @@ files: path: "atpsx.zip" required: true category: bios_zip - system: atari-zn1 + systems: + - atari-zn1 + - arcade source_ref: "src/drivers/zn.c:4520-4522, src/drivers/zn.c:4680" contents: - name: coh-1000w.353 @@ -657,7 +695,9 @@ files: path: "acpsx.zip" required: true category: bios_zip - system: acclaim-zn1 + systems: + - acclaim-zn1 + - arcade source_ref: "src/drivers/zn.c:4543-4545, src/drivers/zn.c:4689" contents: - name: coh-1000a.353 @@ -670,7 +710,9 @@ files: path: "coh1002v.zip" required: true category: bios_zip - system: videosystem-zn1 + systems: + - videosystem-zn1 + - arcade source_ref: "src/drivers/zn.c:4090-4096, src/drivers/zn.c:4722" contents: - name: coh-1002v.ic353 @@ -683,7 +725,9 @@ files: path: "aleck64.zip" required: true category: bios_zip - system: nintendo-aleck64 + systems: + - nintendo-aleck64 + - arcade source_ref: "src/drivers/aleck64.c:697-699,778" contents: - name: pifdata.bin @@ -696,7 +740,9 @@ files: path: "atarisy1.zip" required: true category: bios_zip - system: atari-system1 + systems: + - atari-system1 + - arcade source_ref: "src/drivers/atarisy1.c:837-850, src/drivers/atarisy1.c:2299" contents: - name: "136032.114.j11" @@ -720,7 +766,9 @@ files: path: "cedmag.zip" required: true category: bios_zip - system: efo-cedar-magnet + systems: + - efo-cedar-magnet + - arcade source_ref: "src/drivers/cedar_magnet.c:881-890, src/drivers/cedar_magnet.c:948" contents: - name: "magnet-master-vid-e03.bin" @@ -733,7 +781,9 @@ files: path: "ar_bios.zip" required: true category: bios_zip - system: arcadia-systems + systems: + - arcadia-systems + - arcade source_ref: "src/drivers/arsystems.c:356-377,823" note: >- Three BIOS variants selected with misc_bios (onep300, onep211, @@ -801,7 +851,9 @@ files: path: "maxaflex.zip" required: true category: bios_zip - system: exidy-maxaflex + systems: + - exidy-maxaflex + - arcade source_ref: "src/drivers/maxaflex.c:520-529,585" contents: - name: atarixl.rom @@ -825,7 +877,9 @@ files: path: "crysbios.zip" required: true category: bios_zip - system: brezzasoft-crystal + systems: + - brezzasoft-crystal + - arcade source_ref: "src/drivers/crystal.c:924-929,1032" contents: - name: mx27l1000.u14 @@ -838,7 +892,9 @@ files: path: "macsbios.zip" required: true category: bios_zip - system: imax-macs + systems: + - imax-macs + - arcade source_ref: "src/drivers/macs.c:396-404,614" contents: - name: macsos_l.u43 @@ -857,7 +913,9 @@ files: path: "bctvidbs.zip" required: true category: bios_zip - system: barcrest-mpu4 + systems: + - barcrest-mpu4 + - arcade source_ref: "src/drivers/mpu4.c:2016-2019,2177" contents: - name: vid.p1 diff --git a/scripts/common.py b/scripts/common.py index 1b09493e6..cb4149de5 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -3184,6 +3184,29 @@ def standalone_bindings_for( ) +def _placed_entry(entry: dict, profile: dict, placed: tuple[str, str]) -> dict: + """A standalone entry moved to the (tree, path) a platform reads it from.""" + tree, path = placed + moved = {**entry, "standalone_path": path, + "layout_path": standalone_layout_path(entry, profile)} + if entry.get("load_from"): + # The build's own root no longer applies: RetroDECK hands + # Cemu's MLC, read from its data directory, to bios/cemu. + load_from = entry["load_from"] + moved["load_from"] = { + **(load_from if isinstance(load_from, dict) else {"libretro": load_from}), + "standalone": "system_dir", + } + if tree == "install": + # Below the roms root inside the install tree (AmberELEC's + # /storage/roms): a plain dest the platform prefix skips. + return {**moved, "unprefixed": True} + # A file the launcher runs from the roms directory (ROCKNIX's + # roms/pico-8) or an emulator folder (RetroBat's emulators/pico8) + # is relative to it, and says so. + return {**moved, "root": tree} if tree else moved + + def platform_layouts( emu_name: str, profile: dict, standalone_cores: set[str] ) -> list[tuple[dict, bool]]: @@ -3218,7 +3241,14 @@ def platform_layouts( for system in own: family.setdefault(system, set()).update(entry_launch_systems(f, index) - own) - def bindings_of(entry: dict) -> dict[str, tuple[str, str]]: + # The systems the launcher records name: a launch for one of them reads + # the folder its record gives, every other launch the folder the records + # naming none give. + named_systems = frozenset().union(*( + h.systems for h in _handed(standalone_cores) if h.emulator in names + )) if per_system else frozenset() + + def launch_systems(entry: dict) -> frozenset[str]: systems: frozenset[str] = frozenset() if per_system: own = entry_launch_systems(entry, {}) @@ -3232,12 +3262,39 @@ def platform_layouts( systems = systems.union(*( index.get(MACHINE_KEY + _norm_system_id(s), set()) for s in own )) + return systems + + def bindings_for(systems: frozenset[str]) -> dict[str, tuple[str, str]]: if systems not in by_systems: by_systems[systems] = standalone_bindings_for( emu_name, profile, standalone_cores, systems ) return by_systems[systems] + def bindings_of(entry: dict) -> dict[str, tuple[str, str]]: + return bindings_for(launch_systems(entry)) + + def destinations(entry: dict) -> list[tuple[str, str] | None]: + """Each place the entry's launches read it from, in launch order. + + An entry of a system a record names is read where that record puts + it. One also launched for systems no record names is read, for those + launches, where the records naming none put it: EmuELEC starts + AdvanceMAME with dir_rom roms/arcade for arcade and roms/mame for + mame and cps1-3 (advmame.sh:26-30), and an arcade set is read in both. + """ + systems = launch_systems(entry) + found = [standalone_destination(entry, profile, bindings_for(systems))] + # A system the platform's list or machines put the entry under + # stands for the entry's own ids: XRoar's coco3 is launched as coco. + own = entry_launch_systems(entry, {}) + launched = (systems - own) | (own & named_systems) if systems != own else systems + if launched & named_systems and launched - named_systems: + generic = standalone_destination(entry, profile, bindings_for(frozenset())) + if generic not in found: + found.append(generic) + return found + layout_entries = [ f for f in profile.get("files") or [] if isinstance(f, dict) and standalone_layout_path(f, profile) is not None @@ -3255,30 +3312,11 @@ def platform_layouts( if not isinstance(f, dict) or standalone_layout_path(f, profile) is None: files.append(f) continue - placed = standalone_destination(f, profile, bindings_of(f)) - if placed is None or placed[0] == "outside": + places = [p for p in destinations(f) if p is not None and p[0] != "outside"] + if not places: files.append({**f, "unplaced": OUTSIDE_BIOS_FOLDER}) continue - tree, path = placed - moved = {**f, "standalone_path": path, - "layout_path": standalone_layout_path(f, profile)} - if f.get("load_from"): - # The build's own root no longer applies: RetroDECK hands - # Cemu's MLC, read from its data directory, to bios/cemu. - load_from = f["load_from"] - moved["load_from"] = { - **(load_from if isinstance(load_from, dict) else {"libretro": load_from}), - "standalone": "system_dir", - } - if tree == "install": - # Below the roms root inside the install tree (AmberELEC's - # /storage/roms): a plain dest the platform prefix skips. - files.append({**moved, "unprefixed": True}) - continue - # A file the launcher runs from the roms directory (ROCKNIX's - # roms/pico-8) or an emulator folder (RetroBat's emulators/pico8) - # is relative to it, and says so. - files.append({**moved, "root": tree} if tree else moved) + files.extend(_placed_entry(f, profile, placed) for placed in places) standalone = {**profile, "files": files} dual = {c.removeprefix(DUAL_MODE_PREFIX) for c in standalone_cores if c.startswith(DUAL_MODE_PREFIX)} diff --git a/scripts/packextras.py b/scripts/packextras.py index 1531c384a..e47228a7b 100644 --- a/scripts/packextras.py +++ b/scripts/packextras.py @@ -1545,6 +1545,11 @@ def _collect_emulator_extras( extra["system_dir"] = f["system_dir"] if f.get("unprefixed"): extra["unprefixed"] = True + # A copy placed outside the BIOS tree resolves by its layout + # path alone, as the first pass does: EmuELEC's + # roms/mame/stvbios.zip took bios/Arcade/MAME's set by that tail. + if f.get("layout_path"): + extra["layout_path"] = f["layout_path"] extra["claim_order"] = (2, 0, emu_name) if _take(extra, full_dest, seen_dests, taken): extras.append(extra) diff --git a/tests/test_launcher_placement.py b/tests/test_launcher_placement.py index 9942638b7..6e4d1558a 100644 --- a/tests/test_launcher_placement.py +++ b/tests/test_launcher_placement.py @@ -912,13 +912,38 @@ class AdvanceMameOnEmuELEC(unittest.TestCase): """advmame.rc sets dir_rom /storage/roms/mame, /storage/roms/arcade for the arcade system (advmame.rc:11, advmame.sh:26-30).""" - def test_the_sets_land_in_roms_mame(self) -> None: + def test_the_sets_land_in_roms_mame_and_roms_arcade(self) -> None: + # Started for arcade it reads roms/arcade, for mame and cps1-3 + # roms/mame (es_systems.json, advmame.sh:26-30): an arcade set is + # read in both. view = standalone_view("advancemame", "emuelec") sets = [e for e in view["files"] if e.get("category") == "bios_zip"] - self.assertEqual(len(sets), 29) + self.assertEqual(len(sets), 58) + places: dict[str, set[str]] = {} for entry in sets: - self.assertEqual(entry["standalone_path"], "mame/" + entry["name"]) self.assertTrue(entry.get("unprefixed")) + places.setdefault(entry["name"], set()).add(entry["standalone_path"]) + self.assertEqual(len(places), 29) + for name, paths in places.items(): + self.assertEqual(paths, {"mame/" + name, "arcade/" + name}) + + def test_both_copies_are_one_file(self) -> None: + # Outside the BIOS tree the folder is no proof of content: each copy + # resolves by its layout path, so both folders hold the same file. + from common import load_database + + db = load_database(str(REPO_ROOT / "database.json")) + with redirect_stdout(io.StringIO()): + manifest = generate_manifest( + "emuelec", str(REPO_ROOT / "platforms"), db, str(REPO_ROOT / "bios"), + str(REPO_ROOT / "platforms" / "_registry.yml"), + emulators_dir=str(REPO_ROOT / "emulators"), offline=True, + ) + by_dest = {f["dest"]: f["sha1"] for f in manifest["files"]} + sets = [d.removeprefix("arcade/") for d in by_dest if d.startswith("arcade/")] + self.assertEqual(len(sets), 29) + for name in sets: + self.assertEqual(by_dest["arcade/" + name], by_dest["mame/" + name], name) def test_the_roms_folder_is_no_proof_of_content(self) -> None: # bios/Arcade/MAME/konamigv.zip is MAME 0.289's set, not AdvanceMAME's. @@ -926,7 +951,8 @@ class AdvanceMameOnEmuELEC(unittest.TestCase): db = load_database(str(REPO_ROOT / "database.json")) view = standalone_view("advancemame", "emuelec") - (entry,) = [e for e in view["files"] if e["name"] == "konamigv.zip"] + entry = next(e for e in view["files"] + if e["name"] == "konamigv.zip" and e["standalone_path"].startswith("mame/")) moved, _status = resolve_local_file(entry, db, dest_hint=entry["standalone_path"]) plain = {k: v for k, v in entry.items() if k not in ("layout_path", "standalone_path", "unprefixed")}