diff --git a/emulators/lrps2-416291a.yml b/emulators/lrps2-416291a.yml new file mode 100644 index 000000000..5b4a7919f --- /dev/null +++ b/emulators/lrps2-416291a.yml @@ -0,0 +1,152 @@ +# LRPS2 at 416291a with Batocera's BIOS directory patch +# Source: https://github.com/libretro/parallel-ps2 +# Upstream: https://github.com/PCSX2/pcsx2 + +emulator: "LRPS2 (416291a, Batocera)" +type: libretro +core_classification: community_fork +bios_mode: agnostic +source: "https://github.com/libretro/parallel-ps2" +upstream: "https://github.com/PCSX2/pcsx2" +profiled_date: "2026-11-10" +source_commit: "416291ad7dc3caf5df4501c9249cbbe30cbef811" +pin_frozen: "Batocera, the only platform that ships it, builds this revision with package/batocera/emulators/retroarch/libretro/libretro-ps2/001.bios-dir.patch (Batocera package/batocera/emulators/retroarch/libretro/libretro-ps2/libretro-ps2.mk:7 at batocera-43.1). Later commits are not part of that build." +pin_build: + - platform: batocera + repo: "https://github.com/batocera-linux/batocera.linux" + revision: "batocera-43.1" + path: "package/batocera/emulators/retroarch/libretro/libretro-ps2/libretro-ps2.mk" + variable: LIBRETRO_PS2_VERSION + value: "416291ad7dc3caf5df4501c9249cbbe30cbef811" +core_version: null +version_basis: + kind: none +display_name: "Sony - PlayStation 2 (LRPS2 416291a for Batocera)" +cores: [pcsx2] +built_by: [batocera] +systems: [sony-playstation-2] + +bios_directory: "ps2/" +resources_directory: "pcsx2/resources/" + +notes: | + Batocera installs the build as pcsx2_libretro.so (Batocera + package/batocera/emulators/retroarch/libretro/libretro-ps2/libretro-ps2.mk:35-36) + and launches it as the libretro core pcsx2 of the ps2 system (Batocera + package/batocera/emulationstation/batocera-es-system/es_systems.yml:2293-2294). + The library version is the short commit hash git reports at build time + (cmake/BuildParameters.cmake:4-9, libretro/main.cpp:1423-1427). + + Its patch moves the BIOS scan from pcsx2/bios to ps2 below the system + directory (libretro/main.cpp:1806) and the BIOS folder default to + /userdata/bios/ps2, the same folder on Batocera (pcsx2/Pcsx2Config.cpp:1042). + The data root stays system/pcsx2, so resources are read from + pcsx2/resources and memory cards are written to pcsx2/memcards + (libretro/main.cpp:1910-1912). + + BIOS detection is filename-agnostic: ps2/ is scanned for any file between + 4 and 8 MB with a RESET and a ROMVER romdir entry, no hash is checked, and + the list fills the pcsx2_bios core option whose default is the first BIOS + found (libretro/main.cpp:1800-1838). retro_load_game refuses to start when + no BIOS was found (libretro/main.cpp:1927-1931). The companion files take + their path from the selected BIOS. + + The DEV9 hard disk image is user storage the core creates, not a file to + obtain. + +files: + - name: ".bin" + found_by: content + path: "ps2/.bin" + required: true + description: "PS2 BIOS binary" + min_size: 4194304 + max_size: 8388608 + validation: [size] + config_key: "pcsx2_bios" + source_ref: "source libretro/main.cpp:1800-1838, source libretro/main.cpp:360-365, source libretro/main.cpp:1927-1931, source pcsx2/ps2/BiosTools.cpp:31-32, 60-187, 231-323, source pcsx2/Pcsx2Config.cpp:994-1000, 1042, 1051-1061" + note: > + Any file of 4 to 8 MB in ps2/ whose romdir holds RESET and ROMVER. The + pcsx2_bios core option picks one, and the first valid image found is used + when the configured one is missing. + + - name: ".rom1" + path: "ps2/.rom1" + required: false + max_size: 4194304 + description: "DVD player ROM" + source_ref: "source pcsx2/ps2/BiosTools.cpp:189-213, 314, source pcsx2/MemoryTypes.h:24" + note: "Tries .rom1, then the BIOS path with its extension replaced by rom1. Skipped when neither exists." + + - name: ".rom2" + path: "ps2/.rom2" + required: false + max_size: 524288 + description: "Chinese ROM extension" + source_ref: "source pcsx2/ps2/BiosTools.cpp:189-213, 315, source pcsx2/MemoryTypes.h:25" + note: "Same naming as rom1. Present only on Chinese consoles." + + - name: ".nvm" + path: "ps2/.nvm" + required: false + hle_fallback: true + size: 1024 + description: "NVRAM / EEPROM data" + source_ref: "source pcsx2/CDVD/CDVD.cpp:59-60, source pcsx2/CDVD/CDVD.cpp:160-184" + note: > + Read at the BIOS path with the extension replaced by nvm. Region and + language defaults are filled in when the file is missing, short, or has + no language or region parameters. + + - name: ".mec" + path: "ps2/.mec" + required: false + hle_fallback: true + size: 4 + description: "Mechacon version" + source_ref: "source pcsx2/CDVD/CDVD.cpp:62, source pcsx2/CDVD/CDVD.cpp:186-196" + note: "Read at the BIOS path with the extension replaced by mec. Written as 0x00020603 when missing." + + - name: "GameIndex.yaml" + path: "pcsx2/resources/GameIndex.yaml" + required: false + installed_on: [batocera] + description: "Per-game fixes and settings database" + source_ref: "source pcsx2/GameDatabase.cpp:48, source pcsx2/GameDatabase.cpp:880-885, source libretro/main.cpp:2324-2361" + note: > + Read from pcsx2/resources at the first game lookup. Without it no game + fix applies. Batocera installs the copy of the built tree in + bios/pcsx2/resources (Batocera + package/batocera/emulators/retroarch/libretro/libretro-ps2/libretro-ps2.mk:37-39). + + - name: "cheats_ws.zip" + path: "pcsx2/resources/cheats_ws.zip" + required: false + description: "widescreen patches archive" + source_ref: "source pcsx2/VMManager.cpp:333-348, source libretro/main.cpp:2315-2322" + note: "Read when widescreen patches are enabled and pcsx2/cheats_ws holds no patch for the game." + + - name: "cheats_ni.zip" + path: "pcsx2/resources/cheats_ni.zip" + required: false + description: "no-interlacing patches archive" + source_ref: "source pcsx2/VMManager.cpp:366-383, source libretro/main.cpp:2315-2322" + note: "Read when no-interlacing patches are enabled and pcsx2/cheats_ni holds no patch for the game." + + - name: "eeprom.dat" + load_from: working_directory + required: false + hle_fallback: true + size: 64 + description: "DEV9 EEPROM" + source_ref: "source pcsx2/DEV9/DEV9.cpp:107-116, source pcsx2/DEV9/DEV9.cpp:152-167" + note: "Opened from the working directory when DEV9 starts and mapped over 64 bytes. A compiled-in image stands in when the file is missing." + + - name: "flash.dat" + load_from: working_directory + required: false + hle_fallback: true + size: 8650752 + description: "DEV9 flash memory with ECC" + source_ref: "source pcsx2/DEV9/flash.cpp:21-28, source pcsx2/DEV9/flash.cpp:74-101" + note: "Read from the working directory when DEV9 starts. The flash is blank (0xFF) when the file is missing." diff --git a/emulators/lrps2-9485a53.yml b/emulators/lrps2-9485a53.yml new file mode 100644 index 000000000..7ffef6c18 --- /dev/null +++ b/emulators/lrps2-9485a53.yml @@ -0,0 +1,151 @@ +# LRPS2 at 9485a53 with Recalbox's path patch +# Source: https://github.com/libretro/parallel-ps2 +# Upstream: https://github.com/PCSX2/pcsx2 + +emulator: "LRPS2 (9485a53, Recalbox)" +type: libretro +core_classification: community_fork +bios_mode: agnostic +source: "https://github.com/libretro/parallel-ps2" +upstream: "https://github.com/PCSX2/pcsx2" +profiled_date: "2026-11-10" +source_commit: "9485a53fa5aa2bff17e04518116107f81a8c82e3" +pin_frozen: "Recalbox, the only platform that ships it, builds this revision with package/libretro-ps2/0001-recalbox-paths.patch (Recalbox package/libretro-ps2/libretro-ps2.mk:9 at tag 10.1.1). Later commits are not part of that build." +pin_build: + - platform: recalbox + repo: "https://gitlab.com/recalbox/recalbox" + revision: "10.1.1" + path: "package/libretro-ps2/libretro-ps2.mk" + variable: LIBRETRO_PS2_VERSION + value: "9485a53fa5aa2bff17e04518116107f81a8c82e3" +core_version: null +version_basis: + kind: none +display_name: "Sony - PlayStation 2 (LRPS2 9485a53 for Recalbox)" +cores: [ps2] +built_by: [recalbox] +systems: [sony-playstation-2] + +bios_directory: "ps2/" +resources_directory: "ps2/resources/" + +notes: | + Recalbox installs the build as ps2_libretro.so (Recalbox + package/libretro-ps2/libretro-ps2.mk:31-33) and lists it as the libretro + core ps2 of the ps2 system (Recalbox + package/recalbox-romfs2/systems/ps2/system.ini:65-69). + The library version is the short commit hash git reports at build time + (cmake/BuildParameters.cmake:4-9, libretro/main.cpp:1424-1428). + + Its patch moves the BIOS scan from pcsx2/bios to ps2 below the system + directory (libretro/main.cpp:1807), the data root from system/pcsx2 to + system/ps2, so resources are read from ps2/resources + (libretro/main.cpp:1911-1913), the BIOS folder default to + /recalbox/share/bios/ps2, the same folder on Recalbox, and memory cards to + /recalbox/share/saves/ps2 (pcsx2/Pcsx2Config.cpp:1042-1043, 1061-1062). + + BIOS detection is filename-agnostic: ps2/ is scanned for any file between + 4 and 8 MB with a RESET and a ROMVER romdir entry, no hash is checked, and + the list fills the pcsx2_bios core option whose default is the first BIOS + found (libretro/main.cpp:1801-1839). retro_load_game refuses to start when + no BIOS was found (libretro/main.cpp:1928-1932). The companion files take + their path from the selected BIOS. + + The DEV9 hard disk image is user storage the core creates, not a file to + obtain. + +files: + - name: ".bin" + found_by: content + path: "ps2/.bin" + required: true + description: "PS2 BIOS binary" + min_size: 4194304 + max_size: 8388608 + validation: [size] + config_key: "pcsx2_bios" + source_ref: "source libretro/main.cpp:1801-1839, source libretro/main.cpp:360-365, source libretro/main.cpp:1928-1932, source pcsx2/ps2/BiosTools.cpp:31-32, 60-187, 231-323, source pcsx2/Pcsx2Config.cpp:994-1000, 1042, 1051-1061" + note: > + Any file of 4 to 8 MB in ps2/ whose romdir holds RESET and ROMVER. The + pcsx2_bios core option picks one, and the first valid image found is used + when the configured one is missing. + + - name: ".rom1" + path: "ps2/.rom1" + required: false + max_size: 4194304 + description: "DVD player ROM" + source_ref: "source pcsx2/ps2/BiosTools.cpp:189-213, 314, source pcsx2/MemoryTypes.h:24" + note: "Tries .rom1, then the BIOS path with its extension replaced by rom1. Skipped when neither exists." + + - name: ".rom2" + path: "ps2/.rom2" + required: false + max_size: 524288 + description: "Chinese ROM extension" + source_ref: "source pcsx2/ps2/BiosTools.cpp:189-213, 315, source pcsx2/MemoryTypes.h:25" + note: "Same naming as rom1. Present only on Chinese consoles." + + - name: ".nvm" + path: "ps2/.nvm" + required: false + hle_fallback: true + size: 1024 + description: "NVRAM / EEPROM data" + source_ref: "source pcsx2/CDVD/CDVD.cpp:59-60, source pcsx2/CDVD/CDVD.cpp:160-184" + note: > + Read at the BIOS path with the extension replaced by nvm. Region and + language defaults are filled in when the file is missing, short, or has + no language or region parameters. + + - name: ".mec" + path: "ps2/.mec" + required: false + hle_fallback: true + size: 4 + description: "Mechacon version" + source_ref: "source pcsx2/CDVD/CDVD.cpp:62, source pcsx2/CDVD/CDVD.cpp:186-196" + note: "Read at the BIOS path with the extension replaced by mec. Written as 0x00020603 when missing." + + - name: "GameIndex.yaml" + path: "ps2/resources/GameIndex.yaml" + required: false + installed_on: [recalbox] + description: "Per-game fixes and settings database" + source_ref: "source pcsx2/GameDatabase.cpp:48, source pcsx2/GameDatabase.cpp:880-885, source libretro/main.cpp:2265-2302" + note: > + Read from ps2/resources at the first game lookup. Without it no game fix + applies. Recalbox installs the copy of the built tree in + bios/ps2/resources (Recalbox package/libretro-ps2/libretro-ps2.mk:34-37). + + - name: "cheats_ws.zip" + path: "ps2/resources/cheats_ws.zip" + required: false + description: "widescreen patches archive" + source_ref: "source pcsx2/VMManager.cpp:333-348, source libretro/main.cpp:2256-2263" + note: "Read when widescreen patches are enabled and ps2/cheats_ws holds no patch for the game." + + - name: "cheats_ni.zip" + path: "ps2/resources/cheats_ni.zip" + required: false + description: "no-interlacing patches archive" + source_ref: "source pcsx2/VMManager.cpp:366-383, source libretro/main.cpp:2256-2263" + note: "Read when no-interlacing patches are enabled and ps2/cheats_ni holds no patch for the game." + + - name: "eeprom.dat" + load_from: working_directory + required: false + hle_fallback: true + size: 64 + description: "DEV9 EEPROM" + source_ref: "source pcsx2/DEV9/DEV9.cpp:107-116, source pcsx2/DEV9/DEV9.cpp:152-167" + note: "Opened from the working directory when DEV9 starts and mapped over 64 bytes. A compiled-in image stands in when the file is missing." + + - name: "flash.dat" + load_from: working_directory + required: false + hle_fallback: true + size: 8650752 + description: "DEV9 flash memory with ECC" + source_ref: "source pcsx2/DEV9/flash.cpp:21-28, source pcsx2/DEV9/flash.cpp:74-101" + note: "Read from the working directory when DEV9 starts. The flash is blank (0xFF) when the file is missing." diff --git a/scripts/common.py b/scripts/common.py index 09d2900e2..7fc2bdf04 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -3308,6 +3308,11 @@ def _platform_profiles( } index = upstream_profile_index(profiles) platform = str(config.get("_key") or "") + dual_names = {str(c) for c in config.get("dual_mode_cores") or []} + + def build_modes(p: dict) -> set[str]: + kind = str(p.get("type", "")) + return {mode for mode in ("standalone", "libretro") if mode in kind} def designated(name: str) -> set[str]: # A build the platform compiles itself (built_by) is what its @@ -3319,10 +3324,21 @@ def _platform_profiles( p for p in index.get(name, ()) if platform and platform in (profiles[p].get("built_by") or []) } - if own: - return own preferred = preferred_profile(index, name) - return {preferred} if preferred is not None else set() + if not own: + return {preferred} if preferred is not None else set() + # The own builds stand for the modes they are built in only. + # Batocera builds LRPS2 as pcsx2_libretro.so and launches + # standalone PCSX2 under the same name. + own_modes = set().union(*(build_modes(profiles[p]) for p in own)) + if ( + name in dual_names + and preferred is not None + and preferred not in own + and not build_modes(profiles[preferred]) & own_modes + ): + own.add(preferred) + return own result = {profile for c in core_set for profile in designated(c)} # A name the platform launches both ways designates its libretro diff --git a/tests/test_batocera_cores.py b/tests/test_batocera_cores.py index eeeff5203..890cf67b3 100644 --- a/tests/test_batocera_cores.py +++ b/tests/test_batocera_cores.py @@ -160,5 +160,50 @@ class APlatformsOwnBuildWinsItsName(unittest.TestCase): self.assertEqual(resolve_platform_cores(retropie, profiles), {"uae4all"}) +class APatchedLibretroBuildTakesItsNameOnly(unittest.TestCase): + """Batocera and Recalbox compile LRPS2 with a patch that reads the BIOS + from ps2/ instead of pcsx2/bios/. Their own build replaces the buildbot + LRPS2 under the name they ship, and Batocera's standalone PCSX2, under + the same name, stays.""" + + def test_own_libretro_build_keeps_the_standalone_of_a_dual_name(self): + from common import resolve_platform_cores + + profiles = { + "pcsx2": {"type": "standalone", "source": "s", "cores": ["pcsx2"]}, + "lrps2": {"type": "libretro", "source": "l", "cores": ["lrps2", "pcsx2"]}, + "lrps2-own": {"type": "libretro", "source": "l", "cores": ["pcsx2"], + "built_by": ["x"]}, + } + config = {"_key": "x", "cores": ["pcsx2"], "standalone_cores": ["pcsx2"], + "dual_mode_cores": ["pcsx2"]} + self.assertEqual(resolve_platform_cores(config, profiles), {"pcsx2", "lrps2-own"}) + del config["dual_mode_cores"] + self.assertEqual(resolve_platform_cores(config, profiles), {"lrps2-own"}) + + def test_the_registry_resolves_each_platform_build(self): + from common import load_emulator_profiles, load_platform_config, resolve_platform_cores + + profiles = load_emulator_profiles(str(REPO_ROOT / "emulators")) + platforms = str(REPO_ROOT / "platforms") + + def ps2_cores(platform: str) -> set[str]: + config = load_platform_config(platform, platforms) + return { + c for c in resolve_platform_cores(config, profiles) + if c.startswith(("lrps2", "pcsx2", "pcee2")) + } + + self.assertEqual(ps2_cores("batocera"), {"lrps2-416291a", "pcsx2", "pcee2"}) + self.assertEqual(ps2_cores("recalbox"), {"lrps2-9485a53", "pcsx2"}) + for platform in ("retroarch", "retrobat", "emudeck", "retrodeck"): + with self.subTest(platform=platform): + cores = ps2_cores(platform) + self.assertIn("lrps2", cores) + self.assertFalse({"lrps2-416291a", "lrps2-9485a53"} & cores) + self.assertEqual(profiles["lrps2-416291a"]["files"][0]["path"], "ps2/.bin") + self.assertEqual(profiles["lrps2-9485a53"]["files"][0]["path"], "ps2/.bin") + + if __name__ == "__main__": unittest.main() diff --git a/wiki/adding-a-platform.md b/wiki/adding-a-platform.md index 1154990e3..446d020bf 100644 --- a/wiki/adding-a-platform.md +++ b/wiki/adding-a-platform.md @@ -195,7 +195,10 @@ resolved. Three strategies exist: - **Explicit list**: `cores: [beetle_psx, dolphin, ...]` - match by profile key name or by any name in a profile's `cores:`. When a profile's `built_by` names this - platform, that profile answers for the name (ROCKNIX's `yabadabadoo`). Used by + platform, that profile answers for the name (ROCKNIX's `yabadabadoo`). On a + `dual_mode_cores` name it answers for its own build mode only: Batocera's + patched LRPS2 (`lrps2-416291a`) takes the libretro `pcsx2` and the standalone + PCSX2 stays. Used by Batocera, Recalbox, RetroBat, RomM, KNULLI, AmberELEC, EmuELEC, MinUI, NextUI, MyMinUI, ROCKNIX, RetroPie, EmuDeck, RetroDECK. A list that names `retroarch` (the last three) also takes every libretro profile, as `all_libretro` does,