diff --git a/Config.in b/Config.in index 83412b96d0c..e469eb41a56 100644 --- a/Config.in +++ b/Config.in @@ -427,6 +427,7 @@ menu "Emulators" source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/emulators/nanoboyadvance/Config.in" source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/emulators/shadps4-qtlauncher/Config.in" source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/emulators/drastic/Config.in" + source "$BR2_EXTERNAL_BATOCERA_PATH/package/batocera/emulators/pcsx2x6/Config.in" endmenu menu "WINE" diff --git a/batocera-Changelog.md b/batocera-Changelog.md index 8c28c471b32..f1f5526e863 100644 --- a/batocera-Changelog.md +++ b/batocera-Changelog.md @@ -1,6 +1,7 @@ # 2026/xx/xx - batocera.linux 44 - xxxx ### Special Notes - ShadPS4 has moved from the no longer maintained ShadPS4 Plus to standard ShadPS4 +- The Play! emulator (and Libretro variant) has been removed in favor of PCSX2x6 for Namco2x6 systems as well as removed as a PS2 emulator. ### Hardware - Anbernic RG-DS initial support - Anbernic RG Vita Pro initial support @@ -22,6 +23,7 @@ - Box64 userland x86_64 emulator for aarch64 - Legion Go / Go 2 LED controller support - Libretro-Azahar +- PCSX2x6 emulator for Namco2x6 systems (see the _info.txt file for rom details) - Xenia-Edge for x864_64 and select aarch64 devices ### Fixed - BCC menu not showing on SM8250 devices (i.e. Retroid Pocket 5) diff --git a/package/batocera/core/batocera-configgen/configgen/configgen/generators/pcsx2x6/__init__.py b/package/batocera/core/batocera-configgen/configgen/configgen/generators/pcsx2x6/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/package/batocera/core/batocera-configgen/configgen/configgen/generators/pcsx2x6/pcsx2x6Generator.py b/package/batocera/core/batocera-configgen/configgen/configgen/generators/pcsx2x6/pcsx2x6Generator.py new file mode 100644 index 00000000000..c49509cd79c --- /dev/null +++ b/package/batocera/core/batocera-configgen/configgen/configgen/generators/pcsx2x6/pcsx2x6Generator.py @@ -0,0 +1,846 @@ +from __future__ import annotations + +import logging +import re +import shutil +import time +from pathlib import Path +from typing import TYPE_CHECKING, ClassVar, Final + +from batocera_common.configparser import CaseSensitiveConfigParser + +from ... import Command +from ...batoceraPaths import ( + BIOS, + CACHE, + CONFIGS, + DATAINIT_DIR, + ROMS, + configure_emulator, + ensure_parents_and_open, + mkdir_if_not_exists, +) +from ...controller import Controllers, generate_sdl_game_controller_config, write_sdl_controller_db +from ...utils import vulkan +from ..Generator import Generator + +if TYPE_CHECKING: + from collections.abc import Mapping + + from ...config import SystemConfig + from ...Emulator import Emulator + from ...gun import Guns + from ...input import Input + from ...types import DeviceInfoMapping, HotkeysContext, Resolution + +_logger = logging.getLogger(__name__) + +_PCSX2X6_BIN_DIR: Final = Path("/usr/pcsx2x6/bin") +_PCSX2X6_RESOURCES_DIR: Final = _PCSX2X6_BIN_DIR / "resources" +_PCSX2X6_CONFIG: Final = CONFIGS / "PCSX2x6" +_PCSX2X6_BIOS: Final = BIOS / "namco2x6" + +class Pcsx2x6Generator(Generator): + + wheelTypeMapping: ClassVar = { + "DrivingForce": "0", + "DrivingForcePro": "1", + "GTForce": "3" + } + + def getHotkeysContext(self) -> HotkeysContext: + return { + "name": "pcsx2x6", + "keys": { "exit": ["KEY_LEFTALT", "KEY_F4"], + "menu": "KEY_ESC", + "pause": "KEY_ESC", + "save_state": "KEY_F1", + "restore_state": "KEY_F3", + "previous_slot": [ "KEY_LEFTSHIFT", "KEY_F2" ], + "next_slot": "KEY_F2" + } + } + + def getInGameRatio(self, config, gameResolution, rom): + config_ratio = getGfxRatioFromConfig(config, gameResolution) + if config_ratio == "16:9" or (config_ratio == "Stretch" and gameResolution["width"] / float(gameResolution["height"]) > ((16.0 / 9.0) - 0.1)): + return 16/9 + return 4/3 + + @staticmethod + def isPlayingWithWheel(system: Emulator, wheels: DeviceInfoMapping): + return bool(system.config.use_wheels and wheels) + + @staticmethod + def useEmulatorWheels(playingWithWheel: bool, wheel_type: str): + if playingWithWheel is False: + return False + # the virtual type is the virtual wheel that use a physical wheel to manipulate the pad + return wheel_type != "Virtual" + + @staticmethod + def getWheelType(metadata: Mapping[str, str], playingWithWheel: bool, config: SystemConfig): + wheel_type = "Virtual" + if playingWithWheel is False: + return wheel_type + if "wheel_type" in metadata: + wheel_type = metadata["wheel_type"] + if config_wheel_type := config.get("pcsx2x6_wheel_type"): + wheel_type = config_wheel_type + if wheel_type not in Pcsx2x6Generator.wheelTypeMapping: + wheel_type = "Virtual" + return wheel_type + + def generate(self, system, rom, playersControllers, metadata, guns, wheels, gameResolution): + pcsx2Patches = _PCSX2X6_BIOS / "patches.zip" + + # Remove older config files if present + inisDir = _PCSX2X6_CONFIG / "inis" + files_to_remove = ["PCSX2_ui.ini", "PCSX2_vm.ini", "GS.ini"] + for filename in files_to_remove: + file_path = inisDir / filename + if file_path.exists(): + file_path.unlink() + + playingWithWheel = Pcsx2x6Generator.isPlayingWithWheel(system, wheels) + + # Config files + configureReg(_PCSX2X6_CONFIG) + configureINI(_PCSX2X6_CONFIG, _PCSX2X6_BIOS, system, rom, playersControllers, metadata, guns, wheels, playingWithWheel) + configureAudio(_PCSX2X6_CONFIG) + + # write our own game_controller_db.txt file before launching the game + dbfile = _PCSX2X6_CONFIG / "game_controller_db.txt" + write_sdl_controller_db(playersControllers, dbfile) + + commandArray = ["/usr/pcsx2x6/bin/pcsx2x6-qt"] if configure_emulator(rom) else \ + ["/usr/pcsx2x6/bin/pcsx2x6-qt", "-nogui", rom] + + with Path("/proc/cpuinfo").open() as cpuinfo: + if not re.search(r'^flags\s*:.*\ssse4_1\W', cpuinfo.read(), re.MULTILINE): + _logger.warning("CPU does not support SSE4.1 which is required by pcsx2x6. The emulator will likely crash with SIGILL (illegal instruction).") + + envcmd: dict[str, str | Path] = { + "XDG_CONFIG_HOME": CONFIGS + } + + # wheels won't work correctly when SDL_GAMECONTROLLERCONFIG is set. excluding wheels from SDL_GAMECONTROLLERCONFIG doesn't fix too. + # wheel metadata + if not Pcsx2x6Generator.useEmulatorWheels(playingWithWheel, Pcsx2x6Generator.getWheelType(metadata, playingWithWheel, system.config)): + envcmd["SDL_GAMECONTROLLERCONFIG"] = generate_sdl_game_controller_config(playersControllers) + + # ensure we have the patches.zip file to avoid message. + mkdir_if_not_exists(pcsx2Patches.parent) + if not pcsx2Patches.exists(): + shutil.copy(DATAINIT_DIR / "bios" / "namco2x6" / "patches.zip", pcsx2Patches) + + # state_slot option + if state_filename := system.config.get('state_filename'): + commandArray.extend(["-statefile", state_filename]) + + if state_slot := system.config.get_str('state_slot'): + commandArray.extend(["-stateindex", state_slot]) + + return Command.Command( + array=commandArray, + env=envcmd + ) + +def getGfxRatioFromConfig(config: SystemConfig, gameResolution: Resolution): + # 2: 4:3 ; 1: 16:9 + ratio = config.get("pcsx2x6_ratio") + if ratio == "16:9": + return "16:9" + if ratio == "full": + return "Stretch" + return "4:3" + +def configureReg(config_directory: Path) -> None: + with ensure_parents_and_open(config_directory / "PCSX2-reg.ini", "w") as f: + f.write("DocumentsFolderMode=User\n") + f.write(f"CustomDocumentsFolder={_PCSX2X6_BIN_DIR}\n") + f.write("UseDefaultSettingsFolder=enabled\n") + f.write(f"SettingsFolder={config_directory / 'inis'}\n") + f.write(f"Install_Dir={_PCSX2X6_BIN_DIR}\n") + f.write("RunWizard=0\n") + +def configureAudio(config_directory: Path) -> None: + configFileName = config_directory / 'inis' / "spu2-x.ini" + mkdir_if_not_exists(configFileName.parent) + + # Keep the custom files + if configFileName.exists(): + return + + f = configFileName.open("w") + f.write("[MIXING]\n") + f.write("Interpolation=1\n") + f.write("Disable_Effects=0\n") + f.write("[OUTPUT]\n") + f.write("Output_Module=SDLAudio\n") + f.write("[PORTAUDIO]\n") + f.write("HostApi=ALSA\n") + f.write("Device=default\n") + f.write("[SDL]\n") + f.write("HostApi=alsa\n") + f.close() + +def configureINI(config_directory: Path, bios_directory: Path, system: Emulator, rom: Path, controllers: Controllers, metadata: Mapping[str, str], guns: Guns, wheels: DeviceInfoMapping, playingWithWheel: bool) -> None: + configFileName = config_directory / 'inis' / "PCSX2.ini" + + mkdir_if_not_exists(configFileName.parent) + + if not configFileName.is_file(): + with configFileName.open("w") as f: + f.write("[UI]\n") + + pcsx2x6INIConfig = CaseSensitiveConfigParser(interpolation=None) + + if configFileName.is_file(): + pcsx2x6INIConfig.read(configFileName) + + ## [UI] + if not pcsx2x6INIConfig.has_section("UI"): + pcsx2x6INIConfig.add_section("UI") + + # set the settings we want always enabled + pcsx2x6INIConfig.set("UI", "SettingsVersion", "1") + pcsx2x6INIConfig.set("UI", "InhibitScreensaver", "true") + pcsx2x6INIConfig.set("UI", "ConfirmShutdown", "false") + pcsx2x6INIConfig.set("UI", "StartPaused", "false") + pcsx2x6INIConfig.set("UI", "PauseOnFocusLoss", "false") + pcsx2x6INIConfig.set("UI", "StartFullscreen", "true") + pcsx2x6INIConfig.set("UI", "HideMouseCursor", "true") + pcsx2x6INIConfig.set("UI", "RenderToSeparateWindow", "false") + pcsx2x6INIConfig.set("UI", "HideMainWindowWhenRunning", "true") + pcsx2x6INIConfig.set("UI", "DoubleClickTogglesFullscreen", "false") + + # clear to not have the window anywhere when switching from multi to single screen and vice and versa + for opt in ["MainWindowGeometry", "MainWindowState", "DisplayWindowGeometry"]: + if pcsx2x6INIConfig.has_section("UI") and pcsx2x6INIConfig.has_option("UI", opt): + pcsx2x6INIConfig.remove_option("UI", opt) + + ## [Folders] + if not pcsx2x6INIConfig.has_section("Folders"): + pcsx2x6INIConfig.add_section("Folders") + + # remove inconsistent SaveStates casing if it exists + pcsx2x6INIConfig.remove_option("Folders", "SaveStates") + + # set the folders we want + pcsx2x6INIConfig.set("Folders", "Bios", "../../../bios/namco2x6") + pcsx2x6INIConfig.set("Folders", "Snapshots", "../../../screenshots") + pcsx2x6INIConfig.set("Folders", "Savestates", "../../../saves/namco2x6/pcsx2x6/sstates") + pcsx2x6INIConfig.set("Folders", "MemoryCards", "../../../saves/namco2x6/pcsx2x6") + pcsx2x6INIConfig.set("Folders", "Logs", "../../logs") + pcsx2x6INIConfig.set("Folders", "Cheats", "../../../cheats/namco2x6") + pcsx2x6INIConfig.set("Folders", "CheatsWS", "../../../cheats/namco2x6/cheats_ws") + pcsx2x6INIConfig.set("Folders", "CheatsNI", "../../../cheats/namco2x6/cheats_ni") + pcsx2x6INIConfig.set("Folders", "Cache", "../../cache/namco2x6") + pcsx2x6INIConfig.set("Folders", "Textures", "textures") + pcsx2x6INIConfig.set("Folders", "InputProfiles", "inputprofiles") + pcsx2x6INIConfig.set("Folders", "Videos", "../../../saves/namco2x6/pcsx2x6/videos") + + # create cache folder + mkdir_if_not_exists(CACHE / "namco2x6") + + ## [EmuCore] + if not pcsx2x6INIConfig.has_section("EmuCore"): + pcsx2x6INIConfig.add_section("EmuCore") + + # set the settings we want always enabled + pcsx2x6INIConfig.set("EmuCore", "EnableDiscordPresence", "false") + + # Cheats + pcsx2x6INIConfig.set("EmuCore", "EnableCheats", system.config.get('pcsx2x6_cheats', "false")) + + # Widescreen Patches + pcsx2x6INIConfig.set("EmuCore", "EnableWideScreenPatches", system.config.get("pcsx2x6_EnableWideScreenPatches", "false")) + + # No-interlacing Patches + pcsx2x6INIConfig.set("EmuCore", "EnableNoInterlacingPatches", system.config.get("pcsx2x6_interlacing_patches", "false")) + + ## [Achievements] + if not pcsx2x6INIConfig.has_section("Achievements"): + pcsx2x6INIConfig.add_section("Achievements") + pcsx2x6INIConfig.set("Achievements", "Enabled", "false") + if system.config.get_bool('retroachievements'): + username = system.config.get('retroachievements.username', "") + token = system.config.get('retroachievements.token', "") + pcsx2x6INIConfig.set("Achievements", "Enabled", "true") + pcsx2x6INIConfig.set("Achievements", "Username", username) + pcsx2x6INIConfig.set("Achievements", "Token", token) + pcsx2x6INIConfig.set("Achievements", "LoginTimestamp", str(int(time.time()))) + pcsx2x6INIConfig.set("Achievements", "ChallengeMode", system.config.get_bool('retroachievements.hardcore', return_values=("true", "false"))) + pcsx2x6INIConfig.set("Achievements", "PrimedIndicators", system.config.get_bool('retroachievements.challenge_indicators', return_values=("true", "false"))) + pcsx2x6INIConfig.set("Achievements", "RichPresence", system.config.get_bool('retroachievements.richpresence', return_values=("true", "false"))) + pcsx2x6INIConfig.set("Achievements", "Leaderboards", system.config.get_bool('retroachievements.leaderboards', return_values=("true", "false"))) + pcsx2x6INIConfig.set("Achievements", "EncoreMode", system.config.get_bool('retroachievements.encore', return_values=("true", "false"))) + pcsx2x6INIConfig.set("Achievements", "UnofficialTestMode", system.config.get_bool('retroachievements.unofficial', return_values=("true", "false"))) + # set other settings + pcsx2x6INIConfig.set("Achievements", "TestMode", "false") + pcsx2x6INIConfig.set("Achievements", "UnofficialTestMode", "false") + pcsx2x6INIConfig.set("Achievements", "Notifications", "true") + pcsx2x6INIConfig.set("Achievements", "SoundEffects", "true") + + ## [Filenames] + if not pcsx2x6INIConfig.has_section("Filenames"): + pcsx2x6INIConfig.add_section("Filenames") + + # Read rom metadata/info file to find the platform value + platform = "256" + if rom.is_file(): + try: + with rom.open("r", encoding="utf-8", errors="ignore") as f: + for line in f: + cleaned = line.strip() + if cleaned.startswith("platform"): + parts = [p.strip() for p in cleaned.split("=", 1)] + if len(parts) == 2 and parts[0] == "platform": + platform = parts[1] + break + except Exception as e: + _logger.warning("Could not read platform from ROM file %s: %s", rom, e) + + # Use r27v1602f.7d for 246 platform, default to r27v1602f.8g (for 256 or others) + bios_file = "r27v1602f.7d" if platform == "246" else "r27v1602f.8g" + pcsx2x6INIConfig.set("Filenames", "BIOS", bios_file) + + ## [EMUCORE/GS] + if not pcsx2x6INIConfig.has_section("EmuCore/GS"): + pcsx2x6INIConfig.add_section("EmuCore/GS") + + ## [EMUCORE/Speedhacks] + if not pcsx2x6INIConfig.has_section("EmuCore/Speedhacks"): + pcsx2x6INIConfig.add_section("EmuCore/Speedhacks") + + # Renderer + # Check Vulkan first to be sure + if vulkan.is_available(): + _logger.debug("Vulkan driver is available on the system.") + renderer = "-1" + + if gfxbackend := system.config.get("pcsx2x6_gfxbackend"): + if gfxbackend == "12": + _logger.debug("User selected OpenGL") + if gfxbackend == "13": + _logger.debug("User selected Software! Man you must have a fast CPU!") + elif gfxbackend == "14": + _logger.debug("User selected Vulkan") + if vulkan.has_discrete_gpu(): + _logger.debug("A discrete GPU is available on the system. We will use that for performance") + discrete_name = vulkan.get_discrete_gpu_name() + if discrete_name: + _logger.debug("Using Discrete GPU Name: %s for PCSX2x6", discrete_name) + pcsx2x6INIConfig.set("EmuCore/GS", "Adapter", discrete_name) + else: + _logger.debug("Couldn't get discrete GPU Name") + pcsx2x6INIConfig.set("EmuCore/GS", "Adapter", "(Default)") + else: + _logger.debug("Discrete GPU is not available on the system. Using default.") + pcsx2x6INIConfig.set("EmuCore/GS", "Adapter", "(Default)") + renderer = gfxbackend + else: + _logger.debug("User selected to Automatic") + + pcsx2x6INIConfig.set("EmuCore/GS", "Renderer", renderer) + else: + _logger.debug("Vulkan driver is not available on the system. Falling back to Automatic") + pcsx2x6INIConfig.set("EmuCore/GS", "Renderer", "-1") + + # Ratio + pcsx2x6INIConfig.set("EmuCore/GS", "AspectRatio", system.config.get("pcsx2x6_ratio", "Auto 4:3/3:2")) + + # Vsync + pcsx2x6INIConfig.set("EmuCore/GS","VsyncEnable", system.config.get("pcsx2x6_vsync", "0")) + + # Resolution + pcsx2x6INIConfig.set("EmuCore/GS", "upscale_multiplier", system.config.get("pcsx2x6_resolution", "1")) + + # FXAA + pcsx2x6INIConfig.set("EmuCore/GS", "fxaa", system.config.get("pcsx2x6_fxaa", "false")) + + # FMV Ratio + pcsx2x6INIConfig.set("EmuCore/GS", "FMVAspectRatioSwitch", system.config.get("pcsx2x6_fmv_ratio", "Auto 4:3/3:2")) + + # Mipmapping + pcsx2x6INIConfig.set("EmuCore/GS", "mipmap_hw", system.config.get("pcsx2x6_mipmapping", "-1")) + + # Trilinear Filtering + pcsx2x6INIConfig.set("EmuCore/GS", "TriFilter", system.config.get("pcsx2x6_trilinear_filtering", "-1")) + + # Anisotropic Filtering + pcsx2x6INIConfig.set("EmuCore/GS", "MaxAnisotropy", system.config.get("pcsx2x6_anisotropic_filtering", "0")) + + # Dithering + pcsx2x6INIConfig.set("EmuCore/GS", "dithering_ps2", system.config.get("pcsx2x6_dithering", "2")) + + # Texture Preloading + pcsx2x6INIConfig.set("EmuCore/GS", "texture_preloading", system.config.get("pcsx2x6_texture_loading", "2")) + + # Deinterlacing + pcsx2x6INIConfig.set("EmuCore/GS", "deinterlace_mode", system.config.get("pcsx2x6_deinterlacing", "0")) + + # Anti-Blur + pcsx2x6INIConfig.set("EmuCore/GS", "pcrtc_antiblur", system.config.get("pcsx2x6_blur", "true")) + + # Integer Scaling + pcsx2x6INIConfig.set("EmuCore/GS", "IntegerScaling", system.config.get("pcsx2x6_scaling", "false")) + + # Blending Accuracy + pcsx2x6INIConfig.set("EmuCore/GS", "accurate_blending_unit", system.config.get("pcsx2x6_blending", "1")) + + # Texture Filtering + pcsx2x6INIConfig.set("EmuCore/GS", "filter", system.config.get("pcsx2x6_texture_filtering", "2")) + + # Bilinear Filtering + pcsx2x6INIConfig.set("EmuCore/GS", "linear_present_mode", system.config.get("pcsx2x6_bilinear_filtering", "1")) + + # Load Texture Replacements + pcsx2x6INIConfig.set("EmuCore/GS", "LoadTextureReplacements", system.config.get("pcsx2x6_texture_replacements", "false")) + + # OSD messages + osd_enabled = system.config.get("pcsx2x6_osd_messages", "true") + pcsx2x6INIConfig.set("EmuCore/GS", "OsdShowMessages", osd_enabled) + + # OSD Messages Position + pcsx2x6INIConfig.set("EmuCore/GS", "OsdMessagesPos", "0" if osd_enabled == "false" else system.config.get("pcsx2x6_osd_messages_position", "2")) + + # OSD Performance Position + pcsx2x6INIConfig.set("EmuCore/GS", "OsdPerformancePos", system.config.get("pcsx2x6_osd_performance_position", "0")) + + # Crop Overscan + cropOverscan = "3" if system.config.get_bool("pcsx2x6_overscan") else "0" + + pcsx2x6INIConfig.set("EmuCore/GS", "CropLeft", cropOverscan) + pcsx2x6INIConfig.set("EmuCore/GS", "CropTop", cropOverscan) + pcsx2x6INIConfig.set("EmuCore/GS", "CropRight", cropOverscan) + pcsx2x6INIConfig.set("EmuCore/GS", "CropBottom", cropOverscan) + + # TV Shader + pcsx2x6INIConfig.set("EmuCore", "TVShader", system.config.get("pcsx2x6_shaderset", "0")) + + pcsx2x6INIConfig.set("EmuCore", "AutoIncrementSlot", system.config.get_bool('incrementalsavestates', True, return_values=("true", "false"))) + + pcsx2x6INIConfig.set("EmuCore", "SaveStateOnShutdown", system.config.get_bool('autosave', return_values=("true", "false"))) + + # VU thread speedhack + pcsx2x6INIConfig.set("EmuCore/Speedhacks", "vuThread", system.config.get_bool("pcsx2x6_vuthread", return_values=("true", "false"))) + + # EE Cycle Rate speedhack + pcsx2x6INIConfig.set("EmuCore/Speedhacks", "EECycleRate", system.config.get("pcsx2x6_eecyclerate", "0")) + + ## [InputSources] + if not pcsx2x6INIConfig.has_section("InputSources"): + pcsx2x6INIConfig.add_section("InputSources") + + pcsx2x6INIConfig.set("InputSources", "Keyboard", "true") + pcsx2x6INIConfig.set("InputSources", "Mouse", "true") + pcsx2x6INIConfig.set("InputSources", "SDL", "true") + + ## [Hotkeys] + if not pcsx2x6INIConfig.has_section("Hotkeys"): + pcsx2x6INIConfig.add_section("Hotkeys") + + pcsx2x6INIConfig.set("Hotkeys", "ToggleFullscreen", "Keyboard/Alt & Keyboard/Return") + pcsx2x6INIConfig.set("Hotkeys", "CycleAspectRatio", "Keyboard/F6") + pcsx2x6INIConfig.set("Hotkeys", "CycleInterlaceMode", "Keyboard/F5") + pcsx2x6INIConfig.set("Hotkeys", "CycleMipmapMode", "Keyboard/Insert") + pcsx2x6INIConfig.set("Hotkeys", "GSDumpMultiFrame", "Keyboard/Control & Keyboard/Shift & Keyboard/F8") + pcsx2x6INIConfig.set("Hotkeys", "Screenshot", "Keyboard/F8") + pcsx2x6INIConfig.set("Hotkeys", "GSDumpSingleFrame", "Keyboard/Shift & Keyboard/F8") + pcsx2x6INIConfig.set("Hotkeys", "ToggleSoftwareRendering", "Keyboard/F9") + pcsx2x6INIConfig.set("Hotkeys", "ZoomIn", "Keyboard/Control & Keyboard/Plus") + pcsx2x6INIConfig.set("Hotkeys", "ZoomOut", "Keyboard/Control & Keyboard/Minus") + pcsx2x6INIConfig.set("Hotkeys", "InputRecToggleMode", "Keyboard/Shift & Keyboard/R") + pcsx2x6INIConfig.set("Hotkeys", "LoadStateFromSlot", "Keyboard/F3") + pcsx2x6INIConfig.set("Hotkeys", "SaveStateToSlot", "Keyboard/F1") + pcsx2x6INIConfig.set("Hotkeys", "NextSaveStateSlot", "Keyboard/F2") + pcsx2x6INIConfig.set("Hotkeys", "PreviousSaveStateSlot", "Keyboard/Shift & Keyboard/F2") + pcsx2x6INIConfig.set("Hotkeys", "OpenPauseMenu", "Keyboard/Escape") + pcsx2x6INIConfig.set("Hotkeys", "ToggleFrameLimit", "Keyboard/F4") + pcsx2x6INIConfig.set("Hotkeys", "TogglePause", "Keyboard/Space") + pcsx2x6INIConfig.set("Hotkeys", "ToggleSlowMotion", "Keyboard/Shift & Keyboard/Backtab") + pcsx2x6INIConfig.set("Hotkeys", "ToggleTurbo", "Keyboard/Tab") + pcsx2x6INIConfig.set("Hotkeys", "HoldTurbo", "Keyboard/Period") + + # clean gun sections + if pcsx2x6INIConfig.has_section("USB1") and pcsx2x6INIConfig.has_option("USB1", "Type") and pcsx2x6INIConfig.get("USB1", "Type") == "guncon2": + pcsx2x6INIConfig.remove_option("USB1", "Type") + if pcsx2x6INIConfig.has_section("USB2") and pcsx2x6INIConfig.has_option("USB2", "Type") and pcsx2x6INIConfig.get("USB2", "Type") == "guncon2": + pcsx2x6INIConfig.remove_option("USB2", "Type") + if pcsx2x6INIConfig.has_section("USB1") and pcsx2x6INIConfig.has_option("USB1", "guncon2_Start"): + pcsx2x6INIConfig.remove_option("USB1", "guncon2_Start") + if pcsx2x6INIConfig.has_section("USB2") and pcsx2x6INIConfig.has_option("USB2", "guncon2_Start"): + pcsx2x6INIConfig.remove_option("USB2", "guncon2_Start") + if pcsx2x6INIConfig.has_section("USB1") and pcsx2x6INIConfig.has_option("USB1", "guncon2_C"): + pcsx2x6INIConfig.remove_option("USB1", "guncon2_C") + if pcsx2x6INIConfig.has_section("USB2") and pcsx2x6INIConfig.has_option("USB2", "guncon2_C"): + pcsx2x6INIConfig.remove_option("USB2", "guncon2_C") + if pcsx2x6INIConfig.has_section("USB1") and pcsx2x6INIConfig.has_option("USB1", "guncon2_numdevice"): + pcsx2x6INIConfig.remove_option("USB1", "guncon2_numdevice") + if pcsx2x6INIConfig.has_section("USB2") and pcsx2x6INIConfig.has_option("USB2", "guncon2_numdevice"): + pcsx2x6INIConfig.remove_option("USB2", "guncon2_numdevice") + + # clean wheel sections + if pcsx2x6INIConfig.has_section("USB1") and pcsx2x6INIConfig.has_option("USB1", "Type") and pcsx2x6INIConfig.get("USB1", "Type") == "Pad" and pcsx2x6INIConfig.has_option("USB1", "Pad_subtype") and pcsx2x6INIConfig.get("USB1", "Pad_subtype") == "1": + pcsx2x6INIConfig.remove_option("USB1", "Type") + if pcsx2x6INIConfig.has_section("USB2") and pcsx2x6INIConfig.has_option("USB2", "Type") and pcsx2x6INIConfig.get("USB2", "Type") == "Pad" and pcsx2x6INIConfig.has_option("USB2", "Pad_subtype") and pcsx2x6INIConfig.get("USB2", "Pad_subtype") == "1": + pcsx2x6INIConfig.remove_option("USB2", "Type") + ### + + # guns + if system.config.use_guns and guns: + gun1onport2 = len(guns) == 1 and "gun_gun1port" in metadata and metadata["gun_gun1port"] == "2" + pedalsKeys = {1: "c", 2: "v", 3: "b", 4: "n"} + + if guns and not gun1onport2: + if not pcsx2x6INIConfig.has_section("USB1"): + pcsx2x6INIConfig.add_section("USB1") + pcsx2x6INIConfig.set("USB1", "Type", "guncon2") + for nc, pad in enumerate(controllers, start=1): + if nc == 1 and not gun1onport2 and "start" in pad.inputs: + pcsx2x6INIConfig.set("USB1", "guncon2_Start", f"SDL-{pad.index}/Start") + + ### find a keyboard key to simulate the action of the player (always like button 2) ; search in batocera.conf, else default config + pedalkey = system.config.get("controllers.pedals1", pedalsKeys[1]) + pcsx2x6INIConfig.set("USB1", "guncon2_C", f"Keyboard/{pedalkey.upper()}") + ### + if len(guns) >= 2 or gun1onport2: + if not pcsx2x6INIConfig.has_section("USB2"): + pcsx2x6INIConfig.add_section("USB2") + pcsx2x6INIConfig.set("USB2", "Type", "guncon2") + for nc, pad in enumerate(controllers, start=1): + if (nc == 2 or gun1onport2) and "start" in pad.inputs: + pcsx2x6INIConfig.set("USB2", "guncon2_Start", f"SDL-{pad.index}/Start") + ### find a keyboard key to simulate the action of the player (always like button 2) ; search in batocera.conf, else default config + pedalkey = system.config.get("controllers.pedals2", pedalsKeys[2]) + pcsx2x6INIConfig.set("USB2", "guncon2_C", f"Keyboard/{pedalkey.upper()}") + ### + if gun1onport2: + pcsx2x6INIConfig.set("USB2", "guncon2_numdevice", "0") + # Gun crosshairs + if pcsx2x6INIConfig.has_section("USB1"): + if system.config.get("pcsx2x6_crosshairs") == "1": + pcsx2x6INIConfig.set("USB1", "guncon2_cursor_path", str(_PCSX2X6_RESOURCES_DIR / "crosshairs" / "default.png")) + pcsx2x6INIConfig.set("USB1", "guncon2_cursor_color", "#0000ff") # blue + else: + pcsx2x6INIConfig.set("USB1", "guncon2_cursor_path", "") + if pcsx2x6INIConfig.has_section("USB2"): + if system.config.get("pcsx2x6_crosshairs") == "1": + pcsx2x6INIConfig.set("USB2", "guncon2_cursor_path", str(_PCSX2X6_RESOURCES_DIR / "crosshairs" / "default.png")) + pcsx2x6INIConfig.set("USB2", "guncon2_cursor_color", "#ff0000") # red + else: + pcsx2x6INIConfig.set("USB2", "guncon2_cursor_path", "") + + # wheels + wtype = Pcsx2x6Generator.getWheelType(metadata, playingWithWheel, system.config) + _logger.info("PS2 wheel type is %s", wtype) + if Pcsx2x6Generator.useEmulatorWheels(playingWithWheel, wtype) and wheels: + wheelMapping = { + "DrivingForcePro": { + "up": "Pad_DPadUp", + "down": "Pad_DPadDown", + "left": "Pad_DPadLeft", + "right": "Pad_DPadRight", + "start": "Pad_Start", + "select": "Pad_Select", + "a": "Pad_Circle", + "b": "Pad_Cross", + "x": "Pad_Triangle", + "y": "Pad_Square", + "pageup": "Pad_L1", + "pagedown": "Pad_R1" + }, + "DrivingForce": { + "up": "Pad_DPadUp", + "down": "Pad_DPadDown", + "left": "Pad_DPadLeft", + "right": "Pad_DPadRight", + "start": "Pad_Start", + "select": "Pad_Select", + "a": "Pad_Circle", + "b": "Pad_Cross", + "x": "Pad_Triangle", + "y": "Pad_Square", + "pageup": "Pad_L1", + "pagedown": "Pad_R1" + }, + "GTForce": { + "a": "Pad_Y", + "b": "Pad_B", + "x": "Pad_X", + "y": "Pad_A", + "pageup": "Pad_MenuDown", + "pagedown": "Pad_MenuUp" + } + } + + usbx = 1 + for pad in controllers: + if pad.device_path in wheels: + if not pcsx2x6INIConfig.has_section(f"USB{usbx}"): + pcsx2x6INIConfig.add_section(f"USB{usbx}") + pcsx2x6INIConfig.set(f"USB{usbx}", "Type", "Pad") + + wheel_type = Pcsx2x6Generator.getWheelType(metadata, playingWithWheel, system.config) + pcsx2x6INIConfig.set(f"USB{usbx}", "Pad_subtype", Pcsx2x6Generator.wheelTypeMapping[wheel_type]) + + if pad.physical_device_path is not None: # ffb on the real wheel + pcsx2x6INIConfig.set(f"USB{usbx}", "Pad_FFDevice", f"SDL-{pad.physical_index}") + else: + pcsx2x6INIConfig.set(f"USB{usbx}", "Pad_FFDevice", f"SDL-{pad.index}") + + for i in pad.inputs: + if i in wheelMapping[wheel_type]: + pcsx2x6INIConfig.set(f"USB{usbx}", wheelMapping[wheel_type][i], f"SDL-{pad.index}/{input2wheel(pad.inputs[i])}") + # wheel + if "joystick1left" in pad.inputs: + pcsx2x6INIConfig.set(f"USB{usbx}", "Pad_SteeringLeft", f"SDL-{pad.index}/{input2wheel(pad.inputs['joystick1left'])}") + pcsx2x6INIConfig.set(f"USB{usbx}", "Pad_SteeringRight", f"SDL-{pad.index}/{input2wheel(pad.inputs['joystick1left'], True)}") + # pedals + if "l2" in pad.inputs: + pcsx2x6INIConfig.set(f"USB{usbx}", "Pad_Brake", f"SDL-{pad.index}/{input2wheel(pad.inputs['l2'], None)}") + if "r2" in pad.inputs: + pcsx2x6INIConfig.set(f"USB{usbx}", "Pad_Throttle", f"SDL-{pad.index}/{input2wheel(pad.inputs['r2'], None)}") + usbx = usbx + 1 + + ## [Pad] + if not pcsx2x6INIConfig.has_section("Pad"): + pcsx2x6INIConfig.add_section("Pad") + + pcsx2x6INIConfig.set("Pad", "MultitapPort1", "false") + pcsx2x6INIConfig.set("Pad", "MultitapPort2", "false") + + # remove the previous [Padx] sections to avoid phantom controllers + section_names = ["Pad1", "Pad2", "Pad3", "Pad4", "Pad5", "Pad6", "Pad7", "Pad8"] + for section_name in section_names: + if pcsx2x6INIConfig.has_section(section_name): + pcsx2x6INIConfig.remove_section(section_name) + + # Extract Player 1 and Player 2 controller objects safely + pad1 = controllers[0] if len(controllers) > 0 else None + pad2 = controllers[1] if len(controllers) > 1 else None + + p1_sdl = f"SDL-{pad1.index}" if pad1 is not None else "SDL-0" + p2_sdl = f"SDL-{pad2.index}" if pad2 is not None else "SDL-1" + + pcsx2x6INIConfig.set("InputSources", "SDLControllerEnhancedMode", "false") + + ## [JVS] + if not pcsx2x6INIConfig.has_section("JVS"): + pcsx2x6INIConfig.add_section("JVS") + + jvs_mappings = { + "TestMode": "false", + "VideoVoltage": "true", + "MonitorSyncFrequency": "true", + "VideoSyncSplit": "true", + "SindenBorderEnabled": "false", + "SindenBorderMode": "0", + "SindenBorderThickness": "10", + + # Player 1 controls + "P1_Up": f"{p1_sdl}/DPadUp", + "P1_Down": f"{p1_sdl}/DPadDown", + "P1_Left": f"{p1_sdl}/DPadLeft", + "P1_Right": f"{p1_sdl}/DPadRight", + "Tekken_LeftPunch_P1": f"{p1_sdl}/FaceWest", + "Tekken_RightPunch_P1": f"{p1_sdl}/FaceNorth", + "Tekken_LeftKick_P1": f"{p1_sdl}/FaceSouth", + "Tekken_RightKick_P1": f"{p1_sdl}/FaceEast", + "SoulCal_Horizontal_P1": f"{p1_sdl}/FaceWest", + "SoulCal_Vertical_P1": f"{p1_sdl}/FaceNorth", + "SoulCal_Kick_P1": f"{p1_sdl}/FaceEast", + "SoulCal_Guard_P1": f"{p1_sdl}/FaceSouth", + "Gundam_Shoot_P1": f"{p1_sdl}/FaceWest", + "Gundam_Melee_P1": f"{p1_sdl}/FaceNorth", + "Gundam_Jump_P1": f"{p1_sdl}/FaceSouth", + "Gundam_Target_P1": f"{p1_sdl}/FaceEast", + "BloodyRoar_Punch_P1": f"{p1_sdl}/FaceWest", + "BloodyRoar_Kick_P1": f"{p1_sdl}/FaceSouth", + "BloodyRoar_Beast_P1": f"{p1_sdl}/FaceEast", + "BloodyRoar_Block_P1": f"{p1_sdl}/FaceNorth", + "Fate_Weak_P1": f"{p1_sdl}/FaceWest", + "Fate_Medium_P1": f"{p1_sdl}/FaceNorth", + "Fate_Strong_P1": f"{p1_sdl}/FaceEast", + "Fate_Guard_P1": f"{p1_sdl}/FaceSouth", + "Kinnikuman_Attack_P1": f"{p1_sdl}/FaceWest", + "Kinnikuman_ThrowGrab_P1": f"{p1_sdl}/FaceNorth", + "Kinnikuman_Special_P1": f"{p1_sdl}/FaceEast", + "Kinnikuman_Guard_P1": f"{p1_sdl}/FaceSouth", + "PrideGP_LeftPunch_P1": f"{p1_sdl}/FaceWest", + "PrideGP_RightPunch_P1": f"{p1_sdl}/FaceNorth", + "PrideGP_LeftKick_P1": f"{p1_sdl}/FaceSouth", + "PrideGP_RightKick_P1": f"{p1_sdl}/FaceEast", + "Basara_Weak_P1": f"{p1_sdl}/FaceWest", + "Basara_Medium_P1": f"{p1_sdl}/FaceNorth", + "Basara_Strong_P1": f"{p1_sdl}/FaceEast", + "Basara_Striker_P1": f"{p1_sdl}/FaceSouth", + "DragonBallZ_Light_P1": f"{p1_sdl}/FaceWest", + "DragonBallZ_Heavy_P1": f"{p1_sdl}/FaceNorth", + "DragonBallZ_Guard_P1": f"{p1_sdl}/FaceSouth", + "DragonBallZ_Jump_P1": f"{p1_sdl}/FaceEast", + "YuYu_Punch_P1": f"{p1_sdl}/FaceWest", + "YuYu_Kick_P1": f"{p1_sdl}/FaceNorth", + "YuYu_Guard_P1": f"{p1_sdl}/FaceSouth", + "SixButton_LightPunch_P1": f"{p1_sdl}/FaceWest", + "SixButton_MediumPunch_P1": f"{p1_sdl}/FaceNorth", + "SixButton_HeavyPunch_P1": f"{p1_sdl}/LeftShoulder", + "SixButton_LightKick_P1": f"{p1_sdl}/FaceSouth", + "SixButton_MediumKick_P1": f"{p1_sdl}/FaceEast", + "SixButton_HeavyKick_P1": f"{p1_sdl}/RightShoulder", + "SteerLeft": f"{p1_sdl}/-LeftX", + "SteerRight": f"{p1_sdl}/+LeftX", + "Gas": f"{p1_sdl}/+RightTrigger", + "Brake": f"{p1_sdl}/+LeftTrigger", + "Racing_ShiftUp_P1": f"{p1_sdl}/RightShoulder", + "Racing_ShiftDown_P1": f"{p1_sdl}/LeftShoulder", + "Racing_View_P1": f"{p1_sdl}/FaceNorth", + "BG3_ShiftUp_P1": f"{p1_sdl}/RightShoulder", + "BG3_ShiftDown_P1": f"{p1_sdl}/LeftShoulder", + "BG3_View_P1": f"{p1_sdl}/FaceNorth", + "BG3_Sidebrake_P1": f"{p1_sdl}/FaceWest", + "BG3_Hazard_P1": f"{p1_sdl}/FaceEast", + "P1_DonLeft": f"{p1_sdl}/FaceWest", + "P1_DonRight": f"{p1_sdl}/FaceNorth", + "P1_KaLeft": f"{p1_sdl}/FaceSouth", + "P1_KaRight": f"{p1_sdl}/FaceEast", + "P1_LLeverUp": f"{p1_sdl}/-LeftY", + "P1_LLeverDown": f"{p1_sdl}/+LeftY", + "P1_LLeverLeft": f"{p1_sdl}/-LeftX", + "P1_LLeverRight": f"{p1_sdl}/+LeftX", + "P1_RLeverUp": f"{p1_sdl}/-RightY", + "P1_RLeverDown": f"{p1_sdl}/+RightY", + "P1_RLeverLeft": f"{p1_sdl}/-RightX", + "P1_RLeverRight": f"{p1_sdl}/+RightX", + "P1_LTrigger": f"{p1_sdl}/+LeftTrigger", + "P1_RTrigger": f"{p1_sdl}/+RightTrigger", + "P1_LButton": f"{p1_sdl}/LeftShoulder", + "P1_RButton": f"{p1_sdl}/RightShoulder", + "Smash_TopSpin_P1": f"{p1_sdl}/FaceSouth", + "Smash_Slice_P1": f"{p1_sdl}/FaceWest", + "Technic_Activate_P1": f"{p1_sdl}/FaceWest", + "Technic_Action_P1": f"{p1_sdl}/FaceSouth", + "Technic_Super_P1": f"{p1_sdl}/FaceEast", + "Baseball_A_P1": f"{p1_sdl}/FaceSouth", + "Baseball_B_P1": f"{p1_sdl}/FaceWest", + "Baseball_C_P1": f"{p1_sdl}/FaceNorth", + "GundamQuiz_Target_P1": f"{p1_sdl}/FaceEast", + "GundamQuiz_Shoot_P1": f"{p1_sdl}/FaceWest", + "GundamQuiz_Melee_P1": f"{p1_sdl}/FaceNorth", + "GundamQuiz_Jump_P1": f"{p1_sdl}/FaceSouth", + "Inufuku_1_P1": f"{p1_sdl}/FaceNorth", + "Inufuku_2_P1": f"{p1_sdl}/FaceSouth", + "Inufuku_3_P1": f"{p1_sdl}/FaceWest", + "Inufuku_4_P1": f"{p1_sdl}/FaceEast", + + # Player 2 controls + "P2_Up": f"{p2_sdl}/DPadUp", + "P2_Down": f"{p2_sdl}/DPadDown", + "P2_Left": f"{p2_sdl}/DPadLeft", + "P2_Right": f"{p2_sdl}/DPadRight", + "Tekken_LeftPunch_P2": f"{p2_sdl}/FaceWest", + "Tekken_RightPunch_P2": f"{p2_sdl}/FaceNorth", + "Tekken_LeftKick_P2": f"{p2_sdl}/FaceSouth", + "Tekken_RightKick_P2": f"{p2_sdl}/FaceEast", + "SoulCal_Horizontal_P2": f"{p2_sdl}/FaceWest", + "SoulCal_Vertical_P2": f"{p2_sdl}/FaceNorth", + "SoulCal_Kick_P2": f"{p2_sdl}/FaceEast", + "SoulCal_Guard_P2": f"{p2_sdl}/FaceSouth", + "BloodyRoar_Punch_P2": f"{p2_sdl}/FaceWest", + "BloodyRoar_Kick_P2": f"{p2_sdl}/FaceSouth", + "BloodyRoar_Beast_P2": f"{p2_sdl}/FaceEast", + "BloodyRoar_Block_P2": f"{p2_sdl}/FaceNorth", + "Fate_Weak_P2": f"{p2_sdl}/FaceWest", + "Fate_Medium_P2": f"{p2_sdl}/FaceNorth", + "Fate_Strong_P2": f"{p2_sdl}/FaceEast", + "Fate_Guard_P2": f"{p2_sdl}/FaceSouth", + "Kinnikuman_Attack_P2": f"{p2_sdl}/FaceWest", + "Kinnikuman_ThrowGrab_P2": f"{p2_sdl}/FaceNorth", + "Kinnikuman_Special_P2": f"{p2_sdl}/FaceEast", + "Kinnikuman_Guard_P2": f"{p2_sdl}/FaceSouth", + "PrideGP_LeftPunch_P2": f"{p2_sdl}/FaceWest", + "PrideGP_RightPunch_P2": f"{p2_sdl}/FaceNorth", + "PrideGP_LeftKick_P2": f"{p2_sdl}/FaceSouth", + "PrideGP_RightKick_P2": f"{p2_sdl}/FaceEast", + "Basara_Weak_P2": f"{p2_sdl}/FaceWest", + "Basara_Medium_P2": f"{p2_sdl}/FaceNorth", + "Basara_Strong_P2": f"{p2_sdl}/FaceEast", + "Basara_Striker_P2": f"{p2_sdl}/FaceSouth", + "DragonBallZ_Light_P2": f"{p2_sdl}/FaceWest", + "DragonBallZ_Heavy_P2": f"{p2_sdl}/FaceNorth", + "DragonBallZ_Guard_P2": f"{p2_sdl}/FaceSouth", + "DragonBallZ_Jump_P2": f"{p2_sdl}/FaceEast", + "YuYu_Punch_P2": f"{p2_sdl}/FaceWest", + "YuYu_Kick_P2": f"{p2_sdl}/FaceNorth", + "YuYu_Guard_P2": f"{p2_sdl}/FaceSouth", + "SixButton_LightPunch_P2": f"{p2_sdl}/FaceWest", + "SixButton_MediumPunch_P2": f"{p2_sdl}/FaceNorth", + "SixButton_HeavyPunch_P2": f"{p2_sdl}/LeftShoulder", + "SixButton_LightKick_P2": f"{p2_sdl}/FaceSouth", + "SixButton_MediumKick_P2": f"{p2_sdl}/FaceEast", + "SixButton_HeavyKick_P2": f"{p2_sdl}/RightShoulder", + "P2_DonLeft": f"{p2_sdl}/FaceWest", + "P2_DonRight": f"{p2_sdl}/FaceNorth", + "P2_KaLeft": f"{p2_sdl}/FaceSouth", + "P2_KaRight": f"{p2_sdl}/FaceEast", + "Smash_TopSpin_P2": f"{p2_sdl}/FaceSouth", + "Smash_Slice_P2": f"{p2_sdl}/FaceWest", + "Technic_Activate_P2": f"{p2_sdl}/FaceWest", + "Technic_Action_P2": f"{p2_sdl}/FaceSouth", + "Technic_Super_P2": f"{p2_sdl}/FaceEast", + "Baseball_A_P2": f"{p2_sdl}/FaceSouth", + "Baseball_B_P2": f"{p2_sdl}/FaceWest", + "Baseball_C_P2": f"{p2_sdl}/FaceNorth", + "GundamQuiz_Target_P2": f"{p2_sdl}/FaceEast", + "GundamQuiz_Shoot_P2": f"{p2_sdl}/FaceWest", + "GundamQuiz_Melee_P2": f"{p2_sdl}/FaceNorth", + "GundamQuiz_Jump_P2": f"{p2_sdl}/FaceSouth", + "Inufuku_1_P2": f"{p2_sdl}/FaceNorth", + "Inufuku_2_P2": f"{p2_sdl}/FaceSouth", + "Inufuku_3_P2": f"{p2_sdl}/FaceWest", + "Inufuku_4_P2": f"{p2_sdl}/FaceEast", + + # Generic & Special buttons mapping + "P1_Button1": f"{p1_sdl}/FaceSouth", + "Coin1": f"{p1_sdl}/Back", + "P1_Start": f"{p1_sdl}/Start" + } + + for k, v in jvs_mappings.items(): + pcsx2x6INIConfig.set("JVS", k, v) + + ## [GameList] + if not pcsx2x6INIConfig.has_section("GameList"): + pcsx2x6INIConfig.add_section("GameList") + + pcsx2x6INIConfig.set("GameList", "RecursivePaths", str(ROMS / "namco2x6")) + + with configFileName.open('w') as configfile: + pcsx2x6INIConfig.write(configfile) + +def input2wheel(input: Input, reversedAxis: bool | None = False) -> str | None: + if input.type == "button": + pcsx2x6_magic_button_offset = 21 # PCSX2/SDLInputSource.cpp : const u32 button = ev->button + std::size(s_sdl_button_names) + return f"Button{int(input.id) + pcsx2x6_magic_button_offset}" + if input.type == "hat": + dir = "unknown" + if input.value == '1': + dir = "North" + elif input.value == '2': + dir = "East" + elif input.value == '4': + dir = "South" + elif input.value == '8': + dir = "West" + return f"Hat{input.id}{dir}" + if input.type == "axis": + pcsx2x6_magic_axis_offset = 6 # PCSX2/SDLInputSource.cpp : const u32 axis = ev->axis + std::size(s_sdl_axis_names); + if reversedAxis is None: + return f"FullAxis{int(input.id)+pcsx2x6_magic_axis_offset}~" + dir = "-" + if reversedAxis: + dir = "+" + return f"{dir}Axis{int(input.id)+pcsx2x6_magic_axis_offset}" + return None diff --git a/package/batocera/core/batocera-configgen/configs/configgen-defaults.yml b/package/batocera/core/batocera-configgen/configs/configgen-defaults.yml index 217e466f30a..c89f5017938 100644 --- a/package/batocera/core/batocera-configgen/configs/configgen-defaults.yml +++ b/package/batocera/core/batocera-configgen/configs/configgen-defaults.yml @@ -461,8 +461,8 @@ namco22: emulator: libretro core: mame namco2x6: - emulator: play - core: play + emulator: pcsx2x6 + core: pcsx2x6 namco3xx: emulator: rpcs3 core: rpcs3 diff --git a/package/batocera/core/batocera-desktopapps/apps/pcsx2x6-config.desktop b/package/batocera/core/batocera-desktopapps/apps/pcsx2x6-config.desktop new file mode 100644 index 00000000000..cdd087ca6ac --- /dev/null +++ b/package/batocera/core/batocera-desktopapps/apps/pcsx2x6-config.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Icon=/usr/share/icons/batocera/pcsx2x6.png +Exec=/usr/bin/batocera-config-pcsx2x6 +Terminal=false +Type=Application +Categories=Game;batocera.linux; +Name=pcsx2x6-config diff --git a/package/batocera/core/batocera-desktopapps/batocera-desktopapps.mk b/package/batocera/core/batocera-desktopapps/batocera-desktopapps.mk index b55ac528ad6..25c5ecccc99 100644 --- a/package/batocera/core/batocera-desktopapps/batocera-desktopapps.mk +++ b/package/batocera/core/batocera-desktopapps/batocera-desktopapps.mk @@ -49,6 +49,13 @@ ifeq ($(BR2_PACKAGE_PCSX2),y) BATOCERA_DESKTOPAPPS_ICONS += pcsx2.png endif +# pcsx2x6 +ifeq ($(BR2_PACKAGE_PCSX2X6),y) + BATOCERA_DESKTOPAPPS_SCRIPTS += batocera-config-pcsx2x6 + BATOCERA_DESKTOPAPPS_APPS += pcsx2x6-config.desktop + BATOCERA_DESKTOPAPPS_ICONS += pcsx2x6.png +endif + # dolphin ifeq ($(BR2_PACKAGE_DOLPHIN_EMU)$(BR2_PACKAGE_XORG7),yy) BATOCERA_DESKTOPAPPS_SCRIPTS += batocera-config-dolphin @@ -180,13 +187,6 @@ ifeq ($(BR2_PACKAGE_VITA3K),y) BATOCERA_DESKTOPAPPS_ICONS += vita3k.png endif -# play! -ifeq ($(BR2_PACKAGE_PLAY),y) - BATOCERA_DESKTOPAPPS_SCRIPTS += batocera-config-play - BATOCERA_DESKTOPAPPS_APPS += play-config.desktop - BATOCERA_DESKTOPAPPS_ICONS += play.png -endif - # shadPS4 - QT Launcher ifeq ($(BR2_PACKAGE_SHADPS4),y) BATOCERA_DESKTOPAPPS_SCRIPTS += batocera-config-shadps4 diff --git a/package/batocera/core/batocera-desktopapps/icons/pcsx2x6.png b/package/batocera/core/batocera-desktopapps/icons/pcsx2x6.png new file mode 100644 index 00000000000..5c28a7d5f7c Binary files /dev/null and b/package/batocera/core/batocera-desktopapps/icons/pcsx2x6.png differ diff --git a/package/batocera/core/batocera-desktopapps/scripts/batocera-config-pcsx2x6 b/package/batocera/core/batocera-desktopapps/scripts/batocera-config-pcsx2x6 new file mode 100755 index 00000000000..78f3c078f2e --- /dev/null +++ b/package/batocera/core/batocera-desktopapps/scripts/batocera-config-pcsx2x6 @@ -0,0 +1,13 @@ +#!/bin/bash + +# when the program is called from a non X environment, handle the mouse +# maybe an other choice is better + +if test -z "${DISPLAY}" +then + export DISPLAY=$(getLocalXDisplay) +fi + +export XDG_CONFIG_HOME=/userdata/system/configs + +/usr/pcsx2x6/bin/pcsx2x6-qt diff --git a/package/batocera/core/batocera-scripts/scripts/batocera-systems b/package/batocera/core/batocera-scripts/scripts/batocera-systems index 999c7148a66..cbac94b5089 100644 --- a/package/batocera/core/batocera-scripts/scripts/batocera-systems +++ b/package/batocera/core/batocera-scripts/scripts/batocera-systems @@ -997,6 +997,13 @@ systems = { { "md5": "1e6637f32aa7d1de03510030cac40bcf", "zippedFile": "bk11m_327_basic1.rom", "file": "bios/bk0010.zip", "emulator": "libretro", "core": "mame" }, { "md5": "dc52f365d56fa1951f5d35b1101b9e3f", "zippedFile": "bk11m_325_ext.rom", "file": "bios/bk0010.zip", "emulator": "libretro", "core": "mame" }, { "md5": "fe4627d1e3a1535874085050733263e7", "zippedFile": "bk11m_324_bos.rom", "file": "bios/bk0010.zip", "emulator": "libretro", "core": "mame" }] }, + + # ---------- Namco2x6 ---------- # + "namco2x6": { "name": "Namco2x6", "biosFiles": [ + # Namco System 246 + { "md5": "52cca0058626569c7a9699838baab2d8", "file": "bios/namco2x6/r27v1602f.7d" }, + # Namco System 256 + { "md5": "a58676c6bd79229bda967d07b4ec2e16", "file": "bios/namco2x6/r27v1602f.8g" }] }, } class BiosStatus: diff --git a/package/batocera/core/batocera-system/Config.in b/package/batocera/core/batocera-system/Config.in index d770f0f03d8..da59d69cc48 100644 --- a/package/batocera/core/batocera-system/Config.in +++ b/package/batocera/core/batocera-system/Config.in @@ -1021,6 +1021,9 @@ config BR2_PACKAGE_BATOCERA_ARCADE_SYSTEMS # Standalone MAME select BR2_PACKAGE_MAME if BR2_x86_64 || ((BR2_arm || BR2_aarch64 || BR2_RISCV_64) && BR2_PACKAGE_BATOCERA_WAYLAND) + # Namco System246 and System256 + select BR2_PACKAGE_PCSX2X6 if BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY + help Arcade cores/emulators @@ -1555,8 +1558,6 @@ config BR2_PACKAGE_BATOCERA_CONSOLE_SYSTEMS BR2_PACKAGE_BATOCERA_TARGET_SM8550 || \ BR2_PACKAGE_BATOCERA_TARGET_SM8750 select BR2_PACKAGE_LIBRETRO_PS2 if BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY - select BR2_PACKAGE_PLAY if BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY - select BR2_PACKAGE_LIBRETRO_PLAY if BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY # Sony PS3 select BR2_PACKAGE_RPCS3 if BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY diff --git a/package/batocera/emulationstation/batocera-es-system/es_systems.yml b/package/batocera/emulationstation/batocera-es-system/es_systems.yml index 4798dc34613..359d31a0ec7 100644 --- a/package/batocera/emulationstation/batocera-es-system/es_systems.yml +++ b/package/batocera/emulationstation/batocera-es-system/es_systems.yml @@ -2291,9 +2291,6 @@ ps2: pcsx2: { requireAnyOf: [BR2_PACKAGE_PCSX2], incompatible_extensions: [m3u] } libretro: pcsx2: { requireAnyOf: [BR2_PACKAGE_LIBRETRO_PS2] } - play: { requireAnyOf: [BR2_PACKAGE_LIBRETRO_PLAY], incompatible_extensions: [m3u, zso] } - play: - play: { requireAnyOf: [BR2_PACKAGE_PLAY], incompatible_extensions: [m3u, zso] } comment_en: | For more info: https://wiki.batocera.org/systems:ps2 comment_fr: | @@ -5273,22 +5270,45 @@ namco2x6: manufacturer: Sony / Namco release: 2000 hardware: arcade - extensions: [zip] + extensions: [acgame] platform: namco, sega, arcade emulators: - play: - play: { requireAnyOf: [BR2_PACKAGE_PLAY] } + pcsx2x6: + pcsx2x6: { requireAnyOf: [BR2_PACKAGE_PCSX2X6] } comment_en: | - The Play! emulator provides `work in progress` support for Namco 246 & 256 systems. - Currently you will need a high performance system to be able to emulate supported games. - We recommend using the OpenGL API for now. + The PCSX2X6 emulator provides `work in progress` support for Namco 246 & 256 arcade systems. + + This rom directory should include an .acgame file based on the gameid and a supporting files required to play. + The .acgame is the rom name EmulationStation uses to help launch the game using PCSX2x6. + + Example: + + Ridge Racer V Arcade Battle (RRV2 Ver. B).acgame + + [game] + name=Ridge Racer V Arcade Battle (RRV2 Ver. B) + gameid=NM00001 + platform=246 - This rom directory should include a zip file based on the arcade id and a supporting folder with associated .chd file. - i.e. Tekken 4 - tekken4.zip + [data] + subdir=NM00001 + elf=proverb.elf + dongle=NM00001 RRV2, Ver.B a025531981282a.ps2 + mediasrc=NM00001 RRV1-A (CD-ROM).chd + media=CD + + Based on this .acgame example, we create a subfolder called NM00001 + Within it, we need the `proverb.elf`, `NM00001 RRV2, Ver.B a025531981282a.ps2` & `NM00001 RRV1-A (CD-ROM).chd` for the game. + + i.e. Ridge Racer V Arcade Battle (RRV2 Ver. B).acgame | - tekken4 + NM00001 | - ---tef1dvd0.chd + ---proverb.elf + ---NM00001 RRV2, Ver.B a025531981282a.ps2 + ---NM00001 RRV1-A (CD-ROM).chd + + You will also need the Namco 246 & 256 BIOS files in the /userdata/bios/namco2x6 directory. For more info: https://wiki.batocera.org/systems:namco2x6 diff --git a/package/batocera/emulators/pcsx2x6/000-fix-arm-compilation.patch b/package/batocera/emulators/pcsx2x6/000-fix-arm-compilation.patch new file mode 100644 index 00000000000..62ccccc3e34 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/000-fix-arm-compilation.patch @@ -0,0 +1,69 @@ +--- a/cmake/BuildParameters.cmake ++++ b/cmake/BuildParameters.cmake +@@ -78,8 +78,8 @@ + #------------------------------------------------------------------------------- + # Select the architecture + #------------------------------------------------------------------------------- +-if("${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "x86_64" OR "${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "amd64" OR +- "${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "AMD64" OR "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "x86_64") ++if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64" OR ++ "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64" OR "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "x86_64") + # Multi-ISA only exists on x86. + option(DISABLE_ADVANCE_SIMD "Disable advance use of SIMD (SSE2+ & AVX)" OFF) + +@@ -105,12 +105,12 @@ + add_compile_options("-msse" "-msse2" "-msse4.1" "-mfxsr") + else() + # Can't use march=native on Apple Silicon. +- if(NOT APPLE OR "${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "x86_64") ++ if(NOT APPLE OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") + add_compile_options("-march=native") + endif() + endif() + endif() +-elseif("${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "arm64" OR "${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "aarch64" OR ++elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64" OR + "${CMAKE_OSX_ARCHITECTURES}" STREQUAL "arm64") + message(STATUS "Building for Apple Silicon (ARM64).") + set(ARCH_ARM64 TRUE) +@@ -125,10 +125,27 @@ + # If we're running on Linux, we need to detect the page/cache line size. + # It could be a virtual machine with 4K pages, or 16K with Asahi. + if(LINUX) +- detect_page_size() +- list(APPEND PCSX2_DEFS OVERRIDE_HOST_PAGE_SIZE=${HOST_PAGE_SIZE}) +- detect_cache_line_size() +- list(APPEND PCSX2_DEFS OVERRIDE_HOST_CACHE_LINE_SIZE=${HOST_CACHE_LINE_SIZE}) ++ if(CMAKE_CROSSCOMPILING) ++ # try_run() can't execute target binaries on the build host ++ # during cross-compilation, so detect_page_size()/ ++ # detect_cache_line_size() can't work here. Fall back to the ++ # standard ARMv8 Linux values; override per-target if needed ++ # (e.g. 16K pages) via -DOVERRIDE_HOST_PAGE_SIZE=/ ++ # -DOVERRIDE_HOST_CACHE_LINE_SIZE=. ++ if(NOT DEFINED OVERRIDE_HOST_PAGE_SIZE) ++ set(OVERRIDE_HOST_PAGE_SIZE 0x1000) ++ endif() ++ if(NOT DEFINED OVERRIDE_HOST_CACHE_LINE_SIZE) ++ set(OVERRIDE_HOST_CACHE_LINE_SIZE 64) ++ endif() ++ list(APPEND PCSX2_DEFS OVERRIDE_HOST_PAGE_SIZE=${OVERRIDE_HOST_PAGE_SIZE}) ++ list(APPEND PCSX2_DEFS OVERRIDE_HOST_CACHE_LINE_SIZE=${OVERRIDE_HOST_CACHE_LINE_SIZE}) ++ else() ++ detect_page_size() ++ list(APPEND PCSX2_DEFS OVERRIDE_HOST_PAGE_SIZE=${HOST_PAGE_SIZE}) ++ detect_cache_line_size() ++ list(APPEND PCSX2_DEFS OVERRIDE_HOST_CACHE_LINE_SIZE=${HOST_CACHE_LINE_SIZE}) ++ endif() + endif() + + # Windows page/cache line size seems to match x68-64 +@@ -138,7 +155,7 @@ + list(APPEND PCSX2_DEFS OVERRIDE_HOST_CACHE_LINE_SIZE=64) + endif() + else() +- message(FATAL_ERROR "Unsupported architecture: ${CMAKE_HOST_SYSTEM_PROCESSOR}") ++ message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}") + endif() + + # Require C++20. \ No newline at end of file diff --git a/package/batocera/emulators/pcsx2x6/001-fix-Savestates-path.patch b/package/batocera/emulators/pcsx2x6/001-fix-Savestates-path.patch new file mode 100644 index 00000000000..4abf4d2755a --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/001-fix-Savestates-path.patch @@ -0,0 +1,13 @@ +diff --git a/pcsx2-qt/Settings/FolderSettingsWidget.cpp b/pcsx2-qt/Settings/FolderSettingsWidget.cpp +index 309f1e605c..5c4356e1b4 100644 +--- a/pcsx2-qt/Settings/FolderSettingsWidget.cpp ++++ b/pcsx2-qt/Settings/FolderSettingsWidget.cpp +@@ -21,7 +21,7 @@ FolderSettingsWidget::FolderSettingsWidget(SettingsWindow* settings_dialog, QWid + SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.snapshots, m_ui.snapshotsBrowse, m_ui.snapshotsOpen, m_ui.snapshotsReset, "Folders", "Snapshots", Path::Combine(EmuFolders::DataRoot, "snaps")); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.organizeSnapshotsByGame, "EmuCore/GS", "OrganizeScreenshotsByGame", false); + SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.saveStates, m_ui.saveStatesBrowse, m_ui.saveStatesOpen, m_ui.saveStatesReset, +- "Folders", "SaveStates", Path::Combine(EmuFolders::DataRoot, "sstates")); ++ "Folders", "Savestates", Path::Combine(EmuFolders::DataRoot, "sstates")); + SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.videoDumpingDirectory, m_ui.videoDumpingDirectoryBrowse, m_ui.videoDumpingDirectoryOpen, m_ui.videoDumpingDirectoryReset, + "Folders", "Videos", Path::Combine(EmuFolders::DataRoot, "videos")); + dialog()->registerWidgetHelp(m_ui.organizeSnapshotsByGame, tr("Organize Snapshots by Game"), tr("Unchecked"), \ No newline at end of file diff --git a/package/batocera/emulators/pcsx2x6/002-fix-linux-compilation-headers-gs.patch b/package/batocera/emulators/pcsx2x6/002-fix-linux-compilation-headers-gs.patch new file mode 100644 index 00000000000..623b64ed4fc --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/002-fix-linux-compilation-headers-gs.patch @@ -0,0 +1,26 @@ +diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp +index 757d7ae..79c4937 100644 +--- a/pcsx2/CDVD/CDVD.cpp ++++ b/pcsx2/CDVD/CDVD.cpp +@@ -20,7 +20,7 @@ + #include "CDVD/CDVD_internal.h" + #include "CDVD/IsoReader.h" + #include "CDVD/IsoFileFormats.h" +-#include "GS.h" ++#include "../GS.h" // for gsVideoMode + #include "SIO/Sio.h" + #include "Elfheader.h" + #include "ps2/BiosTools.h" +diff --git a/pcsx2/DebugTools/DebugInterface.cpp b/pcsx2/DebugTools/DebugInterface.cpp +index d29c2b0..2a89378 100644 +--- a/pcsx2/DebugTools/DebugInterface.cpp ++++ b/pcsx2/DebugTools/DebugInterface.cpp +@@ -20,7 +20,7 @@ + #include "R5900.h" + #include "Debug.h" + #include "VU.h" +-#include "GS.h" // Required for gsNonMirroredRead() ++#include "../GS.h" // Required for gsNonMirroredRead() + #include "Counters.h" + + #include "R3000A.h" diff --git a/package/batocera/emulators/pcsx2x6/003-fix-linux-detection.patch b/package/batocera/emulators/pcsx2x6/003-fix-linux-detection.patch new file mode 100644 index 00000000000..9f14bc9c951 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/003-fix-linux-detection.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/Pcsx2Utils.cmake b/cmake/Pcsx2Utils.cmake.new +index 4c2d126e95..9f00c3be0f 100644 +--- a/cmake/Pcsx2Utils.cmake ++++ b/cmake/Pcsx2Utils.cmake.new +@@ -3,7 +3,7 @@ function(detect_operating_system) + message(STATUS "CMake System Name: ${CMAKE_SYSTEM_NAME}") + + # LINUX wasn't added until CMake 3.25. +- if (CMAKE_VERSION VERSION_LESS 3.25.0 AND CMAKE_SYSTEM_NAME MATCHES "Linux") ++ if (CMAKE_SYSTEM_NAME MATCHES "Linux") + # Have to make it visible in this scope as well for below. + set(LINUX TRUE PARENT_SCOPE) + set(LINUX TRUE) diff --git a/package/batocera/emulators/pcsx2x6/004-controller-db.patch b/package/batocera/emulators/pcsx2x6/004-controller-db.patch new file mode 100644 index 00000000000..4b0a3c65013 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/004-controller-db.patch @@ -0,0 +1,13 @@ +diff --git a/pcsx2/Input/SDLInputSource.cpp b/pcsx2/Input/SDLInputSource.cpp +index 4993e43514..38f6a1da47 100644 +--- a/pcsx2/Input/SDLInputSource.cpp ++++ b/pcsx2/Input/SDLInputSource.cpp +@@ -29,7 +29,7 @@ + #include + #include + +-static constexpr const char* CONTROLLER_DB_FILENAME = "game_controller_db.txt"; ++static constexpr const char* CONTROLLER_DB_FILENAME = "/userdata/system/configs/PCSX2x6/game_controller_db.txt"; + + static constexpr const char* s_sdl_axis_names[] = { + "LeftX", // SDL_CONTROLLER_AXIS_LEFTX diff --git a/package/batocera/emulators/pcsx2x6/005-lightguns.patch b/package/batocera/emulators/pcsx2x6/005-lightguns.patch new file mode 100644 index 00000000000..571eb147237 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/005-lightguns.patch @@ -0,0 +1,413 @@ +diff --git a/pcsx2/Input/InputManager.h b/pcsx2/Input/InputManager.h +index 5e7bd4e..b00e125 100644 +--- a/pcsx2/Input/InputManager.h ++++ b/pcsx2/Input/InputManager.h +@@ -163,7 +163,7 @@ namespace InputManager + static constexpr double VIBRATION_UPDATE_INTERVAL_SECONDS = 0.5; // 500ms + + /// Maximum number of host mouse devices. +- static constexpr u32 MAX_POINTER_DEVICES = 1; ++ static constexpr u32 MAX_POINTER_DEVICES = 2; + static constexpr u32 MAX_POINTER_BUTTONS = 3; + + /// Maximum number of software cursors. We allocate an extra two for USB devices with +diff --git a/pcsx2/ImGui/ImGuiManager.cpp b/pcsx2/ImGui/ImGuiManager.cpp +index 84d0641..353d038 100644 +--- a/pcsx2/ImGui/ImGuiManager.cpp ++++ b/pcsx2/ImGui/ImGuiManager.cpp +@@ -1333,13 +1333,15 @@ void ImGuiManager::DrawSoftwareCursors() + } + } + +- // This one's okay to race, worst that happens is we render the wrong number of cursors for a frame. ++ // Use stored position for all cursors (lightguns update via SetSoftwareCursorPosition) + const u32 pointer_count = InputManager::MAX_POINTER_DEVICES; + + for (u32 i = 0; i < pointer_count; i++) +- DrawSoftwareCursor(s_software_cursors[i], InputManager::GetPointerAbsolutePosition(i)); ++ DrawSoftwareCursor(s_software_cursors[i], s_software_cursors[i].pos); + +- for (u32 i = InputManager::MAX_POINTER_DEVICES; i < InputManager::MAX_SOFTWARE_CURSORS; i++) ++ // USB devices with relative positioning use indices after pointer devices ++ const u32 usb_start = pointer_count; ++ for (u32 i = usb_start; i < InputManager::MAX_SOFTWARE_CURSORS; i++) + DrawSoftwareCursor(s_software_cursors[i], s_software_cursors[i].pos); + } + +diff --git a/pcsx2/USB/usb-lightgun/guncon2.cpp b/pcsx2/USB/usb-lightgun/guncon2.cpp +index a69dbb1..d6d0e04 100644 +--- a/pcsx2/USB/usb-lightgun/guncon2.cpp ++++ b/pcsx2/USB/usb-lightgun/guncon2.cpp +@@ -31,6 +31,12 @@ + + #include + ++#include ++#include ++#include ++#include ++#include "GS/Renderers/Common/GSDevice.h" ++ + namespace usb_lightgun + { + enum : u32 +@@ -130,6 +136,7 @@ namespace usb_lightgun + struct GunCon2State + { + explicit GunCon2State(u32 port_); ++ ~GunCon2State(); + + USBDevice dev{}; + USBDesc desc{}; +@@ -148,6 +155,7 @@ namespace usb_lightgun + float center_y = 120; + float scale_x = 1.0f; + float scale_y = 1.0f; ++ int numdevice = -1; + + ////////////////////////////////////////////////////////////////////////// + // Host State (Not Saved) +@@ -175,6 +183,12 @@ namespace usb_lightgun + + std::tuple CalculatePosition(); + ++ // udev ++ int udev_fd; ++ float udev_internalGunX; ++ float udev_internalGunY; ++ int udev_gunMinx, udev_gunMiny, udev_gunMaxx, udev_gunMaxy; ++ + // 0..1, not -1..1. + std::pair GetAbsolutePositionFromRelativeAxes() const; + u32 GetSoftwarePointerIndex() const; +@@ -240,6 +254,12 @@ namespace usb_lightgun + 0x08, // Polling interval (frame counts) + }; + ++ struct event_udev_entry ++ { ++ const char *devnode; ++ struct udev_list_entry *item; ++ }; ++ + static void guncon2_handle_control( + USBDevice* dev, USBPacket* p, int request, int value, int index, int length, uint8_t* data) + { +@@ -269,9 +289,93 @@ namespace usb_lightgun + p->status = USB_RET_STALL; + } + ++ void updateState(GunCon2State* us, u32 bid, bool pressed) { ++ const u32 bit = 1u << bid; ++ if (pressed) ++ us->button_state |= bit; ++ else ++ us->button_state &= ~bit; ++ } ++ ++ static bool udev_has(GunCon2State* us) { ++ return us->udev_fd != -1; ++ } ++ ++ static void udev_handle_event(GunCon2State* us, input_event* event) { ++ switch (event->type) { ++ case EV_KEY: ++ switch (event->code) { ++ case BTN_LEFT: ++ updateState(us, BID_TRIGGER, event->value != 0); // 0: unpressed, 1: pressed, 2: maintained ++ break; ++ case BTN_RIGHT: ++ updateState(us, BID_C, event->value != 0); ++ break; ++ case BTN_MIDDLE: ++ updateState(us, BID_START, event->value != 0); ++ break; ++ case BTN_1: ++ updateState(us, BID_B, event->value != 0); ++ break; ++ case BTN_2: ++ updateState(us, BID_RECALIBRATE, event->value != 0); ++ break; ++ case BTN_3: ++ updateState(us, BID_A, event->value != 0); ++ break; ++ case BTN_4: ++ updateState(us, BID_SELECT, event->value != 0); ++ break; ++ case BTN_5: ++ updateState(us, BID_DPAD_UP, event->value != 0); ++ break; ++ case BTN_6: ++ updateState(us, BID_DPAD_DOWN, event->value != 0); ++ break; ++ case BTN_7: ++ updateState(us, BID_DPAD_LEFT, event->value != 0); ++ break; ++ case BTN_8: ++ updateState(us, BID_DPAD_RIGHT, event->value != 0); ++ break; ++ case BTN_9: ++ break; ++ default: ++ break; ++ } ++ break; ++ ++ case EV_ABS: ++ switch (event->code) { ++ case ABS_X: ++ us->udev_internalGunX = ((event->value - us->udev_gunMinx) / ((float)(us->udev_gunMaxx - us->udev_gunMinx))) * g_gs_device->GetWindowWidth(); ++ break; ++ case ABS_Y: ++ us->udev_internalGunY = ((event->value - us->udev_gunMiny) / ((float)(us->udev_gunMaxy - us->udev_gunMiny))) * g_gs_device->GetWindowHeight(); ++ break; ++ } ++ break; ++ } ++ } ++ ++ static void udev_poll_gun(GunCon2State* us) { ++ struct input_event input_events[32]; ++ int j, len; ++ ++ if(us->udev_fd == -1) return; ++ ++ while ((len = read(us->udev_fd, input_events, sizeof(input_events))) > 0) { ++ len /= sizeof(*input_events); ++ for (j = 0; j < len; j++) { ++ udev_handle_event(us, &(input_events[j])); ++ } ++ } ++ } ++ + static void guncon2_handle_data(USBDevice* dev, USBPacket* p) + { + GunCon2State* const us = USB_CONTAINER_OF(dev, GunCon2State, dev); ++ if(udev_has(us)) udev_poll_gun(us); + + switch (p->pid) + { +@@ -280,6 +384,8 @@ namespace usb_lightgun + if (p->ep->nr == 1) + { + const auto [pos_x, pos_y] = us->CalculatePosition(); ++ if (!us->cursor_path.empty() && udev_has(us)) ++ ImGuiManager::SetSoftwareCursorPosition(us->port, us->udev_internalGunX, us->udev_internalGunY); + + // Time Crisis games do a "calibration" by displaying a black frame for a single frame, + // waiting for the gun to report (0, 0), and then computing an offset on the first non-zero +@@ -350,8 +456,16 @@ namespace usb_lightgun + GunCon2State::GunCon2State(u32 port_) + : port(port_) + { ++ udev_fd = -1; ++ udev_internalGunX = 0.0; ++ udev_internalGunY = 0.0; + } + ++ GunCon2State::~GunCon2State() ++ { ++ if(udev_fd != -1) close(udev_fd); ++ } ++ + void GunCon2State::AutoConfigure() + { + const std::string serial = VMManager::GetDiscSerial(); +@@ -384,6 +498,13 @@ namespace usb_lightgun + (has_relative_binds) ? GetAbsolutePositionFromRelativeAxes() : InputManager::GetPointerAbsolutePosition(0); + GSTranslateWindowToDisplayCoordinates(window_x, window_y, &pointer_x, &pointer_y); + ++ if(udev_has(this)) { ++ GSTranslateWindowToDisplayCoordinates(udev_internalGunX, udev_internalGunY, &pointer_x, &pointer_y); ++ } else { ++ // basic mouse position ++ GSTranslateWindowToDisplayCoordinates(window_x, window_y, &pointer_x, &pointer_y); ++ } ++ + s16 pos_x, pos_y; + if (pointer_x < 0.0f || pointer_y < 0.0f) + { +@@ -433,7 +554,8 @@ namespace usb_lightgun + + u32 GunCon2State::GetSoftwarePointerIndex() const + { +- return has_relative_binds ? (InputManager::MAX_POINTER_DEVICES + port) : 0; ++ if (udev_fd != -1) return port; ++ return has_relative_binds ? (InputManager::MAX_POINTER_DEVICES + port) : port; + } + + void GunCon2State::UpdateSoftwarePointerPosition() +@@ -458,9 +580,151 @@ namespace usb_lightgun + return "guncon2"; + } + ++ int event_isNumber(const char *s) { ++ int n; ++ ++ if(strlen(s) == 0) { ++ return 0; ++ } ++ ++ for(n=0; ndevnode, bb->devnode); ++ } ++ ++ void GunCon2Device::udev_open_gun(GunCon2State* us) { ++ struct udev_enumerate *enumerate; ++ struct udev_list_entry *devs = NULL; ++ struct udev_list_entry *item = NULL; ++ unsigned sorted_count = 0; ++ struct event_udev_entry sorted[8]; // max devices ++ unsigned int i; ++ struct udev *udev; ++ int fd = -1; ++ ++ udev = udev_new(); ++ if(udev == NULL) return; ++ ++ enumerate = udev_enumerate_new(udev); ++ ++ if (enumerate != NULL) { ++ udev_enumerate_add_match_property(enumerate, "ID_INPUT_GUN", "1"); ++ udev_enumerate_add_match_subsystem(enumerate, "input"); ++ udev_enumerate_scan_devices(enumerate); ++ devs = udev_enumerate_get_list_entry(enumerate); ++ ++ for (item = devs; item; item = udev_list_entry_get_next(item)) { ++ const char *name = udev_list_entry_get_name(item); ++ struct udev_device *dev = udev_device_new_from_syspath(udev, name); ++ const char *devnode = udev_device_get_devnode(dev); ++ ++ if (devnode != NULL && sorted_count < 8) { ++ sorted[sorted_count].devnode = devnode; ++ sorted[sorted_count].item = item; ++ sorted_count++; ++ } else { ++ udev_device_unref(dev); ++ } ++ } ++ ++ /* Sort the udev entries by devnode name so that they are ++ * created in the proper order */ ++ qsort(sorted, sorted_count, ++ sizeof(struct event_udev_entry), sort_devnodes); ++ ++ for (i = 0; i < sorted_count; i++) { ++ if((i == us->port && us->numdevice == -1) || (i == us->numdevice && us->numdevice >= 0 )) { ++ const char *name = udev_list_entry_get_name(sorted[i].item); ++ ++ /* Get the filename of the /sys entry for the device ++ * and create a udev_device object (dev) representing it. */ ++ struct udev_device *dev = udev_device_new_from_syspath(udev, name); ++ const char *devnode = udev_device_get_devnode(dev); ++ char devname[64]; ++ ++ if (devnode) { ++ fd = open(devnode, O_RDONLY | O_NONBLOCK); ++ if (fd != -1) { ++ if (ioctl(fd, EVIOCGNAME(sizeof(devname)), devname) < 0) { ++ devname[0] = '\0'; ++ } ++ } ++ } ++ udev_device_unref(dev); ++ } ++ } ++ udev_enumerate_unref(enumerate); ++ } ++ if (udev != NULL) udev_unref(udev); ++ ++ // configure ++ us->udev_fd = fd; ++ if(fd != -1) { ++ udev_configure_gun(us); ++ } ++ } ++ ++ void GunCon2Device::udev_configure_gun(GunCon2State* us) { ++ struct input_absinfo absx, absy; ++ if(ioctl(us->udev_fd, EVIOCGABS(ABS_X), &absx) >= 0) { ++ if(ioctl(us->udev_fd, EVIOCGABS(ABS_Y), &absy) >= 0) { ++ us->udev_gunMinx = absx.minimum; ++ us->udev_gunMaxx = absx.maximum; ++ us->udev_gunMiny = absy.minimum; ++ us->udev_gunMaxy = absy.maximum; ++ } ++ } ++ } ++ + USBDevice* GunCon2Device::CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const + { + GunCon2State* s = new GunCon2State(port); ++ ++ // gun device ++ s->numdevice = USB::GetConfigInt(si, s->port, TypeName(), "numdevice", -1); ++ ++ udev_open_gun(s); ++ + s->desc.full = &s->desc_dev; + s->desc.str = desc_strings; + +diff --git a/pcsx2/USB/usb-lightgun/guncon2.h b/pcsx2/USB/usb-lightgun/guncon2.h +index 9b7d011..5d46d36 100644 +--- a/pcsx2/USB/usb-lightgun/guncon2.h ++++ b/pcsx2/USB/usb-lightgun/guncon2.h +@@ -18,6 +18,8 @@ + + namespace usb_lightgun + { ++ struct GunCon2State; ++ + class GunCon2Device final : public DeviceProxy + { + public: +@@ -30,5 +32,8 @@ namespace usb_lightgun + void SetBindingValue(USBDevice* dev, u32 bind_index, float value) const override; + std::span Bindings(u32 subtype) const override; + std::span Settings(u32 subtype) const override; ++ private: ++ static void udev_open_gun(GunCon2State* us); ++ static void udev_configure_gun(GunCon2State* us); + }; + } // namespace usb_lightgun diff --git a/package/batocera/emulators/pcsx2x6/006-patches-in-bios-folder.patch b/package/batocera/emulators/pcsx2x6/006-patches-in-bios-folder.patch new file mode 100644 index 00000000000..d99410f6915 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/006-patches-in-bios-folder.patch @@ -0,0 +1,11 @@ +--- a/pcsx2/Patch.cpp 2023-10-01 12:11:29.837825347 +0200 ++++ b/pcsx2/Patch.cpp 2023-10-01 12:11:48.690284822 +0200 +@@ -288,7 +288,7 @@ + if (s_patches_zip) + return true; + +- const std::string filename = Path::Combine(EmuFolders::Resources, PATCHES_ZIP_NAME); ++ const std::string filename = Path::Combine(EmuFolders::Bios, PATCHES_ZIP_NAME); + + zip_error ze = {}; + zip_source_t* zs = zip_source_file_create(filename.c_str(), 0, 0, &ze); diff --git a/package/batocera/emulators/pcsx2x6/007-center-notifications.patch b/package/batocera/emulators/pcsx2x6/007-center-notifications.patch new file mode 100644 index 00000000000..cf325236b45 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/007-center-notifications.patch @@ -0,0 +1,27 @@ +diff --git a/pcsx2/ImGui/ImGuiFullscreen.cpp b/pcsx2/ImGui/ImGuiFullscreen.cpp +index 49b06a50bd..a351c6f08d 100644 +--- a/pcsx2/ImGui/ImGuiFullscreen.cpp ++++ b/pcsx2/ImGui/ImGuiFullscreen.cpp +@@ -2640,6 +2640,10 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) + const u32 toast_title_color = s_light_theme ? IM_COL32(1, 1, 1, 255) : IM_COL32(0xff, 0xff, 0xff, 255); + const u32 toast_text_color = s_light_theme ? IM_COL32(0, 0, 0, 255) : IM_COL32(0xff, 0xff, 0xff, 255); + ++ // Get the screen width and height ++ const ImGuiIO& io = ImGui::GetIO(); ++ const float screen_width = io.DisplaySize.x; ++ + for (u32 index = 0; index < static_cast(s_notifications.size());) + { + Notification& notif = s_notifications[index]; +@@ -2696,7 +2696,10 @@ void ImGuiFullscreen::DrawNotifications(ImVec2& position, float spacing) + min_width); + const float box_height = + std::max((vertical_padding * 2.0f) + ImCeil(title_size.y) + vertical_spacing + ImCeil(text_size.y), min_height); +- ++ ++ // Center the notification horizontally ++ position.x = (screen_width - box_width) / 2.0f; ++ + u8 opacity; + if (time_passed < NOTIFICATION_FADE_IN_TIME) + opacity = static_cast((time_passed / NOTIFICATION_FADE_IN_TIME) * 255.0f); diff --git a/package/batocera/emulators/pcsx2x6/008-fix-sdl-input.patch b/package/batocera/emulators/pcsx2x6/008-fix-sdl-input.patch new file mode 100644 index 00000000000..03721774de3 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/008-fix-sdl-input.patch @@ -0,0 +1,18 @@ +diff --git a/pcsx2/Input/SDLInputSource.cpp b/pcsx2/Input/SDLInputSource.cpp.new +index 9b7995fb12..0cebf805e4 100644 +--- a/pcsx2/Input/SDLInputSource.cpp ++++ b/pcsx2/Input/SDLInputSource.cpp.new +@@ -1438,9 +1438,10 @@ bool SDLInputSource::CloseDevice(SDL_JoystickID joystick_index) + + { + std::lock_guard lock(m_controllers_key_mutex); +- InputManager::OnInputDeviceDisconnected( +- {InputBindingKey{.source_type = InputSourceType::SDL, .source_index = static_cast(it->player_id)}}, +- fmt::format("SDL-{}", it->player_id)); ++ // Properly initialize InputBindingKey ++ InputBindingKey key = {InputSourceType::SDL, static_cast(it->player_id)}; ++ ++ InputManager::OnInputDeviceDisconnected(key, fmt::format("SDL-{}", it->player_id)); + + if (it->haptic) + SDL_CloseHaptic(it->haptic); \ No newline at end of file diff --git a/package/batocera/emulators/pcsx2x6/009-statename.patch b/package/batocera/emulators/pcsx2x6/009-statename.patch new file mode 100644 index 00000000000..4e81e83295d --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/009-statename.patch @@ -0,0 +1,34 @@ +diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp +index b98f4f6..9f99764 100644 +--- a/pcsx2/VMManager.cpp ++++ b/pcsx2/VMManager.cpp +@@ -1764,20 +1764,15 @@ bool SaveStateBase::vmFreeze() + + std::string VMManager::GetSaveStateFileName(const char* game_serial, u32 game_crc, s32 slot, bool backup) + { +- std::string filename; +- if (std::strlen(game_serial) > 0) +- { +- if (slot < 0) +- filename = fmt::format("{} ({:08X}).resume.p2s", game_serial, game_crc); +- else if (backup) +- filename = fmt::format("{} ({:08X}).{:02d}.p2s.backup", game_serial, game_crc, slot); +- else +- filename = fmt::format("{} ({:08X}).{:02d}.p2s", game_serial, game_crc, slot); +- +- filename = Path::Combine(EmuFolders::Savestates, filename); +- } +- +- return filename; ++ if(slot < 0) { ++ return Path::Combine(EmuFolders::Savestates, std::string(Path::GetFileName(GetDiscPath())) + StringUtil::StdStringFromFormat(".p2s.auto")); ++ } ++ else if (backup) { ++ return Path::Combine(EmuFolders::Savestates, std::string(Path::GetFileName(GetDiscPath())) + StringUtil::StdStringFromFormat(".%02d.p2s.backup", slot)); ++ } ++ else { ++ return Path::Combine(EmuFolders::Savestates, std::string(Path::GetFileName(GetDiscPath())) + StringUtil::StdStringFromFormat(".%02d.p2s", slot)); ++ } + } + + std::string VMManager::GetSaveStateFileName(const char* filename, s32 slot, bool backup) \ No newline at end of file diff --git a/package/batocera/emulators/pcsx2x6/010-savestatescreenshot.patch b/package/batocera/emulators/pcsx2x6/010-savestatescreenshot.patch new file mode 100644 index 00000000000..39505e14bbd --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/010-savestatescreenshot.patch @@ -0,0 +1,14 @@ +diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp +index fe20da7..12c5d93 100644 +--- a/pcsx2/VMManager.cpp ++++ b/pcsx2/VMManager.cpp +@@ -1850,6 +1850,9 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip + ZipSaveState(std::move(elist), std::move(screenshot), std::move(osd_key), filename, slot_for_message); + } + ++ // screenshot in dirctory ++ GSQueueSnapshot(fmt::format("{}.png", filename)); ++ + Host::OnSaveStateSaved(filename); + return true; + } diff --git a/package/batocera/emulators/pcsx2x6/011-savestatestartslot.patch b/package/batocera/emulators/pcsx2x6/011-savestatestartslot.patch new file mode 100644 index 00000000000..d04ef02dc2d --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/011-savestatestartslot.patch @@ -0,0 +1,45 @@ +diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp +index 5587e92..43608c0 100644 +--- a/pcsx2-qt/QtHost.cpp ++++ b/pcsx2-qt/QtHost.cpp +@@ -2123,6 +2123,11 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptrsave_state = (++it)->toStdString(); + continue; + } ++ else if (CHECK_ARG_PARAM(QStringLiteral("-stateindex"))) ++ { ++ AutoBoot(autoboot)->state_start_index = (++it)->toInt(); ++ continue; ++ } + else if (CHECK_ARG_PARAM(QStringLiteral("-elf"))) + { + AutoBoot(autoboot)->elf_override = (++it)->toStdString(); +diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp +index 12c5d93..3324fc5 100644 +--- a/pcsx2/VMManager.cpp ++++ b/pcsx2/VMManager.cpp +@@ -1561,6 +1561,12 @@ bool VMManager::Initialize(VMBootParameters boot_params) + + SetEmuThreadAffinities(); + ++ if (boot_params.state_start_index.has_value()) { ++ for(int i=1; i state_index; ++ std::optional state_start_index; + std::optional source_type; + + std::optional fast_boot; diff --git a/package/batocera/emulators/pcsx2x6/012-savestateincrement.patch b/package/batocera/emulators/pcsx2x6/012-savestateincrement.patch new file mode 100644 index 00000000000..59ac3d9f699 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/012-savestateincrement.patch @@ -0,0 +1,49 @@ +diff --git a/pcsx2/Config.h b/pcsx2/Config.h +index 3b12a96..382b19b 100644 +--- a/pcsx2/Config.h ++++ b/pcsx2/Config.h +@@ -1359,6 +1359,7 @@ struct Pcsx2Config + UseSavestateSelector : 1, + InhibitScreensaver : 1, + BackupSavestate : 1, ++ AutoIncrementSlot : 1, + ManuallySetRealTimeClock : 1, // passes user-set real-time clock information to cdvd at startup + UseSystemLocaleFormat : 1, // presents OS time format instead of yyyy-MM-dd HH:mm:ss for manual RTC + +diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp +index 9a715c0..ef746da 100644 +--- a/pcsx2/Pcsx2Config.cpp ++++ b/pcsx2/Pcsx2Config.cpp +@@ -1931,6 +1931,7 @@ Pcsx2Config::Pcsx2Config() + EnableDiscordPresence = false; + ManuallySetRealTimeClock = false; + UseSystemLocaleFormat = false; ++ AutoIncrementSlot = false; + + // To be moved to FileMemoryCard pluign (someday) + for (uint slot = 0; slot < 8; ++slot) +@@ -1734,6 +1735,7 @@ void Pcsx2Config::LoadSaveCore(SettingsWrapper& wrap) + SettingsWrapBitBool(EnableDiscordPresence); + SettingsWrapBitBool(InhibitScreensaver); + SettingsWrapBitBool(HostFs); ++ SettingsWrapBitBool(AutoIncrementSlot); + + SettingsWrapBitBool(BackupSavestate); + SettingsWrapBitBool(SavestateZstdCompression); +diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp +index 3324fc5..46e02c4 100644 +--- a/pcsx2/VMManager.cpp ++++ b/pcsx2/VMManager.cpp +@@ -2022,6 +2022,12 @@ bool VMManager::SaveState(const char* filename, bool zip_on_thread, bool backup_ + + bool VMManager::SaveStateToSlot(s32 slot, bool zip_on_thread) + { ++ //increment the slot before saving ++ if(EmuConfig.AutoIncrementSlot) { ++ SaveStateSelectorUI::SelectNextSlot(false); ++ slot = SaveStateSelectorUI::GetCurrentSlot(); ++ } ++ + const std::string filename(GetCurrentSaveStateFileName(slot)); + if (filename.empty()) + return false; diff --git a/package/batocera/emulators/pcsx2x6/014-supress-renderer-warning.patch b/package/batocera/emulators/pcsx2x6/014-supress-renderer-warning.patch new file mode 100644 index 00000000000..cba6b00faab --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/014-supress-renderer-warning.patch @@ -0,0 +1,25 @@ +diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp +index 5fd4c05791..0b126a0afc 100644 +--- a/pcsx2/VMManager.cpp ++++ b/pcsx2/VMManager.cpp +@@ -3217,14 +3217,14 @@ void VMManager::WarnAboutUnsafeSettings() + TRANSLATE_SV("VMManager", "Integer scaling is enabled. This may shrink the image.")); + } + static bool render_change_warn = false; +- if (EmuConfig.GS.Renderer != GSRendererType::Auto && EmuConfig.GS.Renderer != GSRendererType::SW && !render_change_warn) +- { ++ //if (EmuConfig.GS.Renderer != GSRendererType::Auto && EmuConfig.GS.Renderer != GSRendererType::SW && !render_change_warn) ++ //{ + // show messagesbox +- render_change_warn = true; ++ //render_change_warn = true; + +- append(ICON_FA_CIRCLE_EXCLAMATION, +- TRANSLATE_SV("VMManager", "Graphics API is not set to Automatic. This may cause performance problems and graphical issues.")); +- } ++ //append(ICON_FA_CIRCLE_EXCLAMATION, ++ //TRANSLATE_SV("VMManager", "Graphics API is not set to Automatic. This may cause performance problems and graphical issues.")); ++ //} + } + if (EmuConfig.GS.TextureFiltering != BiFiltering::PS2) + { \ No newline at end of file diff --git a/package/batocera/emulators/pcsx2x6/016-version-fix-for-retroachievements.patch b/package/batocera/emulators/pcsx2x6/016-version-fix-for-retroachievements.patch new file mode 100644 index 00000000000..f9577eaad18 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/016-version-fix-for-retroachievements.patch @@ -0,0 +1,22 @@ +--- pcsx2-v2.4.0/pcsx2/BuildVersion.cpp 2025-06-29 12:47:12.000000000 -0700 ++++ pcsx2-v2.4.0-lbrpdx/pcsx2/BuildVersion.cpp 2025-10-03 08:39:48.770869570 -0700 +@@ -5,12 +5,12 @@ + + namespace BuildVersion + { +- const char* GitTag = GIT_TAG; +- bool GitTaggedCommit = GIT_TAGGED_COMMIT; +- int GitTagHi = GIT_TAG_HI; +- int GitTagMid = GIT_TAG_MID; +- int GitTagLo = GIT_TAG_LO; +- const char* GitRev = GIT_REV; +- const char* GitHash = GIT_HASH; ++ const char* GitTag = "v2.5.229"; ++ bool GitTaggedCommit = 1; ++ int GitTagHi = 2; ++ int GitTagMid = 5; ++ int GitTagLo = 229; ++ const char* GitRev = "v2.5.229"; ++ const char* GitHash = "8fb2940f25876d404b0a7b9ec39d55b2b3abe9b9"; + const char* GitDate = GIT_DATE; + } // namespace BuildVersion diff --git a/package/batocera/emulators/pcsx2x6/Config.in b/package/batocera/emulators/pcsx2x6/Config.in new file mode 100644 index 00000000000..07bbce4527a --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/Config.in @@ -0,0 +1,34 @@ +config BR2_PACKAGE_PCSX2X6 + bool "pcsx2x6" + depends on BR2_x86_64 + depends on BR2_PACKAGE_XORG7 + select BR2_PACKAGE_ALSA_LIB + select BR2_PACKAGE_BATOCERA_QT6 + select BR2_PACKAGE_BR2_PACKAGE_FMT + select BR2_PACKAGE_DEJAVU + select BR2_PACKAGE_ECM + select BR2_PACKAGE_FREETYPE + select BR2_PACKAGE_KDDOCKWIDGETS + select BR2_PACKAGE_LIBAIO + select BR2_PACKAGE_LIBBACKTRACE + select BR2_PACKAGE_LIBCURL + select BR2_PACKAGE_LIBGTK3 + select BR2_PACKAGE_LIBPCAP + select BR2_PACKAGE_LIBPNG + select BR2_PACKAGE_LIBSAMPLERATE + select BR2_PACKAGE_LIBSOUNDTOUCH + select BR2_PACKAGE_PLUTOSVG + select BR2_PACKAGE_PORTAUDIO + select BR2_PACKAGE_RAPIDYAML + select BR2_PACKAGE_SDL3 + select BR2_PACKAGE_SHADERC + select BR2_PACKAGE_VULKAN_HEADERS if BR2_PACKAGE_BATOCERA_VULKAN + select BR2_PACKAGE_VULKAN_LOADER if BR2_PACKAGE_BATOCERA_VULKAN + select BR2_PACKAGE_WEBP + select BR2_PACKAGE_WXWIDGETS + select BR2_PACKAGE_YAML_CPP + select BR2_PACKAGE_ZLIB + help + PCSX2X6 is a fork of PCSX2 to emulate NAMCO System246 and System256 arcade units. + + https://github.com/PS2Homebrew-arcade/pcsx2x6 diff --git a/package/batocera/emulators/pcsx2x6/crosshairs/default.png b/package/batocera/emulators/pcsx2x6/crosshairs/default.png new file mode 100644 index 00000000000..1320425480c Binary files /dev/null and b/package/batocera/emulators/pcsx2x6/crosshairs/default.png differ diff --git a/package/batocera/emulators/pcsx2x6/pcsx2x6.emulator.yml b/package/batocera/emulators/pcsx2x6/pcsx2x6.emulator.yml new file mode 100644 index 00000000000..2dfc8958502 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/pcsx2x6.emulator.yml @@ -0,0 +1,304 @@ +cores: + pcsx2x6: + shared_features: + - powermode + - tdp + - videomode + - bezel + - bezel_stretch + - hud + - hud_corner + - bezel.tattoo + - bezel.tattoo_corner + - bezel.tattoo_file + - bezel.resize_tattoo + - use_guns + - use_wheels + - wheel_rotation + - wheel_deadzone + - wheel_midzone + features: + - cheevos + custom_features: + pcsx2x6_shaderset: + submenu: RENDERING + prompt: SHADER SET + description: + choices: + scanlines: 1 + diagonal filter: 2 + triangular filter: 3 + wave filter: 4 + lottes crt: 5 + 4xrgss: 6 + nxagss: 7 + pcsx2x6_osd_messages: + submenu: DISPLAY + prompt: OSD MESSAGES + description: Display on screen information like savestate, config warning etc... + choices: + Enabled (Default): "true" + Disabled: "false" + pcsx2x6_osd_messages_position: + submenu: DISPLAY + prompt: OSD MESSAGES POSITION + description: Choose the on-screen position for system messages. + choices: + Top Left: 1 + Top Center (Default): 2 + Top Right: 3 + Center Left: 4 + Center: 5 + Center Right: 6 + Bottom Left: 7 + Bottom Center: 8 + Bottom Right: 9 + pcsx2x6_osd_performance_position: + submenu: DISPLAY + prompt: OSD PERFORMANCE POSITION + description: Choose the on-screen position for performance metrics (FPS, CPU usage, etc.). + choices: + None (Default): 0 + Top Left: 1 + Top Center: 2 + Top Right: 3 + Center Left: 4 + Center: 5 + Center Right: 6 + Bottom Left: 7 + Bottom Center: 8 + Bottom Right: 9 + pcsx2x6_ratio: + submenu: DISPLAY + prompt: GAME ASPECT RATIO + description: Force the game to render in this aspect ratio. Not all systems support all ratios. + choices: + Fit to Window / Fullscreen: Stretch + Auto Standard (4:3 interlaced / 3:2 progressive): Auto 4:3/3:2 + Standard (4:3): 4:3 + Widescreen (16:9): 16:9 + 10:7 (1.43:1 - custom): 10:7 + pcsx2x6_fmv_ratio: + submenu: DISPLAY + prompt: FMV ASPECT RATIO + description: Adjusts Full Motion Video movies aspect ratio. + choices: + Off (Default): "off" + Auto Standard (4:3 interlaced / 3:2 progressive): Auto 4:3/3:2 + Standard (4:3): 4:3 + Widescreen (16:9): 16:9 + 10:7 (1.43:1 - custom): 10:7 + pcsx2x6_crop_overscan: + submenu: DISPLAY + prompt: CROP OVERSCAN + description: Crops a small number of pixels from the screen edges to hide visual artifacts. + preset: switchauto + pcsx2x6_deinterlacing: + submenu: DISPLAY + prompt: DEINTERLACING + description: FMV movie deinterlacing. + choices: + None: 1 + Weave (Top Field First): 2 + Weave (Bottom Field First): 3 + Bob (Top Field First): 4 + Bob (Bottom Field First): 5 + Blend (Top Field First): 6 + Blend (Bottom Field First): 7 + Adaptive (Top Field First): 8 + Adaptive (Bottom Field First): 9 + pcsx2x6_bilinear_filtering: + submenu: DISPLAY + prompt: BILINEAR FILTERING + description: Smooths the overall picture as it is displayed on the screen. Corrects positioning between pixels. + choices: + None: 0 + Bilinear (Smooth): 1 + Bilinear (Sharp): 2 + pcsx2x6_EnableWideScreenPatches: + submenu: DISPLAY + prompt: ENABLE WIDESCREEN PATCHES + description: Enhancement. Only works with a 16/9 ratio and bezels disabled. + choices: + Disabled: "false" + Enabled: "true" + pcsx2x6_blur: + submenu: DISPLAY + prompt: ANTI-BLUR + description: Enable to improve bluriness. + choices: + Disabled: "false" + Enabled: "true" + pcsx2x6_vsync: + submenu: DISPLAY + prompt: VSYNC + description: Fix screen tearing. CPU heavy. + choices: + Disabled: 0 + Enabled: 1 + pcsx2x6_interlacing_patches: + submenu: DISPLAY + prompt: INTERLACING PATCHES + description: Enable No-interlacing Patches. + choices: + Disabled: "false" + Enabled: "true" + pcsx2x6_scaling: + submenu: DISPLAY + prompt: INTEGER SCALING + description: Can sharpen images in lower resolutions. + choices: + Disabled: "false" + Enabled: "true" + pcsx2x6_gfxbackend: + submenu: RENDERING + prompt: GRAPHICS API + description: Choose which graphics API library to use. + choices: + OpenGL: 12 + Software: 13 + Vulkan: 14 + pcsx2x6_resolution: + submenu: RENDERING + prompt: RENDERING RESOLUTION + description: Enhancement. Increase the rendering resolution. Makes 3D objects clearer. + choices: + Native (PS2)(Default): 1 + 1.25x Native: 1.25 + 1.5x Native: 1.5 + 1.75x Native: 1.75 + 2x Native (~720p): 2 + 2.25x Native: 2.25 + 2.5x Native: 2.5 + 2.75x Native: 2.75 + 3x Native (~1080p): 3 + 3.5x Native: 3.5 + 4x Native (~1440p/2K): 4 + 5x Native (~1620p): 5 + 6x Native (~2160p/4K): 6 + 7x Native (~2520p): 7 + 8x Native (~2880p/5K): 8 + pcsx2x6_mipmapping: + submenu: RENDERING + prompt: MIPMAPPING + description: Choose mipmapping type. + choices: + "Off": 0 + Basic (generated Mipmaps): 1 + Full (PS2 Mipmaps): 2 + pcsx2x6_texture_filtering: + submenu: RENDERING + prompt: TEXTURE FILTERING + description: Control the texture filtering of the emulation. + choices: + Nearest: 0 + Bilinear (Forced): 1 + Bilinear (PS2): 2 + Bilinear (Forced excluding sprite): 3 + pcsx2x6_trilinear_filtering: + submenu: RENDERING + prompt: TRILINEAR FILTERING + description: Control the texture's trilinear filtering of the emulation. + choices: + Off (None): 0 + Trilinear (PS2): 1 + Trilinear (Forced): 2 + pcsx2x6_anisotropic_filtering: + submenu: RENDERING + prompt: ANISOTROPIC FILTERING + description: Improves clarity of distant textures. + choices: + "Off": 0 + 2x: 2 + 4x: 4 + 8x: 8 + 16x: 16 + pcsx2x6_dithering: + submenu: RENDERING + prompt: DITHERING + description: Reduces banding between colours and improves the preceived colour depth. + choices: + "Off": 0 + Unscaled (Default): 1 + Scaled: 2 + Force 32bit: 3 + pcsx2x6_blending: + submenu: RENDERING + prompt: BLENDING ACCURACY + description: The higher the setting, the more blending is emulated in the shader accurately, and the higher the speed penalty will be. + choices: + Minimum: 0 + Basic (Recommended): 1 + Medium: 2 + High: 3 + Full (Slow): 4 + Maximum (Very Slow): 5 + pcsx2x6_texture_loading: + submenu: RENDERING + prompt: TEXTURE PRELOADING + description: Uploads entire textures at once instead of small pieces. Improves performance in most games. + choices: + None: 0 + Partial: 1 + Full (Hash Cache): 2 + pcsx2x6_fxaa: + submenu: RENDERING + prompt: FXAA + description: Applies the FXAA anti-aliasing algorithm to improve the visual quality of games. + choices: + Disabled: "false" + Enabled: "true" + pcsx2x6_cheats: + prompt: CHEATS + description: Automatically loads and applies cheats on game start. + choices: + Disabled: "false" + Enabled: "true" + pcsx2x6_vuthread: + submenu: SPEEDHACKS + group: ADVANCED OPTIONS + prompt: MULTI-THREADED VU1 + description: Runs VU1 emulation on a separate thread to improve performance on multi-core CPUs. May cause issues in some games. + preset: switchauto + pcsx2x6_eecyclerate: + submenu: SPEEDHACKS + group: ADVANCED OPTIONS + prompt: EE CYCLE RATE + description: Adjusts the emulated PS2 CPU speed. Lower or higher values may improve performance or game speed, but may cause issues. + choices: + "50%": -3 + "60%": -2 + "75%": -1 + "100%(Default)": 0 + "130%": 1 + "180%": 2 + "300%": 3 + pcsx2x6_texture_replacements: + group: ADVANCED OPTIONS + prompt: TEXTURE REPLACEMENTS + description: Allows user replaceable textures. + choices: + "Off": "false" + "On": "true" + pcsx2x6_wheel_type: + order: 100 + group: CONTROLS + submenu: WHEELS + prompt: WHEEL MODEL + description: Playstation 2 wheel type + choices: + Driving Force: DrivingForce + Driving Force Pro: DrivingForcePro + GT Force: GTForce + Virtual: Virtual + pcsx2x6_crosshairs: + order: 99 + group: CONTROLS + submenu: LIGHT GUNS + prompt: SHOW LIGHT GUN CROSSHAIRS + description: + choices: + "Off": 0 + "On": 1 +systems: + - name: namco2x6 diff --git a/package/batocera/emulators/pcsx2x6/pcsx2x6.mk b/package/batocera/emulators/pcsx2x6/pcsx2x6.mk new file mode 100644 index 00000000000..9eb1944d6e0 --- /dev/null +++ b/package/batocera/emulators/pcsx2x6/pcsx2x6.mk @@ -0,0 +1,97 @@ +################################################################################ +# +# pcsx2x6 +# +################################################################################ +# Version: Commits on Jul 20, 2026 +PCSX2X6_VERSION = 758326a085f2029cef4df08e8b23a3e574cfd085 +PCSX2X6_SITE = https://github.com/PS2Homebrew-arcade/pcsx2x6.git +PCSX2X6_SITE_METHOD = git +PCSX2X6_GIT_SUBMODULES = YES +PCSX2X6_LICENSE = GPLv3 +PCSX2X6_LICENSE_FILE = COPYING.GPLv3 +PCSX2X6_EMULATOR_INFO = pcsx2x6.emulator.yml + +PCSX2X6_SUPPORTS_IN_SOURCE_BUILD = NO + +PCSX2X6_DEPENDENCIES += alsa-lib ecm fmt freetype host-clang host-libcurl kddockwidgets +PCSX2X6_DEPENDENCIES += libaio libbacktrace libcurl libgtk3 libpcap libpng libsamplerate +PCSX2X6_DEPENDENCIES += libsoundtouch plutosvg portaudio qt6base qt6svg qt6tools +PCSX2X6_DEPENDENCIES += rapidyaml shaderc sdl3 webp wxwidgets xorgproto yaml-cpp zlib + +# Use clang for performance +PCSX2X6_CONF_OPTS += -DCMAKE_C_COMPILER=$(HOST_DIR)/bin/clang +PCSX2X6_CONF_OPTS += -DCMAKE_CXX_COMPILER=$(HOST_DIR)/bin/clang++ +PCSX2X6_CONF_OPTS += -DCMAKE_EXE_LINKER_FLAGS="-lm -lstdc++" + +ifeq ($(BR2_aarch64),y) +PCSX2X6_CONF_OPTS += -DCMAKE_C_FLAGS="$(TARGET_CFLAGS)" +PCSX2X6_CONF_OPTS += -DCMAKE_CXX_FLAGS="$(TARGET_CXXFLAGS) -Wno-c++11-narrowing -Wno-narrowing" +endif + +PCSX2X6_CONF_OPTS += -DCMAKE_BUILD_TYPE=Release +PCSX2X6_CONF_OPTS += -DBUILD_SHARED_LIBS=OFF +PCSX2X6_CONF_OPTS += -DENABLE_TESTS=OFF +PCSX2X6_CONF_OPTS += -DUSE_SYSTEM_LIBS=AUTO +# The following flag is misleading and *needed* ON to avoid doing -march=native +PCSX2X6_CONF_OPTS += -DDISABLE_ADVANCE_SIMD=ON + +# below may not be needed for newer versions +define PCSX2X6_FIX_WHOLE_ARCHIVE + find $(@D) -name "CMakeLists.txt" -exec sed -i 's|.[<]LINK_LIBRARY:WHOLE_ARCHIVE,\([^>]*\)>|-Wl,--whole-archive \1 -Wl,--no-whole-archive|g' {} + +endef +PCSX2X6_PRE_CONFIGURE_HOOKS += PCSX2X6_FIX_WHOLE_ARCHIVE + +ifeq ($(BR2_PACKAGE_XORG7),y) + PCSX2X6_CONF_OPTS += -DX11_API=ON +else + PCSX2X6_CONF_OPTS += -DX11_API=OFF +endif + +ifeq ($(BR2_PACKAGE_BATOCERA_WAYLAND),y) + PCSX2X6_CONF_OPTS += -DWAYLAND_API=ON +else + PCSX2X6_CONF_OPTS += -DWAYLAND_API=OFF +endif + +ifeq ($(BR2_PACKAGE_HAS_LIBGL),y) + PCSX2X6_CONF_OPTS += -DUSE_OPENGL=ON +else + PCSX2X6_CONF_OPTS += -DUSE_OPENGL=OFF +endif + +ifeq ($(BR2_PACKAGE_BATOCERA_VULKAN),y) + PCSX2X6_CONF_OPTS += -DUSE_VULKAN=ON +else + PCSX2X6_CONF_OPTS += -DUSE_VULKAN=OFF +endif + +define PCSX2X6_INSTALL_TARGET_CMDS + $(INSTALL) -m 0755 -D $(@D)/buildroot-build/bin/pcsx2-qt \ + $(TARGET_DIR)/usr/pcsx2x6/bin/pcsx2x6-qt + cp -pr $(@D)/bin/resources $(TARGET_DIR)/usr/pcsx2x6/bin/ + cp -pr $(@D)/buildroot-build/bin/translations $(TARGET_DIR)/usr/pcsx2x6/bin/ + # use our SDL config + rm $(TARGET_DIR)/usr/pcsx2x6/bin/resources/game_controller_db.txt +endef + +# Download and copy PCSX2X6 patches.zip to BIOS folder +define PCSX2X6_PATCHES + mkdir -p $(TARGET_DIR)/usr/share/batocera/datainit/bios/namco2x6 + $(HOST_DIR)/bin/curl -L \ + https://github.com/PS2Homebrew-arcade/pcsx2x6_patches/releases/download/latest/patches.zip -o \ + $(TARGET_DIR)/usr/share/batocera/datainit/bios/namco2x6/patches.zip +endef + +PCSX2X6_POST_INSTALL_TARGET_HOOKS += PCSX2X6_PATCHES + +define PCSX2X6_CROSSHAIRS + mkdir -p $(TARGET_DIR)/usr/pcsx2x6/bin/resources/crosshairs + cp -pr $(BR2_EXTERNAL_BATOCERA_PATH)/package/batocera/emulators/pcsx2x6/crosshairs/ \ + $(TARGET_DIR)/usr/pcsx2x6/bin/resources/ +endef + +PCSX2X6_POST_INSTALL_TARGET_HOOKS += PCSX2X6_CROSSHAIRS + +$(eval $(cmake-package)) +$(eval $(emulator-info-package)) diff --git a/package/batocera/emulators/play/001-fpic.patch b/package/batocera/emulators/play/001-fpic.patch deleted file mode 100644 index 674557622b7..00000000000 --- a/package/batocera/emulators/play/001-fpic.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/CMakeLists.txt 2022-07-06 17:20:45.649840228 +0200 -+++ b/CMakeLists.txt 2022-07-06 17:21:21.050594954 +0200 -@@ -60,6 +60,7 @@ - endif() - - if(BUILD_PLAY) -+ add_compile_options(-fPIC) - if(TARGET_PLATFORM_UNIX OR TARGET_PLATFORM_MACOS OR TARGET_PLATFORM_WIN32 OR USE_QT) - add_subdirectory(Source/ui_qt/) - add_subdirectory(tools/ElfView) diff --git a/package/batocera/emulators/play/002-fix-arcadepath.patch b/package/batocera/emulators/play/002-fix-arcadepath.patch deleted file mode 100644 index 66faef941e0..00000000000 --- a/package/batocera/emulators/play/002-fix-arcadepath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/deps/Framework/src/PathUtils.cpp b/deps/Framework/src/PathUtils.cpp -index 42cabcde87..e47a38b0fd 100644 ---- a/deps/Framework/src/PathUtils.cpp -+++ b/deps/Framework/src/PathUtils.cpp -@@ -138,7 +138,7 @@ fs::path PathUtils::GetAppResourcesPath() - //TODO: We should probably append an app name to this path, as installing there pollutes the directory. - //Also, the prefix path can be overridden by the build system, which makes hardcoding this not appropriate, - //but works for the default settings. -- return "/usr/local/share"; -+ return "/usr/share"; - } - - fs::path PathUtils::GetRoamingDataPath() diff --git a/package/batocera/emulators/play/003-gcc13-fix.patch b/package/batocera/emulators/play/003-gcc13-fix.patch deleted file mode 100644 index 97c76244e2c..00000000000 --- a/package/batocera/emulators/play/003-gcc13-fix.patch +++ /dev/null @@ -1,55 +0,0 @@ -diff --git a/deps/Framework/src/vulkan/Instance.cpp b/deps/Framework/src/vulkan/Instance.cpp -index 8a3b7b36ac..b39cb766f7 100644 ---- a/deps/Framework/src/vulkan/Instance.cpp -+++ b/deps/Framework/src/vulkan/Instance.cpp -@@ -1,6 +1,7 @@ - #include "vulkan/VulkanDef.h" - #include "vulkan/Instance.h" - #include "vulkan/Loader.h" -+#include - - #define SET_PROC_ADDR(functionName) this->functionName = reinterpret_cast(CLoader::GetInstance().vkGetInstanceProcAddr(m_handle, #functionName)); - -diff --git a/Source/hdd/ApaDefs.h b/Source/hdd/ApaDefs.h -index ae9b5bcc58..1bba3b951e 100644 ---- a/Source/hdd/ApaDefs.h -+++ b/Source/hdd/ApaDefs.h -@@ -1,4 +1,5 @@ - #pragma once -+#include - - namespace Hdd - { -diff --git a/Source/hdd/ApaReader.cpp b/Source/hdd/ApaReader.cpp -index bf9b586142..e7dfbf8fae 100644 ---- a/Source/hdd/ApaReader.cpp -+++ b/Source/hdd/ApaReader.cpp -@@ -2,6 +2,7 @@ - #include - #include - #include "HddDefs.h" -+#include - - using namespace Hdd; - -diff --git a/Source/hdd/PfsDefs.h b/Source/hdd/PfsDefs.h -index 5557e2d5ba..1a13bdbf93 100644 ---- a/Source/hdd/PfsDefs.h -+++ b/Source/hdd/PfsDefs.h -@@ -1,3 +1,4 @@ -+#include - #pragma once - - namespace Hdd -diff --git a/Source/hdd/PfsReader.cpp b/Source/hdd/PfsReader.cpp -index 3db9fdcc80..74248692c9 100644 ---- a/Source/hdd/PfsReader.cpp -+++ b/Source/hdd/PfsReader.cpp -@@ -3,6 +3,7 @@ - #include - #include "HddDefs.h" - #include "StringUtils.h" -+#include - - using namespace Hdd; - diff --git a/package/batocera/emulators/play/004-lightgun-support.patch b/package/batocera/emulators/play/004-lightgun-support.patch deleted file mode 100644 index b843e0106ff..00000000000 --- a/package/batocera/emulators/play/004-lightgun-support.patch +++ /dev/null @@ -1,269 +0,0 @@ ---- a/Source/ui_qt/unix/InputProviderEvDev.h -+++ b/Source/ui_qt/unix/InputProviderEvDev.h -@@ -1,19 +1,39 @@ - #pragma once - -+#include -+#include -+#include - #include "input/InputProvider.h" - #include "GamePadUtils.h" - #include "GamePadDeviceListener.h" - -+class CPS2VM; -+ - class CInputProviderEvDev : public CInputProvider - { -+ struct LightGunDevice -+ { -+ int fd = -1; -+ struct libevdev* dev = nullptr; -+ int minX = 0, maxX = 1, minY = 0, maxY = 1; -+ float lastX = 0.5f, lastY = 0.5f; -+ }; -+ - public: -- CInputProviderEvDev(); -- virtual ~CInputProviderEvDev() = default; -+ CInputProviderEvDev(CPS2VM* = nullptr); -+ ~CInputProviderEvDev(); - - uint32 GetId() const override; - std::string GetTargetDescription(const BINDINGTARGET&) const override; -+ bool HasLightGuns() const; - - private: - void OnEvDevInputEvent(GamePadDeviceId, int, int, int, const input_absinfo*); -+ void ScanLightGuns(); -+ void PollLightGuns(); - CGamePadDeviceListener m_GPDL; -+ CPS2VM* m_vm = nullptr; -+ std::vector m_guns; -+ std::atomic m_gunPolling{false}; -+ std::thread m_gunThread; - }; ---- a/Source/ui_qt/unix/InputProviderEvDev.cpp -+++ b/Source/ui_qt/unix/InputProviderEvDev.cpp -@@ -1,12 +1,36 @@ - #include -+#include -+#include -+#include -+#include - #include "InputProviderEvDev.h" -+#include "PS2VM.h" -+#include "gs/GSHandler.h" - #include "string_format.h" - - #define PROVIDER_ID 'evdv' - --CInputProviderEvDev::CInputProviderEvDev() -+CInputProviderEvDev::CInputProviderEvDev(CPS2VM* vm) - : m_GPDL([this](auto a, auto b, auto c, auto d, auto e) { this->OnEvDevInputEvent(a, b, c, d, e); }) -+ , m_vm(vm) - { -+ ScanLightGuns(); -+ if(!m_guns.empty()) -+ { -+ m_gunPolling = true; -+ m_gunThread = std::thread([this]() { PollLightGuns(); }); -+ } -+} -+ -+CInputProviderEvDev::~CInputProviderEvDev() -+{ -+ m_gunPolling = false; -+ if(m_gunThread.joinable()) m_gunThread.join(); -+ for(auto& g : m_guns) -+ { -+ if(g.dev) libevdev_free(g.dev); -+ if(g.fd >= 0) close(g.fd); -+ } - } - - uint32 CInputProviderEvDev::GetId() const -@@ -19,6 +43,11 @@ - return string_format("EvDev: btn-%d", target.keyId); - } - -+bool CInputProviderEvDev::HasLightGuns() const -+{ -+ return !m_guns.empty(); -+} -+ - void CInputProviderEvDev::OnEvDevInputEvent(GamePadDeviceId deviceId, int code, int value, int type, const input_absinfo* abs) - { - BINDINGTARGET tgt; -@@ -66,3 +95,102 @@ - OnInput(tgt, fixedValue); - } - } -+ -+void CInputProviderEvDev::ScanLightGuns() -+{ -+ struct udev* udev = udev_new(); -+ if(!udev) return; -+ -+ struct udev_enumerate* enumerate = udev_enumerate_new(udev); -+ udev_enumerate_add_match_subsystem(enumerate, "input"); -+ udev_enumerate_add_match_property(enumerate, "ID_INPUT_GUN", "1"); -+ udev_enumerate_scan_devices(enumerate); -+ -+ struct udev_list_entry* entry; -+ udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) -+ { -+ struct udev_device* udevDev = udev_device_new_from_syspath(udev, udev_list_entry_get_name(entry)); -+ const char* devnode = udev_device_get_devnode(udevDev); -+ if(devnode && strstr(devnode, "/dev/input/event")) -+ { -+ int fd = open(devnode, O_RDONLY | O_NONBLOCK); -+ if(fd >= 0) -+ { -+ struct libevdev* dev = nullptr; -+ if(libevdev_new_from_fd(fd, &dev) >= 0) -+ { -+ const struct input_absinfo* absX = libevdev_get_abs_info(dev, ABS_X); -+ const struct input_absinfo* absY = libevdev_get_abs_info(dev, ABS_Y); -+ if(absX && absY) -+ { -+ LightGunDevice gun; -+ gun.fd = fd; -+ gun.dev = dev; -+ gun.minX = absX->minimum; gun.maxX = absX->maximum; -+ gun.minY = absY->minimum; gun.maxY = absY->maximum; -+ m_guns.push_back(gun); -+ } -+ else -+ { -+ libevdev_free(dev); -+ close(fd); -+ } -+ } -+ else -+ { -+ close(fd); -+ } -+ } -+ } -+ udev_device_unref(udevDev); -+ } -+ udev_enumerate_unref(enumerate); -+ udev_unref(udev); -+} -+ -+void CInputProviderEvDev::PollLightGuns() -+{ -+ while(m_gunPolling) -+ { -+ for(auto& gun : m_guns) -+ { -+ if(!gun.dev) continue; -+ struct input_event ev; -+ int rc; -+ while((rc = libevdev_next_event(gun.dev, LIBEVDEV_READ_FLAG_NORMAL, &ev)) == LIBEVDEV_READ_STATUS_SUCCESS) -+ { -+ if(ev.type == EV_ABS) -+ { -+ if(ev.code == ABS_X) -+ gun.lastX = (gun.maxX > gun.minX) ? std::clamp((float)(ev.value - gun.minX) / (gun.maxX - gun.minX), 0.f, 1.f) : 0.5f; -+ else if(ev.code == ABS_Y) -+ gun.lastY = (gun.maxY > gun.minY) ? std::clamp((float)(ev.value - gun.minY) / (gun.maxY - gun.minY), 0.f, 1.f) : 0.5f; -+ if(m_vm) -+ { -+ float x = gun.lastX, y = gun.lastY; -+ if(auto gs = m_vm->GetGSHandler()) -+ { -+ auto vp = gs->GetPresentationViewport(); -+ if(vp.width > 0 && vp.height > 0) -+ { -+ x = std::clamp((gun.lastX * (vp.offsetX * 2 + vp.width) - vp.offsetX) / vp.width, 0.f, 1.f); -+ y = std::clamp((gun.lastY * (vp.offsetY * 2 + vp.height) - vp.offsetY) / vp.height, 0.f, 1.f); -+ } -+ } -+ m_vm->ReportGunPosition(x, y); -+ } -+ } -+ else if(ev.type == EV_KEY) -+ { -+ BINDINGTARGET tgt; -+ tgt.providerId = PROVIDER_ID; -+ tgt.deviceId = {0, 0, 0, 0, 0, 0}; -+ tgt.keyId = ev.code; -+ tgt.keyType = BINDINGTARGET::KEYTYPE::BUTTON; -+ OnInput(tgt, ev.value); -+ } -+ } -+ } -+ std::this_thread::sleep_for(std::chrono::milliseconds(8)); -+ } -+} ---- a/Source/ui_qt/mainwindow.h -+++ b/Source/ui_qt/mainwindow.h -@@ -15,6 +15,10 @@ - #include "InputProviderQtMouse.h" - #include "ScreenShotUtils.h" - -+#ifdef HAS_LIBEVDEV -+class CInputProviderEvDev; -+#endif -+ - namespace Ui - { - class MainWindow; -@@ -113,6 +117,9 @@ - bool m_pauseFocusLost = true; - std::shared_ptr m_qtKeyInputProvider; - std::shared_ptr m_qtMouseInputProvider; -+#ifdef HAS_LIBEVDEV -+ std::shared_ptr m_evdevProvider; -+#endif - LastOpenCommand m_lastOpenCommand; - fs::path m_lastPath; - ---- a/Source/ui_qt/mainwindow.cpp -+++ b/Source/ui_qt/mainwindow.cpp -@@ -185,7 +185,8 @@ - bindingManager.RegisterInputProvider(std::make_shared()); - #endif - #ifdef HAS_LIBEVDEV -- bindingManager.RegisterInputProvider(std::make_shared()); -+ m_evdevProvider = std::make_shared(m_virtualMachine); -+ bindingManager.RegisterInputProvider(m_evdevProvider); - #endif - #ifdef WIN32 - bindingManager.RegisterInputProvider(std::make_shared()); -@@ -912,6 +913,10 @@ - - void MainWindow::outputWindow_mouseMoveEvent(QMouseEvent* ev) - { -+#ifdef HAS_LIBEVDEV -+ if(m_evdevProvider && m_evdevProvider->HasLightGuns()) return; -+#endif -+ - if(m_virtualMachine->HasGunListener()) - { - auto gsHandler = m_virtualMachine->GetGSHandler(); ---- a/Source/ui_qt/CMakeLists.txt -+++ b/Source/ui_qt/CMakeLists.txt -@@ -59,6 +59,11 @@ - find_package(LIBEVDEV) - if(LIBEVDEV_FOUND) - list(APPEND UI_QT_PROJECT_LIBS ${LIBEVDEV_LIBRARY}) -+ find_package(PkgConfig REQUIRED) -+ pkg_check_modules(UDEV libudev) -+ if(UDEV_FOUND) -+ list(APPEND UI_QT_PROJECT_LIBS ${UDEV_LIBRARIES}) -+ endif() - list(APPEND UI_QT_DEFINITIONS_LIST HAS_LIBEVDEV=1) - endif() - -@@ -369,6 +374,9 @@ - - if(LIBEVDEV_FOUND) - target_include_directories(Play PRIVATE ${LIBEVDEV_INCLUDE_DIR}) -+ if(UDEV_FOUND) -+ target_include_directories(Play PRIVATE ${UDEV_INCLUDE_DIRS}) -+ endif() - endif() - - if(TARGET_PLATFORM_WIN32) diff --git a/package/batocera/emulators/play/Config.in b/package/batocera/emulators/play/Config.in deleted file mode 100644 index 196549d0747..00000000000 --- a/package/batocera/emulators/play/Config.in +++ /dev/null @@ -1,18 +0,0 @@ -config BR2_PACKAGE_PLAY - bool "play" - select BR2_PACKAGE_PCRE2 - select BR2_PACKAGE_LIBCURL - select BR2_PACKAGE_LIBCURL_OPENSSL - select BR2_PACKAGE_LIBEVDEV - select BR2_PACKAGE_LIBGLEW if BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY - select BR2_PACKAGE_OPENAL - select BR2_PACKAGE_BATOCERA_QT6 - select BR2_PACKAGE_SQLITE - select BR2_PACKAGE_VULKAN_HEADERS if BR2_PACKAGE_BATOCERA_VULKAN - select BR2_PACKAGE_VULKAN_LOADER if BR2_PACKAGE_BATOCERA_VULKAN - select BR2_PACKAGE_ECM - select BR2_PACKAGE_XORG7 if BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY - help - Play! is a PlayStation2 emulator. - - https://purei.org/ diff --git a/package/batocera/emulators/play/namco2x6.keys b/package/batocera/emulators/play/namco2x6.keys deleted file mode 100644 index bb490d2810d..00000000000 --- a/package/batocera/emulators/play/namco2x6.keys +++ /dev/null @@ -1,124 +0,0 @@ -{ - "actions_player1": [ - { - "trigger": ["joystick1up"], - "type": "key", - "target": [ "KEY_T" ] - }, - { - "trigger": ["joystick1down"], - "type": "key", - "target": [ "KEY_G" ] - }, - { - "trigger": ["joystick1left"], - "type": "key", - "target": [ "KEY_F" ] - }, - { - "trigger": ["joystick1right"], - "type": "key", - "target": [ "KEY_H" ] - }, - { - "trigger": ["joystick2up"], - "type": "key", - "target": [ "KEY_I" ] - }, - { - "trigger": ["joystick2down"], - "type": "key", - "target": [ "KEY_K" ] - }, - { - "trigger": ["joystick2left"], - "type": "key", - "target": [ "KEY_J" ] - }, - { - "trigger": ["joystick2right"], - "type": "key", - "target": [ "KEY_L" ] - }, - { - "trigger": ["up"], - "type": "key", - "target": [ "KEY_UP" ] - }, - { - "trigger": ["down"], - "type": "key", - "target": [ "KEY_DOWN" ] - }, - { - "trigger": ["left"], - "type": "key", - "target": [ "KEY_LEFT" ] - }, - { - "trigger": ["right"], - "type": "key", - "target": [ "KEY_RIGHT" ] - }, - { - "trigger": ["start"], - "type": "key", - "target": [ "KEY_ENTER" ] - }, - { - "trigger": ["select"], - "type": "key", - "target": [ "KEY_BACKSPACE" ] - }, - { - "trigger": ["a"], - "type": "key", - "target": [ "KEY_X" ] - }, - { - "trigger": ["b"], - "type": "key", - "target": [ "KEY_Z" ] - }, - { - "trigger": ["x"], - "type": "key", - "target": [ "KEY_S" ] - }, - { - "trigger": ["y"], - "type": "key", - "target": [ "KEY_A" ] - }, - { - "trigger": ["pageup"], - "type": "key", - "target": [ "KEY_1" ] - }, - { - "trigger": ["pagedown"], - "type": "key", - "target": [ "KEY_8" ] - }, - { - "trigger": ["l2"], - "type": "key", - "target": [ "KEY_2" ] - }, - { - "trigger": ["r2"], - "type": "key", - "target": [ "KEY_9" ] - }, - { - "trigger": ["l3"], - "type": "key", - "target": [ "KEY_3" ] - }, - { - "trigger": ["r3"], - "type": "key", - "target": [ "KEY_0" ] - } - ] -} diff --git a/package/batocera/emulators/play/play.emulator.yml b/package/batocera/emulators/play/play.emulator.yml deleted file mode 100644 index 878979b8253..00000000000 --- a/package/batocera/emulators/play/play.emulator.yml +++ /dev/null @@ -1,79 +0,0 @@ -features: - - padtokeyboard -shared_features: - - powermode - - tdp - - videomode - - bezel - - bezel_stretch - - hud - - hud_corner - - bezel.tattoo - - bezel.tattoo_corner - - bezel.tattoo_file - - bezel.resize_tattoo - - use_wheels - - wheel_rotation - - wheel_deadzone - - wheel_midzone -custom_features: - play_api: - prompt: GRAPHICS API - description: Choose which graphics API library to use. - choices: - OpenGL (Default): "0" - Vulkan: "1" - play_scale: - prompt: OPENGL RENDERING RESOLUTION - description: Enhancement. Increase the rendering resolution. Makes 3D objects clearer. - choices: - 1x (~480): "1" - 2x (~960): "2" - 4x (~1920p): "4" - 8x (~4320p / 8k): "8" - 16x (~7680p / 16k): "16" - play_mode: - prompt: PRESENTATION MODE - choices: - Fill Screen: "0" - Fit Screen: "1" - Original Size: "2" - play_widescreen: - prompt: ENABLE WIDESCREEN - description: Enable widescreen support. - choices: - Disabled: "false" - Enabled: "true" - play_vsync: - prompt: VSYNC - description: Fix screen tearing. - choices: - Disabled: "false" - Enabled: "true" - play_filter: - prompt: SMOOTH GAMES (BILINEAR FILTERING) - description: Only applicable when using OpenGL. - choices: - Disabled: "false" - Enabled: "true" - play_language: - prompt: LANGUAGE - description: Change the game's language. - choices: - Japanese: "0" - English: "1" - French: "2" - Spanish: "3" - German: "4" - Italian: "5" - Dutch: "6" - Portuguese: "7" - Russian: "8" - Korean: "9" - Traditional Chinese: "10" - Simplified Chinese: "11" -systems: - - namco2x6 - - name: ps2 - exclude_extensions: - - m3u diff --git a/package/batocera/emulators/play/play.mk b/package/batocera/emulators/play/play.mk deleted file mode 100644 index 567eb08f6f2..00000000000 --- a/package/batocera/emulators/play/play.mk +++ /dev/null @@ -1,46 +0,0 @@ -################################################################################ -# -# play! -# -################################################################################ - -PLAY_VERSION = 0.77 -PLAY_SITE = https://github.com/jpd002/Play-.git -PLAY_SITE_METHOD = git -PLAY_GIT_SUBMODULES = YES -PLAY_LICENSE = BSD -PLAY_EMULATOR_INFO = play.emulator.yml - -PLAY_DEPENDENCIES = openal qt6base sqlite ecm libevdev eudev - -PLAY_CONF_OPTS += -DCMAKE_BUILD_TYPE=Release -PLAY_CONF_OPTS += -DBUILD_SHARED_LIBS=OFF -PLAY_CONF_OPTS += -DINSTALL_STATIC_LIBS=ON -PLAY_CONF_OPTS += -DBUILD_TESTS=OFF -PLAY_CONF_OPTS += -DENABLE_AMAZON_S3=OFF - -ifeq ($(BR2_PACKAGE_BATOCERA_TARGET_X86_64_ANY),y) - PLAY_DEPENDENCIES += libglew libglu - PLAY_CONF_OPTS += -DOpenGL_GL_PREFERENCE=GLVND -else - PLAY_DEPENDENCIES += qt6wayland - # Force to use GLES on ARM/Wayland - PLAY_CONF_OPTS += -DUSE_GLEW=OFF - PLAY_CONF_OPTS += -DUSE_GLES=ON -endif - -ifeq ($(BR2_PACKAGE_BATOCERA_VULKAN),y) - PLAY_DEPENDENCIES += vulkan-headers vulkan-loader -endif - -define PLAY_EVMAPY - # evmap config - mkdir -p $(TARGET_DIR)/usr/share/evmapy - cp $(BR2_EXTERNAL_BATOCERA_PATH)/package/batocera/emulators/play/*.keys \ - $(TARGET_DIR)/usr/share/evmapy -endef - -PLAY_POST_INSTALL_TARGET_HOOKS += PLAY_EVMAPY - -$(eval $(cmake-package)) -$(eval $(emulator-info-package)) diff --git a/package/batocera/emulators/play/ps2.play.keys b/package/batocera/emulators/play/ps2.play.keys deleted file mode 100644 index bb490d2810d..00000000000 --- a/package/batocera/emulators/play/ps2.play.keys +++ /dev/null @@ -1,124 +0,0 @@ -{ - "actions_player1": [ - { - "trigger": ["joystick1up"], - "type": "key", - "target": [ "KEY_T" ] - }, - { - "trigger": ["joystick1down"], - "type": "key", - "target": [ "KEY_G" ] - }, - { - "trigger": ["joystick1left"], - "type": "key", - "target": [ "KEY_F" ] - }, - { - "trigger": ["joystick1right"], - "type": "key", - "target": [ "KEY_H" ] - }, - { - "trigger": ["joystick2up"], - "type": "key", - "target": [ "KEY_I" ] - }, - { - "trigger": ["joystick2down"], - "type": "key", - "target": [ "KEY_K" ] - }, - { - "trigger": ["joystick2left"], - "type": "key", - "target": [ "KEY_J" ] - }, - { - "trigger": ["joystick2right"], - "type": "key", - "target": [ "KEY_L" ] - }, - { - "trigger": ["up"], - "type": "key", - "target": [ "KEY_UP" ] - }, - { - "trigger": ["down"], - "type": "key", - "target": [ "KEY_DOWN" ] - }, - { - "trigger": ["left"], - "type": "key", - "target": [ "KEY_LEFT" ] - }, - { - "trigger": ["right"], - "type": "key", - "target": [ "KEY_RIGHT" ] - }, - { - "trigger": ["start"], - "type": "key", - "target": [ "KEY_ENTER" ] - }, - { - "trigger": ["select"], - "type": "key", - "target": [ "KEY_BACKSPACE" ] - }, - { - "trigger": ["a"], - "type": "key", - "target": [ "KEY_X" ] - }, - { - "trigger": ["b"], - "type": "key", - "target": [ "KEY_Z" ] - }, - { - "trigger": ["x"], - "type": "key", - "target": [ "KEY_S" ] - }, - { - "trigger": ["y"], - "type": "key", - "target": [ "KEY_A" ] - }, - { - "trigger": ["pageup"], - "type": "key", - "target": [ "KEY_1" ] - }, - { - "trigger": ["pagedown"], - "type": "key", - "target": [ "KEY_8" ] - }, - { - "trigger": ["l2"], - "type": "key", - "target": [ "KEY_2" ] - }, - { - "trigger": ["r2"], - "type": "key", - "target": [ "KEY_9" ] - }, - { - "trigger": ["l3"], - "type": "key", - "target": [ "KEY_3" ] - }, - { - "trigger": ["r3"], - "type": "key", - "target": [ "KEY_0" ] - } - ] -}