diff --git a/emulators/openmsx.yml b/emulators/openmsx.yml index b088e8cd3..c8f8d3913 100644 --- a/emulators/openmsx.yml +++ b/emulators/openmsx.yml @@ -1,5 +1,6 @@ emulator: openMSX type: standalone +core_classification: other source: "https://github.com/openMSX/openMSX" upstream: "https://github.com/openMSX/openMSX" profiled_date: "2026-03-26" @@ -22,6 +23,11 @@ systems: - msxturbor - colecovision - spectravideo +standalone_directories: + - "OPENMSX_HOME" + - "~/.openMSX" +standalone_pools: + filepool: "share/systemroms" notes: | Standalone MSX emulator supporting MSX1 through turboR, ColecoVision, @@ -35,13 +41,14 @@ notes: | looked up beside the machine's XML (src/memory/Rom.cc:121-170, src/config/HardwareConfig.cc:279-280, src/file/FileContext.cc:152-156). Files may be gzipped or individually zipped. - Each user data directory contributes share/systemroms, scanned - recursively (src/file/FilePool.cc:27, src/file/FilePoolCore.cc:369-385). RetroBat - sets OPENMSX_HOME to bios/openmsx and refuses to start a machine whose - ROMs are not in bios/openmsx/share/systemroms. RetroPie links - share/systemroms to BIOS/openmsx and Batocera adds bios/openmsx to the - pool, both scanned recursively, so openmsx/share/systemroms serves all - three. + The user directory is $OPENMSX_HOME, else ~/.openMSX, and its share/ + subfolder is the user data directory unless $OPENMSX_USER_DATA names + another (src/file/FileOperations.cc:415-448). The pool starts with + systemroms below the user and the system data directories + (src/file/FilePool.cc:21-33), and each `filepool add -types system_rom` + entry is searched the same way, recursively, until a file of the SHA1 is + found (src/file/FilePool.cc:98-142, src/file/FilePoolCore.cc:244-264, + 369-385). Batocera configgen (openmsxGenerator.py) configures filepool paths (/userdata/bios/Machines position 1, /userdata/bios/openmsx position 2) @@ -67,7 +74,7 @@ files: # Based on Philips NMS 8245 with extra hardware # ------------------------------------------------------- - name: NMS8245SystemROM1.08.bin - path: "openmsx/share/systemroms/NMS8245SystemROM1.08.bin" + path: "share/systemroms/NMS8245SystemROM1.08.bin" sha1: cc57c1dcd7249ea9f8e2547244592e7d97308ed0 required: true size: 65536 @@ -76,7 +83,7 @@ files: source_ref: "share/machines/Boosted_MSX2_EN.xml:24,65,111" - name: fmpac.rom - path: "openmsx/share/systemroms/fmpac.rom" + path: "share/systemroms/fmpac.rom" sha1: fec451b9256570a0e4869355a40329c57f40705f required: true description: "Panasonic SW-M004 FMPAC (FM-PAC) sound cartridge ROM" @@ -84,7 +91,7 @@ files: source_ref: "share/machines/Boosted_MSX2_EN.xml:83" - name: phc-70fd2_basickun.rom - path: "openmsx/share/systemroms/phc-70fd2_basickun.rom" + path: "share/systemroms/phc-70fd2_basickun.rom" sha1: 22b3191d865010264001b9d896186a9818478a6b required: true description: "Sanyo PHC-70FD2 MSX-BASIC Kun compiler ROM" @@ -92,7 +99,7 @@ files: source_ref: "share/machines/Boosted_MSX2_EN.xml:89, share/machines/Boosted_MSX2+_JP.xml:167, share/machines/Boosted_MSXturboR_with_IDE.xml:79" - name: yrw801.rom - path: "openmsx/share/systemroms/yrw801.rom" + path: "share/systemroms/yrw801.rom" sha1: 32760893ce06dbe3930627755ba065cc3d8ec6ca required: true description: "Yamaha YRW801 MoonSound wavetable ROM" @@ -104,7 +111,7 @@ files: # Based on Panasonic FS-A1WSX with extra hardware # ------------------------------------------------------- - name: fs-a1wsx_basic-bios2p.rom - path: "openmsx/share/systemroms/fs-a1wsx_basic-bios2p.rom" + path: "share/systemroms/fs-a1wsx_basic-bios2p.rom" sha1: f4433752d3bf876bfefb363c749d4d2e08a218b6 required: true description: "FS-A1WSX MSX2+ BIOS and BASIC ROM" @@ -112,7 +119,7 @@ files: source_ref: "share/machines/Boosted_MSX2+_JP.xml:143" - name: fs-a1wsx_fmbasic.rom - path: "openmsx/share/systemroms/fs-a1wsx_fmbasic.rom" + path: "share/systemroms/fs-a1wsx_fmbasic.rom" sha1: aad42ba4289b33d8eed225d42cea930b7fc5c228 required: true description: "FS-A1WSX MSX-MUSIC (OPLL) ROM" @@ -121,7 +128,7 @@ files: - name: fs-a1wsx_msx2psub.rom installed_on: [retropie] - path: "openmsx/share/systemroms/fs-a1wsx_msx2psub.rom" + path: "share/systemroms/fs-a1wsx_msx2psub.rom" sha1: fe0254cbfc11405b79e7c86c7769bd6322b04995 required: true description: "FS-A1WSX MSX2+ Sub ROM" @@ -130,7 +137,7 @@ files: note: "RetroPie unpacks openmsxroms.tar.gz into the installed share/systemroms, which the file pool scans. This ROM is in it as MSX2PEXT.ROM and is found by its SHA1." - name: fs-a1wsx_kanjibasic.rom - path: "openmsx/share/systemroms/fs-a1wsx_kanjibasic.rom" + path: "share/systemroms/fs-a1wsx_kanjibasic.rom" sha1: dcc3a67732aa01c4f2ee8d1ad886444a4dbafe06 required: true description: "FS-A1WSX Kanji Driver with BASIC ROM" @@ -138,7 +145,7 @@ files: source_ref: "share/machines/Boosted_MSX2+_JP.xml:225" - name: fs-a1wsx_disk.rom - path: "openmsx/share/systemroms/fs-a1wsx_disk.rom" + path: "share/systemroms/fs-a1wsx_disk.rom" sha1: 7ed7c55e0359737ac5e68d38cb6903f9e5d7c2b6 required: true description: "FS-A1WSX FDC (TC8566AF) disk controller ROM" @@ -146,7 +153,7 @@ files: source_ref: "share/machines/Boosted_MSX2+_JP.xml:237" - name: fs-a1wsx_firmware.rom - path: "openmsx/share/systemroms/fs-a1wsx_firmware.rom" + path: "share/systemroms/fs-a1wsx_firmware.rom" sha1: 3330d9b6b76e3c4ccb7cf252496ed15d08b95d3f required: true description: "FS-A1WSX Panasonic firmware ROM" @@ -154,7 +161,7 @@ files: source_ref: "share/machines/Boosted_MSX2+_JP.xml:251" - name: fs-a1wsx_kanjifont.rom - path: "openmsx/share/systemroms/fs-a1wsx_kanjifont.rom" + path: "share/systemroms/fs-a1wsx_kanjifont.rom" sha1: 5aff2d9b6efc723bc395b0f96f0adfa83cc54a49 aliases: [fs-a1gt_kanjifont.rom] required: true @@ -168,7 +175,7 @@ files: # Based on Panasonic FS-A1GT with IDE interface # ------------------------------------------------------- - name: fs-a1gt_firmware.rom - path: "openmsx/share/systemroms/fs-a1gt_firmware.rom" + path: "share/systemroms/fs-a1gt_firmware.rom" sha1: - e779c338eb91a7dea3ff75f3fde76b8af22c4a3a - 5fa3aa79aeba2c0441f349e78e9a16d9d64422ea @@ -178,7 +185,7 @@ files: source_ref: "share/machines/Boosted_MSXturboR_with_IDE.xml:23" - name: ide250.dat - path: "openmsx/share/systemroms/ide250.dat" + path: "share/systemroms/ide250.dat" sha1: 93e41c7d479bc90c1d1f6d081af20fd9924b8ada required: true description: "Sunrise IDE 2.50 interface ROM" @@ -186,7 +193,7 @@ files: source_ref: "share/machines/Boosted_MSXturboR_with_IDE.xml:49" - name: ide250.zip - path: "openmsx/share/systemroms/ide250.zip" + path: "share/systemroms/ide250.zip" required: false description: "Sunrise IDE 2.50 distribution archive (IDE250.DAT, IDE250.TXT, IDEFLOAD.COM, IDEFLOAD.TXT)" contents: @@ -201,7 +208,7 @@ files: # ColecoVision with built-in Super Game Module # ------------------------------------------------------- - name: COLECO.ROM - path: "openmsx/share/systemroms/COLECO.ROM" + path: "share/systemroms/COLECO.ROM" sha1: 45bedc4cbdeac66c7df59e9e599195c778d86a92 required: true description: "ColecoVision system BIOS ROM" @@ -212,7 +219,7 @@ files: # Spectravideo_SVI-328 (spectravideo) # ------------------------------------------------------- - name: svi-3x8_v111.rom - path: "openmsx/share/systemroms/svi-3x8_v111.rom" + path: "share/systemroms/svi-3x8_v111.rom" sha1: 10349ce675f6d6d47f0976e39cb7188eba858d89 required: true description: "SVI-328 BASIC v1.11 ROM" @@ -224,7 +231,7 @@ files: # MSX1 with LaserDisc controller # ------------------------------------------------------- - name: px-7_basic-bios1.rom - path: "openmsx/share/systemroms/px-7_basic-bios1.rom" + path: "share/systemroms/px-7_basic-bios1.rom" sha1: 302afb5d8be26c758309ca3df611ae69cced2821 required: true description: "Pioneer PX-7 MSX1 BIOS and BASIC ROM" @@ -232,7 +239,7 @@ files: source_ref: "share/machines/Pioneer_PX-7.xml:67" - name: px-7_pbasic.rom - path: "openmsx/share/systemroms/px-7_pbasic.rom" + path: "share/systemroms/px-7_pbasic.rom" sha1: 665d805f96616e1037f1823050657b7849899283 required: true description: "Pioneer PX-7 LaserDisc PBASIC controller ROM" @@ -248,7 +255,7 @@ files: # found does not start (src/memory/Rom.cc:173-184). # ------------------------------------------------------- - name: cf-3300_basic-bios1.rom - path: "openmsx/share/systemroms/cf-3300_basic-bios1.rom" + path: "share/systemroms/cf-3300_basic-bios1.rom" sha1: c7a2c5baee6a9f0e1c6ee7d76944c0ab1886796c required: false size: 32768 @@ -257,7 +264,7 @@ files: source_ref: "share/machines/National_CF-3300.xml:51-52" - name: cf-3300_disk.rom - path: "openmsx/share/systemroms/cf-3300_disk.rom" + path: "share/systemroms/cf-3300_disk.rom" sha1: f1525de4e0b60a6687156c2a96f8a8b2044b6c56 required: false size: 16384 @@ -266,7 +273,7 @@ files: source_ref: "share/machines/National_CF-3300.xml:75-76" - name: fs-5500_basic-bios2.rom - path: "openmsx/share/systemroms/fs-5500_basic-bios2.rom" + path: "share/systemroms/fs-5500_basic-bios2.rom" sha1: 44e0dd215b2a9f0770dd76fb49187c05b083eed9 required: false size: 32768 @@ -275,7 +282,7 @@ files: source_ref: "share/machines/National_FS-5500F2.xml:76-77, share/machines/National_FS-5500F1.xml:78" - name: fs-5500_msx2sub.rom - path: "openmsx/share/systemroms/fs-5500_msx2sub.rom" + path: "share/systemroms/fs-5500_msx2sub.rom" sha1: 4be8371f3b03e70ddaca495958345f3c4f8e2d36 required: false size: 16384 @@ -284,7 +291,7 @@ files: source_ref: "share/machines/National_FS-5500F2.xml:100-101, share/machines/National_FS-5500F1.xml:102" - name: fs-5500_kanjibasic.rom - path: "openmsx/share/systemroms/fs-5500_kanjibasic.rom" + path: "share/systemroms/fs-5500_kanjibasic.rom" sha1: 3a9a942ed888dd641cddf8deada1879c454df3c6 required: false size: 32768 @@ -293,7 +300,7 @@ files: source_ref: "share/machines/National_FS-5500F2.xml:107-108, share/machines/National_FS-5500F1.xml:109" - name: fs-5500_kanjifont.rom - path: "openmsx/share/systemroms/fs-5500_kanjifont.rom" + path: "share/systemroms/fs-5500_kanjifont.rom" sha1: 9ed3ab6d893632b9246e91b412cd5db519e7586b required: false size: 131072 @@ -302,7 +309,7 @@ files: source_ref: "share/machines/National_FS-5500F2.xml:20-21, share/machines/National_FS-5500F1.xml:22" - name: fs-5500_superimp.rom - path: "openmsx/share/systemroms/fs-5500_superimp.rom" + path: "share/systemroms/fs-5500_superimp.rom" sha1: b677a861b67e8763a11d5dcf52416b42493ade57 required: false size: 32768 @@ -311,7 +318,7 @@ files: source_ref: "share/machines/National_FS-5500F2.xml:117-118, share/machines/National_FS-5500F1.xml:119" - name: nms8245_msx2sub.rom - path: "openmsx/share/systemroms/nms8245_msx2sub.rom" + path: "share/systemroms/nms8245_msx2sub.rom" sha1: - 5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02 - e7905d16d2ccd57a013c122dc432106cd59ef52c @@ -322,7 +329,7 @@ files: source_ref: "share/machines/Philips_NMS_8245.xml:108-110" - name: vg8020_basic-bios1.rom - path: "openmsx/share/systemroms/vg8020_basic-bios1.rom" + path: "share/systemroms/vg8020_basic-bios1.rom" sha1: 829c00c3114f25b3dae5157c0a238b52a3ac37db required: false size: 32768 @@ -331,7 +338,7 @@ files: source_ref: "share/machines/Philips_VG_8020.xml:53-54" - name: svi-738_basic-bios1.rom - path: "openmsx/share/systemroms/svi-738_basic-bios1.rom" + path: "share/systemroms/svi-738_basic-bios1.rom" sha1: c53b3f2c00f31683914f7452f3f4d94ae2929c0d required: false size: 32768 @@ -340,7 +347,7 @@ files: source_ref: "share/machines/Spectravideo_SVI-738.xml:53-54" - name: svi-738_se_basic-bios1.rom - path: "openmsx/share/systemroms/svi-738_se_basic-bios1.rom" + path: "share/systemroms/svi-738_se_basic-bios1.rom" sha1: 87f4d0fa58cfe9cef818a3185df2735e6da6168c required: false size: 32768 @@ -349,7 +356,7 @@ files: source_ref: "share/machines/Spectravideo_SVI-738_SE.xml:53-54" - name: svi-738_rs232.rom - path: "openmsx/share/systemroms/svi-738_rs232.rom" + path: "share/systemroms/svi-738_rs232.rom" sha1: 4e9384c9d137f0ab65ffc5a78f04cd8c9df6c8b7 required: false size: 8192 @@ -358,7 +365,7 @@ files: source_ref: "share/machines/Spectravideo_SVI-738.xml:76-77, share/machines/Spectravideo_SVI-738_SE.xml:76-77" - name: svi-738_disk.rom - path: "openmsx/share/systemroms/svi-738_disk.rom" + path: "share/systemroms/svi-738_disk.rom" sha1: - 99a40266bc296cef1d432cb0caa8df1a7e570be4 - 55d5d1fd57d35384b821af62083f2a23d85580e6 diff --git a/platforms/batocera.yml b/platforms/batocera.yml index 0d55954da..95c94a27c 100644 --- a/platforms/batocera.yml +++ b/platforms/batocera.yml @@ -6562,6 +6562,24 @@ launchers: passes_to: environment FLYCAST_BIOS_PATH fails_when_missing: false source_ref: "package/batocera/core/batocera-configgen/configgen/configgen/generators/flycast/flycastGenerator.py:199" +- emulator: openmsx + build: standalone + kind: passes_path + names: [] + search_dir: /userdata/bios/Machines + passes_to: "written: filepool add -path /userdata/bios/Machines -types system_rom\ + \ -position 1" + fails_when_missing: false + source_ref: "package/batocera/core/batocera-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:88" +- emulator: openmsx + build: standalone + kind: passes_path + names: [] + search_dir: /userdata/bios/openmsx + passes_to: "written: filepool add -path /userdata/bios/openmsx -types system_rom\ + \ -position 2" + fails_when_missing: false + source_ref: "package/batocera/core/batocera-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:89" - emulator: o2em build: libretro kind: core_option diff --git a/platforms/knulli.yml b/platforms/knulli.yml index 3345c487d..e1c6ebafe 100644 --- a/platforms/knulli.yml +++ b/platforms/knulli.yml @@ -5685,6 +5685,24 @@ launchers: passes_to: environment FLYCAST_BIOS_PATH fails_when_missing: false source_ref: "package/system/knulli-configgen/configgen/configgen/generators/flycast/flycastGenerator.py:239" +- emulator: openmsx + build: standalone + kind: passes_path + names: [] + search_dir: /userdata/bios/Machines + passes_to: "written: filepool add -path /userdata/bios/Machines -types system_rom\ + \ -position 1" + fails_when_missing: false + source_ref: "package/system/knulli-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:95" +- emulator: openmsx + build: standalone + kind: passes_path + names: [] + search_dir: /userdata/bios/openmsx + passes_to: "written: filepool add -path /userdata/bios/openmsx -types system_rom\ + \ -position 2" + fails_when_missing: false + source_ref: "package/system/knulli-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:96" - emulator: lexaloffle build: standalone kind: fixed_name @@ -6014,24 +6032,6 @@ launchers: passes_to: command line -crosshairpath fails_when_missing: false source_ref: "package/system/knulli-configgen/configgen/configgen/generators/mame/mameGenerator.py:180" -- emulator: openmsx - build: standalone - kind: passes_path - names: [] - search_dir: /userdata/bios/Machines - passes_to: "written: filepool add -path /userdata/bios/Machines -types system_rom\ - \ -position 1" - fails_when_missing: false - source_ref: "package/system/knulli-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:95" -- emulator: openmsx - build: standalone - kind: passes_path - names: [] - search_dir: /userdata/bios/openmsx - passes_to: "written: filepool add -path /userdata/bios/openmsx -types system_rom\ - \ -position 2" - fails_when_missing: false - source_ref: "package/system/knulli-configgen/configgen/configgen/generators/openmsx/openmsxGenerator.py:96" - emulator: fpinball build: standalone kind: passes_path diff --git a/platforms/retrobat.yml b/platforms/retrobat.yml index 5786bc1a0..540f55dea 100644 --- a/platforms/retrobat.yml +++ b/platforms/retrobat.yml @@ -4779,6 +4779,14 @@ launchers: fails_when_missing: ApplicationException ' machine has missing BIOS file '' in 'bios\openmsx\share\systemroms' folder.' source_ref: "emulatorLauncher/Generators/OpenMSX.Generator.cs:156-159, 467" +- emulator: openmsx + build: standalone + kind: passes_path + names: [] + search_dir: bios/openmsx + passes_to: environment OPENMSX_HOME + fails_when_missing: false + source_ref: "emulatorLauncher/Generators/OpenMSX.Generator.cs:51, 255" - emulator: kronos build: standalone kind: fixed_name diff --git a/platforms/retropie.yml b/platforms/retropie.yml index cc54b45a2..34276c303 100644 --- a/platforms/retropie.yml +++ b/platforms/retropie.yml @@ -778,6 +778,15 @@ launchers: passes_to: $configdir/msx/openmsx/share/systemroms is a symlink to it fails_when_missing: false source_ref: "scriptmodules/emulators/openmsx.sh:77" +- emulator: openmsx + package: openmsx + build: standalone + kind: links + names: [] + search_dir: $configdir/msx/openmsx + passes_to: $home/.openMSX is a symlink to it + fails_when_missing: false + source_ref: "scriptmodules/emulators/openmsx.sh:76" - emulator: oricutron package: oricutron repo: "git https://github.com/HerbFargus/oricutron.git extras" @@ -1269,6 +1278,15 @@ launchers: passes_to: $md_inst/share/xroar/roms is a symlink to it fails_when_missing: false source_ref: "scriptmodules/emulators/xroar.sh:52" +- emulator: xroar + package: xroar + build: standalone + kind: passes_path + names: [] + search_dir: $md_inst + passes_to: "${prefix} of configure --prefix" + fails_when_missing: false + source_ref: "scriptmodules/emulators/xroar.sh:37" - emulator: yabasanshiro package: yabasanshiro repo: "git https://github.com/retropie/yabause.git pi4-1-9-0" diff --git a/scripts/scraper/batocera_launchers.py b/scripts/scraper/batocera_launchers.py index 67f03887e..8f62babcf 100644 --- a/scripts/scraper/batocera_launchers.py +++ b/scripts/scraper/batocera_launchers.py @@ -15,7 +15,8 @@ launcher looks for nothing". from __future__ import annotations import ast -from collections.abc import Callable +import re +from collections.abc import Callable, Iterable from typing import Any from .configgen_launchers import ( @@ -898,6 +899,56 @@ def environment_directories(cg: Configgen) -> list[Launcher]: return launchers +# Generators that hand the emulator a /userdata/bios directory through its +# configuration: a line written to the file it reads (openMSX's script.tcl +# filepool entries) or a key saved to it. +CONFIG_DIRECTORY_GENERATORS = ("openmsx.openmsxGenerator",) + + +def config_directories( + cg: Configgen, modules: Iterable[str] = CONFIG_DIRECTORY_GENERATORS +) -> list[Launcher]: + """file.write("") and settings.save(key, ).""" + prefix = cg.bios.rstrip("/") + inside = re.compile(rf"{re.escape(prefix)}(?:/[^\s\"';]*)?") + launchers = [] + for module in modules: + src = cg.generator(module) + constants = dict(cg.paths) + for node in src.tree.body: + name = _assigned_name(node) + value = _path_value(getattr(node, "value", None), constants) + if name is not None and value is not None: + constants[name] = value + found: list[tuple[str, str, ast.Call]] = [] + for call in _calls(src.tree, attr="write"): + text = call.args[0] if len(call.args) == 1 else None + if isinstance(text, ast.Constant) and isinstance(text.value, str): + line = text.value.strip() + named = inside.search(line) + if named: + found.append((named.group(0), f"written: {line}", call)) + for call in _calls(src.tree, attr="save"): + if len(call.args) != 2 or not isinstance(call.args[0], ast.Constant): + continue + value = _path_value(call.args[1], constants) + if value is None and isinstance(call.args[1], ast.Constant): + value = str(call.args[1].value) + if value is not None and (value.rstrip("/") == prefix or value.startswith(prefix + "/")): + found.append((value, str(call.args[0].value), call)) + if not found: + src.fail("hands no /userdata/bios directory through its config") + launchers.extend( + Launcher( + emulator=cg.emulator(module), build="standalone", kind="passes_path", + names=[], search_dir=directory, passes_to=passes_to, + source_ref=src.ref([src.span(call)]), + ) + for directory, passes_to, call in found + ) + return launchers + + # libretro core options whose value names the BIOS file the core opens. FILE_OPTIONS = (("_o2em_options", "o2em_bios"), ("_neocd_options", "neocd_bios")) @@ -952,7 +1003,8 @@ def core_file_options(cg: Configgen) -> list[Launcher]: EXTRACTORS: tuple[Callable[[Configgen], list[Launcher]], ...] = ( duckstation, duckstation_legacy, hatari, xemu, melonds, mupen64plus, - x16emu, gsplus, lexaloffle, environment_directories, core_file_options, + x16emu, gsplus, lexaloffle, environment_directories, config_directories, + core_file_options, ) diff --git a/scripts/scraper/knulli_launchers.py b/scripts/scraper/knulli_launchers.py index 5a31bcd6b..4ea63895f 100644 --- a/scripts/scraper/knulli_launchers.py +++ b/scripts/scraper/knulli_launchers.py @@ -31,6 +31,7 @@ import yaml try: from .batocera_launchers import ( Configgen, + config_directories, _path_value, _render, _system_branches, @@ -53,6 +54,7 @@ try: except ImportError: from batocera_launchers import ( # type: ignore[no-redef] Configgen, + config_directories, _path_value, _render, _system_branches, @@ -84,9 +86,18 @@ PICO8_INSTALLER = "board/fsoverlay/usr/bin/knulli-install-pico8" ES_FEATURES = "package/emulationstation/knulli-es-system/es_features.yml" # Batocera's extractors that read KNULLI's generators unchanged. +# The generators of Batocera's config_directories that KNULLI keeps in the +# same shape: openMSX's filepool lines. +CONFIG_DIRECTORY_GENERATORS = ("openmsx.openmsxGenerator",) + + +def knulli_config_directories(cg: Configgen) -> list[Launcher]: + return config_directories(cg, CONFIG_DIRECTORY_GENERATORS) + + SHARED = ( duckstation, duckstation_legacy, hatari, xemu, melonds, mupen64plus, x16emu, - environment_directories, + environment_directories, knulli_config_directories, ) @@ -365,7 +376,6 @@ GENERIC_SITES = ( GenericSites("amiberry/amiberryGenerator.py", "passes_path"), GenericSites("fsuae/fsuaeGenerator.py", "passes_path"), GenericSites("mame/mameGenerator.py", "passes_path"), - GenericSites("openmsx/openmsxGenerator.py", "passes_path"), GenericSites("fpinball/fpinballGenerator.py", "passes_path"), GenericSites( "libretro/libretroRetroarchCustom.py", "passes_path", "libretro", emulator="retroarch" diff --git a/scripts/scraper/retrobat_launchers.py b/scripts/scraper/retrobat_launchers.py index 6c851eade..b15ea4f3d 100644 --- a/scripts/scraper/retrobat_launchers.py +++ b/scripts/scraper/retrobat_launchers.py @@ -1279,6 +1279,29 @@ def openmsx(sources: dict[str, Source]) -> list[Record]: ) if not records: raise LauncherPatternMissing(src.path, "biosFiles entries") + # OPENMSX_HOME is the user directory: openMSX reads share/systemroms below it. + home = src.find( + rf'string (\w+) = Path\.Combine\({BIOS}, "([^"]+)"\);', "OPENMSX_HOME folder" + ) + handed = src.find( + rf'ret\.EnvironmentVariables\["(OPENMSX_HOME)"\] = {home.group(1)};', + "OPENMSX_HOME environment", home.end(), + ) + records.append( + _record( + "openmsx", + "standalone", + "passes_path", + [], + source_ref( + (src.path, [src.lines(home.start(), home.end()), + src.lines(handed.start(), handed.end())]) + ), + search_dir=f"bios/{home.group(2)}", + passes_to=f"environment {handed.group(1)}", + fails_when_missing=False, + ) + ) return records diff --git a/scripts/scraper/retropie_launchers.py b/scripts/scraper/retropie_launchers.py index 65c03d28d..6a488b935 100644 --- a/scripts/scraper/retropie_launchers.py +++ b/scripts/scraper/retropie_launchers.py @@ -525,6 +525,7 @@ class PackageReader: self._links(links) self._outside_bios(events) self._working_directory(events) + self._reaching_links(events) return self.records @staticmethod @@ -828,6 +829,54 @@ class PackageReader: ) + def _reaching_links(self, events: list[Event]) -> None: + """The directories outside the BIOS one that a whole-directory link lies in. + + openmsx's moveConfigDir "$home/.openMSX" "$configdir/msx/openmsx" + puts the emulator's directory where the systemroms link to BIOS/openmsx + lies, and xroar's ./configure --prefix="$md_inst" builds the program + whose share/xroar/roms links to $biosdir. Neither line names the BIOS + directory, and each says where the link is read from. + """ + suffix = " is a symlink to it" + linked = [ + record.passes_to.removesuffix(suffix).rstrip("/") + for record in self.records + if record.kind == "links" and not record.names + and (record.passes_to or "").endswith(suffix) + ] + if not linked: + return + + def holds(directory: str) -> bool: + directory = directory.strip("\"'").rstrip("/") + return bool(directory) and any( + path == directory or path.startswith(directory + "/") for path in linked + ) + + for event in events: + if event.heredoc or not event.words or _mentions_bios(event.raw): + continue + command = event.words[0][0] + if command == "moveConfigDir" and len(event.words) >= 3: + link, real = event.words[1][0], event.words[2][0] + if holds(real): + self.record( + "links", [], [(event.path, event.line)], + search_dir=real, passes_to=f"{link}{suffix}", + fails_when_missing=False, depends_on_option=self._depends(event), + ) + elif command.endswith("configure"): + for word in event.words[1:]: + prefix = word[0].strip("\"'") + if prefix.startswith("--prefix=") and holds(prefix[len("--prefix="):]): + self.record( + "passes_path", [], [(event.path, event.line)], + search_dir=prefix[len("--prefix="):].strip("\"'"), + passes_to="${prefix} of configure --prefix", + fails_when_missing=False, depends_on_option=self._depends(event), + ) + def _working_directory(self, events: list[Event]) -> None: """The folder a generated launch script enters before it runs the emulator.""" if self.package.id not in WORKING_DIRECTORY: diff --git a/tests/test_launchers.py b/tests/test_launchers.py index 838704d04..59cb86c6b 100644 --- a/tests/test_launchers.py +++ b/tests/test_launchers.py @@ -29,6 +29,7 @@ from scraper.batocera_launchers import ( # noqa: E402 Configgen, Launcher, LauncherShapeError, + config_directories, duckstation, extract_launchers, hatari, @@ -521,6 +522,49 @@ class LexaloffleBinaries(unittest.TestCase): lexaloffle(lexaloffle_tree(text)) +OPENMSX = f"{GENERATORS}/openmsx/openmsxGenerator.py" +OPENMSX_FILEPOOL = ''' def generate(self, system, rom): + with settings_tcl.open("a") as file: + file.write("filepool add -path /userdata/bios/Machines -types system_rom -position 1\\n") + file.write("filepool add -path /userdata/bios/openmsx -types system_rom -position 2\\n")''' + + +class ConfigDirectories(unittest.TestCase): + """A BIOS directory written into the emulator's config, at batocera-43.1.""" + + def test_each_filepool_line_hands_its_directory(self): + cg = configgen({OPENMSX: placed({1: "class OpenmsxGenerator:", 86: OPENMSX_FILEPOOL})}) + records = [r.entry() for r in config_directories(cg, ("openmsx.openmsxGenerator",))] + self.assertEqual( + [(r["emulator"], r["kind"], r["search_dir"], r["source_ref"]) for r in records], + [ + ("openmsx", "passes_path", "/userdata/bios/Machines", f"{OPENMSX}:88"), + ("openmsx", "passes_path", "/userdata/bios/openmsx", f"{OPENMSX}:89"), + ], + ) + self.assertEqual( + records[1]["passes_to"], + "written: filepool add -path /userdata/bios/openmsx -types system_rom -position 2", + ) + + def test_a_saved_setting_hands_its_directory(self): + amiberry = f"{GENERATORS}/amiberry/amiberryGenerator.py" + text = placed({ + 1: "from pathlib import Path\n_BIOS_DIR = Path('/userdata/bios/amiga')", + 10: "def generate():\n amiberryconf.save('rom_path', _BIOS_DIR)", + }) + (record,) = config_directories(configgen({amiberry: text}), ("amiberry.amiberryGenerator",)) + self.assertEqual( + (record.search_dir, record.passes_to, record.source_ref), + ("/userdata/bios/amiga", "rom_path", f"{amiberry}:11"), + ) + + def test_a_generator_handing_no_bios_directory_raises(self): + cg = configgen({OPENMSX: "def generate():\n file.write('savestate x')\n"}) + with self.assertRaises(LauncherShapeError): + config_directories(cg, ("openmsx.openmsxGenerator",)) + + class ExtractionNeverEmpty(unittest.TestCase): def test_a_generator_of_unknown_shape_stops_the_whole_scrape(self): find = DUCKSTATION_FIND.replace("def find_bios(", "def locate_bios(") diff --git a/tests/test_launchers_retrobat.py b/tests/test_launchers_retrobat.py index 30b523bc3..5cd4a0ec9 100644 --- a/tests/test_launchers_retrobat.py +++ b/tests/test_launchers_retrobat.py @@ -145,6 +145,9 @@ OPENMSX = """ { "Philips_VG_8020", new string[] { "vg8020_basic-bios1.rom" } }, { "Pioneer_PX-7", new string[] { "px-7_basic-bios1.rom", "px-7_pbasic.rom" } } }; + + string sharePath = Path.Combine(AppConfig.GetFullPath("bios"), "openmsx"); + ret.EnvironmentVariables["OPENMSX_HOME"] = sharePath; """ DUCKSTATION = """ @@ -390,12 +393,21 @@ class NamesInLauncherOrder(unittest.TestCase): [ ("-machine Philips_VG_8020", ["vg8020_basic-bios1.rom"]), ("-machine Pioneer_PX-7", ["px-7_basic-bios1.rom", "px-7_pbasic.rom"]), + ("environment OPENMSX_HOME", []), ], ) self.assertEqual(records[0]["search_dir"], "bios/openmsx/share/systemroms") self.assertFalse(records[0]["case_insensitive"]) self.assertEqual(records[1]["source_ref"], f"{G}/OpenMSX.Generator.cs:1-4, 10") + def test_openmsx_home_is_handed_as_a_directory(self): + # OPENMSX_HOME is the user directory openMSX reads share/systemroms below. + records = rl.openmsx(_sources(**{f"{G}/OpenMSX.Generator.cs": OPENMSX})) + home = records[-1] + self.assertEqual(home["kind"], "passes_path") + self.assertEqual(home["search_dir"], "bios/openmsx") + self.assertEqual(home["source_ref"], f"{G}/OpenMSX.Generator.cs:13-14") + def test_duckstation_reads_the_standalone_feature_not_the_core(self): search, option = rl.duckstation( _sources(**{f"{G}/Duckstation.Generator.cs": DUCKSTATION, rl.FEATURES: FEATURES}) diff --git a/tests/test_launchers_retropie.py b/tests/test_launchers_retropie.py index 510aeaae7..b57cc60b8 100644 --- a/tests/test_launchers_retropie.py +++ b/tests/test_launchers_retropie.py @@ -315,6 +315,59 @@ UAE4ALL = module("uae4all", """ """, 60) +OPENMSX = module("openmsx", """ + function configure_openmsx() { + mkUserDir "$home/.openMSX/share/systemroms" + moveConfigDir "$home/.openMSX" "$configdir/msx/openmsx" + moveConfigDir "$configdir/msx/openmsx/share/systemroms" "$home/RetroPie/BIOS/openmsx" + moveConfigDir "$home/.unrelated" "$configdir/msx/other" + } +""", 74) + + +XROAR = module("xroar", """ + function build_xroar() { + ./configure --prefix="$md_inst" "${params[@]}" + } + + function configure_xroar() { + mkdir -p "$md_inst/share/xroar" + ln -snf "$biosdir" "$md_inst/share/xroar/roms" + } +""", 36) + + +class ReachingLinks(unittest.TestCase): + """The directory outside BIOS that a whole-directory link is read from.""" + + def test_openmsx_user_directory_holds_the_systemroms_link(self): + found = records({f"{EMULATORS}/openmsx.sh": OPENMSX}, "openmsx") + links = [r for r in found if r["kind"] == "links"] + self.assertEqual( + [(r["search_dir"], r["passes_to"], r["source_ref"]) for r in links], + [ + ( + "$home/RetroPie/BIOS/openmsx", + "$configdir/msx/openmsx/share/systemroms is a symlink to it", + f"{EMULATORS}/openmsx.sh:77", + ), + ( + "$configdir/msx/openmsx", + "$home/.openMSX is a symlink to it", + f"{EMULATORS}/openmsx.sh:76", + ), + ], + ) + + def test_xroar_install_prefix_holds_the_roms_link(self): + found = records({f"{EMULATORS}/xroar.sh": XROAR}, "xroar") + prefix = [r for r in found if r["kind"] == "passes_path"] + self.assertEqual( + [(r["search_dir"], r["passes_to"], r["source_ref"]) for r in prefix], + [("$md_inst", "${prefix} of configure --prefix", f"{EMULATORS}/xroar.sh:37")], + ) + + class WorkingDirectory(unittest.TestCase): def test_drastic_reads_below_the_folder_its_script_enters(self): found = records({f"{EMULATORS}/drastic.sh": DRASTIC}, "drastic") diff --git a/tests/test_standalone_links.py b/tests/test_standalone_links.py index ad83765cd..e0e4a9728 100644 --- a/tests/test_standalone_links.py +++ b/tests/test_standalone_links.py @@ -497,3 +497,43 @@ class Scrapers(unittest.TestCase): if __name__ == "__main__": unittest.main() + +class OpenmsxOnEachPlatform(unittest.TestCase): + """openMSX finds system ROMs by SHA1 in share/systemroms below its user + directory and in every filepool entry. RetroBat sets OPENMSX_HOME to + bios/openmsx, RetroPie links share/systemroms of its user directory to + BIOS/openmsx, Batocera and KNULLI add bios/Machines and bios/openmsx to + the filepool and list their own files in bios/openmsx.""" + + EXPECTED = { + "batocera": {"share/systemroms": ("", "openmsx")}, + "knulli": {"share/systemroms": ("", "openmsx")}, + "retrobat": {"": ("", "openmsx")}, + "retropie": {"share/systemroms": ("", "openmsx")}, + } + + @classmethod + def setUpClass(cls) -> None: + cls.profile = load_emulator_profiles(str(REPO_ROOT / "emulators"))["openmsx"] + + def test_each_platform_hands_openmsx_its_folder(self) -> None: + for platform, expected in self.EXPECTED.items(): + with self.subTest(platform=platform): + cfg = load_platform_config(platform, str(REPO_ROOT / "platforms")) + self.assertEqual( + standalone_bindings_for("openmsx", self.profile, standalone_set(cfg)), + expected, + ) + + def test_every_system_rom_lands_in_bios_openmsx(self) -> None: + for platform in self.EXPECTED: + with self.subTest(platform=platform): + cfg = load_platform_config(platform, str(REPO_ROOT / "platforms")) + (view, _standalone), = platform_layouts( + "openmsx", self.profile, standalone_set(cfg) + ) + for entry in view["files"]: + self.assertNotIn("unplaced", entry) + self.assertTrue( + entry["standalone_path"].startswith("openmsx/"), entry["standalone_path"] + )