From b6d2748ed7b22be7791060c3b9d901dca19d8848 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 26 Nov 2025 20:44:20 +0200 Subject: [PATCH 01/18] initial --- CMakeLists.txt | 4 + src/common/config.cpp | 25 -- src/common/config.h | 4 - src/common/path_util.cpp | 1 + src/common/path_util.h | 2 + src/core/emulator_settings.cpp | 321 +++++++++++++++++++++++++ src/core/emulator_settings.h | 328 ++++++++++++++++++++++++++ src/core/libraries/kernel/process.cpp | 7 +- src/core/user_manager.cpp | 112 +++++++++ src/core/user_manager.h | 44 ++++ src/emulator.cpp | 5 +- src/main.cpp | 3 + 12 files changed, 822 insertions(+), 34 deletions(-) create mode 100644 src/core/emulator_settings.cpp create mode 100644 src/core/emulator_settings.h create mode 100644 src/core/user_manager.cpp create mode 100644 src/core/user_manager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c1ebca79..efa5016b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -833,6 +833,10 @@ set(CORE src/core/aerolib/stubs.cpp src/core/thread.h src/core/tls.cpp src/core/tls.h + src/core/emulator_settings.cpp + src/core/emulator_settings.h + src/core/user_manager.cpp + src/core/user_manager.h ) if (ARCHITECTURE STREQUAL "x86_64") diff --git a/src/common/config.cpp b/src/common/config.cpp index b0f068142..c69606996 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -130,7 +130,6 @@ public: // General static ConfigEntry volumeSlider(100); -static ConfigEntry isNeo(false); static ConfigEntry isDevKit(false); static ConfigEntry extraDmemInMbytes(0); static ConfigEntry isPSNSignedIn(false); @@ -195,7 +194,6 @@ static ConfigEntry rdocEnable(false); // Debug static ConfigEntry isDebugDump(false); static ConfigEntry isShaderDebug(false); -static ConfigEntry isSeparateLogFilesEnabled(false); static ConfigEntry isFpsColor(true); static ConfigEntry logEnabled(true); @@ -298,10 +296,6 @@ void setVolumeSlider(int volumeValue, bool is_game_specific) { volumeSlider.set(volumeValue, is_game_specific); } -bool isNeoModeConsole() { - return isNeo.get(); -} - bool isDevKitConsole() { return isDevKit.get(); } @@ -655,10 +649,6 @@ void setLanguage(u32 language, bool is_game_specific) { m_language.set(language, is_game_specific); } -void setNeoMode(bool enable, bool is_game_specific) { - isNeo.set(enable, is_game_specific); -} - void setDevKitConsole(bool enable, bool is_game_specific) { isDevKit.set(enable, is_game_specific); } @@ -671,10 +661,6 @@ void setLogFilter(const string& type, bool is_game_specific) { logFilter.set(type, is_game_specific); } -void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific) { - isSeparateLogFilesEnabled.set(enabled, is_game_specific); -} - void setUserName(const string& name, bool is_game_specific) { userName.set(name, is_game_specific); } @@ -768,10 +754,6 @@ u32 GetLanguage() { return m_language.get(); } -bool getSeparateLogFilesEnabled() { - return isSeparateLogFilesEnabled.get(); -} - bool getPSNSignedIn() { return isPSNSignedIn.get(); } @@ -861,7 +843,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { const toml::value& general = data.at("General"); volumeSlider.setFromToml(general, "volumeSlider", is_game_specific); - isNeo.setFromToml(general, "isPS4Pro", is_game_specific); isDevKit.setFromToml(general, "isDevKit", is_game_specific); if (is_game_specific) { // do not get this value from the base config extraDmemInMbytes.setFromToml(general, "extraDmemInMbytes", is_game_specific); @@ -946,7 +927,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { const toml::value& debug = data.at("Debug"); isDebugDump.setFromToml(debug, "DebugDump", is_game_specific); - isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific); isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific); isFpsColor.setFromToml(debug, "FPSColor", is_game_specific); logEnabled.setFromToml(debug, "logEnabled", is_game_specific); @@ -1061,7 +1041,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { userName.setTomlValue(data, "General", "userName", is_game_specific); isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific); isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific); - isNeo.setTomlValue(data, "General", "isPS4Pro", is_game_specific); isDevKit.setTomlValue(data, "General", "isDevKit", is_game_specific); if (is_game_specific) { extraDmemInMbytes.setTomlValue(data, "General", "extraDmemInMbytes", is_game_specific); @@ -1110,8 +1089,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific); isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific); - isSeparateLogFilesEnabled.setTomlValue(data, "Debug", "isSeparateLogFilesEnabled", - is_game_specific); logEnabled.setTomlValue(data, "Debug", "logEnabled", is_game_specific); m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific); @@ -1183,7 +1160,6 @@ void setDefaultValues(bool is_game_specific) { if (is_game_specific) { readbacksEnabled.set(false, is_game_specific); readbackLinearImagesEnabled.set(false, is_game_specific); - isNeo.set(false, is_game_specific); isDevKit.set(false, is_game_specific); isPSNSignedIn.set(false, is_game_specific); isConnectedToNetwork.set(false, is_game_specific); @@ -1241,7 +1217,6 @@ void setDefaultValues(bool is_game_specific) { // GS - Debug isDebugDump.set(false, is_game_specific); isShaderDebug.set(false, is_game_specific); - isSeparateLogFilesEnabled.set(false, is_game_specific); logEnabled.set(true, is_game_specific); // GS - Settings diff --git a/src/common/config.h b/src/common/config.h index 5c9f89ae6..5ec66682a 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -110,8 +110,6 @@ void setPadSpkOutputDevice(std::string device, bool is_game_specific = false); std::string getMicDevice(); void setCursorHideTimeout(int newcursorHideTimeout, bool is_game_specific = false); void setMicDevice(std::string device, bool is_game_specific = false); -void setSeparateLogFilesEnabled(bool enabled, bool is_game_specific = false); -bool getSeparateLogFilesEnabled(); u32 GetLanguage(); void setLanguage(u32 language, bool is_game_specific = false); void setUseSpecialPad(bool use); @@ -122,8 +120,6 @@ bool getPSNSignedIn(); void setPSNSignedIn(bool sign, bool is_game_specific = false); bool patchShaders(); // no set bool fpsColor(); // no set -bool isNeoModeConsole(); -void setNeoMode(bool enable, bool is_game_specific = false); bool isDevKitConsole(); void setDevKitConsole(bool enable, bool is_game_specific = false); diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index bd0aff040..8ec1b38b0 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -127,6 +127,7 @@ static auto UserPaths = [] { create_path(PathType::MetaDataDir, user_dir / METADATA_DIR); create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY); create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS); + create_path(PathType::HomeDir, user_dir / HOME_DIR); std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt"); if (notice_file.is_open()) { diff --git a/src/common/path_util.h b/src/common/path_util.h index 0a0234eba..dff44866b 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -24,6 +24,7 @@ enum class PathType { MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored. CustomTrophy, // Where custom files for trophies are stored. CustomConfigs, // Where custom files for different games are stored. + HomeDir, // PS4 home directory }; constexpr auto PORTABLE_DIR = "user"; @@ -42,6 +43,7 @@ constexpr auto PATCHES_DIR = "patches"; constexpr auto METADATA_DIR = "game_data"; constexpr auto CUSTOM_TROPHY = "custom_trophy"; constexpr auto CUSTOM_CONFIGS = "custom_configs"; +constexpr auto HOME_DIR = "home"; // Filenames constexpr auto LOG_FILE = "shad_log.txt"; diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp new file mode 100644 index 000000000..3734eeff6 --- /dev/null +++ b/src/core/emulator_settings.cpp @@ -0,0 +1,321 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include "emulator_settings.h" + +using json = nlohmann::json; + +std::shared_ptr EmulatorSettings::s_instance = nullptr; +std::mutex EmulatorSettings::s_mutex; + +namespace nlohmann { +template <> +struct adl_serializer { + static void to_json(json& j, const std::filesystem::path& p) { + j = p.u8string(); + } + static void from_json(const json& j, std::filesystem::path& p) { + p = j.get(); + } +}; +} // namespace nlohmann + +// -------------------- +// Print summary +// -------------------- +void EmulatorSettings::PrintChangedSummary(const std::vector& changed) { + if (changed.empty()) { + std::cout << "[Settings] No game-specific overrides applied\n"; + return; + } + std::cout << "[Settings] Game-specific overrides applied:\n"; + for (const auto& k : changed) + std::cout << " * " << k << "\n"; +} + +// -------------------- +// ctor/dtor + singleton +// -------------------- +EmulatorSettings::EmulatorSettings() { + Load(); +} +EmulatorSettings::~EmulatorSettings() { + Save(); +} + +std::shared_ptr EmulatorSettings::GetInstance() { + std::lock_guard lock(s_mutex); + if (!s_instance) + s_instance = std::make_shared(); + return s_instance; +} + +void EmulatorSettings::SetInstance(std::shared_ptr instance) { + std::lock_guard lock(s_mutex); + s_instance = instance; +} + +// -------------------- +// General helpers +// -------------------- +bool EmulatorSettings::AddGameInstallDir(const std::filesystem::path& dir, bool enabled) { + for (const auto& d : m_general.install_dirs.value) + if (d.path == dir) + return false; + m_general.install_dirs.value.push_back({dir, enabled}); + return true; +} + +std::vector EmulatorSettings::GetGameInstallDirs() const { + std::vector out; + for (const auto& d : m_general.install_dirs.value) + if (d.enabled) + out.push_back(d.path); + return out; +} + +void EmulatorSettings::SetAllGameInstallDirs(const std::vector& dirs) { + m_general.install_dirs.value = dirs; +} + +std::filesystem::path EmulatorSettings::GetHomeDir() { + if (m_general.home_dir.value.empty()) { + return Common::FS::GetUserPath(Common::FS::PathType::HomeDir); + } + return m_general.home_dir.value; +} + +void EmulatorSettings::SetHomeDir(const std::filesystem::path& dir) { + m_general.home_dir.value = dir; +} + +std::filesystem::path EmulatorSettings::GetSysModulesDir() { + if (m_general.sys_modules_dir.value.empty()) { + return Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir); + } + return m_general.sys_modules_dir.value; +} + +void EmulatorSettings::SetSysModulesDir(const std::filesystem::path& dir) { + m_general.sys_modules_dir.value = dir; +} + +// -------------------- +// Save +// -------------------- +bool EmulatorSettings::Save(const std::string& serial) const { + try { + if (!serial.empty()) { + const std::filesystem::path cfgDir = + Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs); + std::filesystem::create_directories(cfgDir); + const std::filesystem::path path = cfgDir / (serial + ".json"); + + json j = json::object(); + + // Only write overrideable fields for each group + json generalObj = json::object(); + for (auto& item : m_general.GetOverrideableFields()) { + json whole = m_general; + if (whole.contains(item.key)) + generalObj[item.key] = whole[item.key]; + } + j["General"] = generalObj; + + // Debug + json debugObj = json::object(); + for (auto& item : m_debug.GetOverrideableFields()) { + json whole = m_debug; + if (whole.contains(item.key)) + debugObj[item.key] = whole[item.key]; + } + j["Debug"] = debugObj; + + // Input + json inputObj = json::object(); + for (auto& item : m_input.GetOverrideableFields()) { + json whole = m_input; + if (whole.contains(item.key)) + inputObj[item.key] = whole[item.key]; + } + j["Input"] = inputObj; + + // Audio + json audioObj = json::object(); + for (auto& item : m_audio.GetOverrideableFields()) { + json whole = m_audio; + if (whole.contains(item.key)) + audioObj[item.key] = whole[item.key]; + } + j["Audio"] = audioObj; + + // GPU + json gpuObj = json::object(); + for (auto& item : m_gpu.GetOverrideableFields()) { + json whole = m_gpu; + if (whole.contains(item.key)) + gpuObj[item.key] = whole[item.key]; + } + j["GPU"] = gpuObj; + + // Vulkan + json vulkanObj = json::object(); + for (auto& item : m_vulkan.GetOverrideableFields()) { + json whole = m_vulkan; + if (whole.contains(item.key)) + vulkanObj[item.key] = whole[item.key]; + } + j["Vulkan"] = vulkanObj; + + std::ofstream out(path); + if (!out.is_open()) { + std::cerr << "Failed to open file for writing: " << path << std::endl; + return false; + } + out << std::setw(4) << j; + out.flush(); + if (out.fail()) { + std::cerr << "Failed to write settings to: " << path << std::endl; + return false; + } + return true; + } else { + const std::filesystem::path path = + Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.json"; + json j; + j["General"] = m_general; + j["Debug"] = m_debug; + j["Input"] = m_input; + j["Audio"] = m_audio; + j["GPU"] = m_gpu; + j["Vulkan"] = m_vulkan; + j["Users"] = m_userManager.GetUsers(); + + std::ofstream out(path); + if (!out.is_open()) { + std::cerr << "Failed to open file for writing: " << path << std::endl; + return false; + } + out << std::setw(4) << j; + out.flush(); + if (out.fail()) { + std::cerr << "Failed to write settings to: " << path << std::endl; + return false; + } + return true; + } + } catch (const std::exception& e) { + std::cerr << "Error saving settings: " << e.what() << std::endl; + return false; + } +} + +// -------------------- +// Load +// -------------------- +bool EmulatorSettings::Load(const std::string& serial) { + try { + const std::filesystem::path userDir = + Common::FS::GetUserPath(Common::FS::PathType::UserDir); + const std::filesystem::path configPath = userDir / "config.json"; + + // Load global config if exists + if (std::ifstream globalIn{configPath}; globalIn.good()) { + json gj; + globalIn >> gj; + if (gj.contains("General")) { + json current = m_general; // JSON from existing struct with all defaults + current.update(gj.at("General")); // merge only fields present in file + m_general = current.get(); // convert back + } + if (gj.contains("Debug")) { + json current = m_debug; + current.update(gj.at("Debug")); + m_debug = current.get(); + } + if (gj.contains("Input")) { + json current = m_input; + current.update(gj.at("Input")); + m_input = current.get(); + } + if (gj.contains("Audio")) { + json current = m_audio; + current.update(gj.at("Audio")); + m_audio = current.get(); + } + if (gj.contains("GPU")) { + json current = m_gpu; + current.update(gj.at("GPU")); + m_gpu = current.get(); + } + if (gj.contains("Vulkan")) { + json current = m_vulkan; + current.update(gj.at("Vulkan")); + m_vulkan = current.get(); + } + if (gj.contains("Users")) + m_userManager.GetUsers() = gj.at("Users").get(); + } else { + // ensure a default user exists + if (m_userManager.GetUsers().user.empty()) + m_userManager.GetUsers().user = m_userManager.CreateDefaultUser(); + } + + // Load per-game overrides and apply + if (!serial.empty()) { + const std::filesystem::path gamePath = + Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (serial + ".json"); + if (!std::filesystem::exists(gamePath)) + return false; + + std::ifstream in(gamePath); + if (!in.is_open()) + return false; + + json gj; + in >> gj; + + std::vector changed; + + if (gj.contains("General")) { + ApplyGroupOverrides(m_general, gj.at("General"), changed); + } + if (gj.contains("Debug")) { + ApplyGroupOverrides(m_debug, gj.at("Debug"), changed); + } + if (gj.contains("Input")) { + ApplyGroupOverrides(m_input, gj.at("Input"), changed); + } + if (gj.contains("Audio")) { + ApplyGroupOverrides(m_audio, gj.at("Audio"), changed); + } + if (gj.contains("GPU")) { + ApplyGroupOverrides(m_gpu, gj.at("GPU"), changed); + } + if (gj.contains("Vulkan")) { + ApplyGroupOverrides(m_vulkan, gj.at("Vulkan"), changed); + } + + PrintChangedSummary(changed); + return true; + } + + return true; + } catch (const std::exception& e) { + std::cerr << "Error loading settings: " << e.what() << std::endl; + return false; + } +} + +void EmulatorSettings::setDefaultValues() { + m_general = GeneralSettings{}; + m_debug = DebugSettings{}; + m_input = InputSettings{}; + m_audio = AudioSettings{}; + m_gpu = GPUSettings{}; + m_vulkan = VulkanSettings{}; +} diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h new file mode 100644 index 000000000..158409939 --- /dev/null +++ b/src/core/emulator_settings.h @@ -0,0 +1,328 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "common/types.h" +#include "core/user_manager.h" + +// ------------------------------- +// Generic Setting wrapper +// ------------------------------- +template +struct Setting { + T value{}; +}; + +template +void to_json(nlohmann::json& j, const Setting& s) { + j = s.value; +} + +template +void from_json(const nlohmann::json& j, Setting& s) { + s.value = j.get(); +} + +// ------------------------------- +// Helper to describe a per-field override action +// ------------------------------- +struct OverrideItem { + const char* key; + // apply(basePtrToStruct, jsonEntry, changedFields) + std::function&)> apply; +}; + +// Helper factory: create an OverrideItem binding a pointer-to-member +template +inline OverrideItem make_override(const char* key, Setting Struct::* member) { + return OverrideItem{key, [member, key](void* base, const nlohmann::json& entry, + std::vector& changed) { + if (!entry.is_object()) + return; + + Struct* obj = reinterpret_cast(base); + Setting& dst = obj->*member; + + Setting tmp = entry.get>(); + + if (dst.value != tmp.value) { + changed.push_back(std::string(key) + " ( " + + nlohmann::json(dst.value).dump() + " → " + + nlohmann::json(tmp.value).dump() + " )"); + } + + dst.value = tmp.value; + }}; +} + +// ------------------------------- +// Support types +// ------------------------------- +struct GameInstallDir { + std::filesystem::path path; + bool enabled; +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GameInstallDir, path, enabled) + +// ------------------------------- +// General settings +// ------------------------------- +struct GeneralSettings { + Setting> install_dirs; + Setting addon_install_dir; + Setting home_dir; + Setting sys_modules_dir; + + Setting volume_slider{100}; + Setting neo_mode{false}; + Setting dev_kit_mode{false}; + Setting extra_dmem_in_mbytes{0}; + Setting psn_signed_in{false}; + Setting trophy_popup_disabled{false}; + Setting trophy_notification_duration{6.0}; + Setting log_filter{""}; + Setting log_type{"sync"}; + Setting show_splash{false}; + Setting side_trophy{"right"}; + Setting connected_to_network{false}; + Setting discord_rpc_enabled{false}; + + // return a vector of override descriptors (runtime, but tiny) + std::vector GetOverrideableFields() const { + return std::vector{ + make_override("volume_slider", &GeneralSettings::volume_slider), + make_override("neo_mode", &GeneralSettings::neo_mode), + make_override("dev_kit_mode", &GeneralSettings::dev_kit_mode), + make_override("extra_dmem_in_mbytes", + &GeneralSettings::extra_dmem_in_mbytes), + make_override("psn_signed_in", &GeneralSettings::psn_signed_in), + make_override("trophy_popup_disabled", + &GeneralSettings::trophy_popup_disabled), + make_override("trophy_notification_duration", + &GeneralSettings::trophy_notification_duration), + make_override("log_filter", &GeneralSettings::log_filter), + make_override("log_type", &GeneralSettings::log_type), + make_override("show_splash", &GeneralSettings::show_splash), + make_override("side_trophy", &GeneralSettings::side_trophy), + make_override("connected_to_network", + &GeneralSettings::connected_to_network)}; + } +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_dir, home_dir, + sys_modules_dir, volume_slider, neo_mode, dev_kit_mode, + extra_dmem_in_mbytes, psn_signed_in, trophy_popup_disabled, + trophy_notification_duration, log_filter, log_type, show_splash, + side_trophy, connected_to_network, discord_rpc_enabled) + +// ------------------------------- +// Debug settings +// ------------------------------- +struct DebugSettings { + Setting separate_logging_enabled{false}; // specific + Setting debug_dump{false}; // specific + Setting shader_debug{false}; // specific + Setting fps_color{true}; + Setting log_enabled{true}; // specific + + std::vector GetOverrideableFields() const { + return std::vector{ + make_override("debug_dump", &DebugSettings::debug_dump), + make_override("shader_debug", &DebugSettings::shader_debug), + make_override("separate_logging_enabled", + &DebugSettings::separate_logging_enabled), + make_override("log_enabled", &DebugSettings::log_enabled)}; + } +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, separate_logging_enabled, debug_dump, + shader_debug, fps_color, log_enabled) + +// ------------------------------- +// Input settings +// ------------------------------- +enum HideCursorState : int { Never, Idle, Always }; + +struct InputSettings { + Setting cursor_state{HideCursorState::Idle}; // specific + Setting cursor_hide_timeout{5}; // specific + Setting use_special_pad{false}; + Setting special_pad_class{1}; + Setting motion_controls_enabled{true}; // specific + Setting use_unified_Input_Config{true}; + Setting default_controller_id{""}; + Setting background_controller_input{false}; // specific + + std::vector GetOverrideableFields() const { + return std::vector{ + make_override("cursor_state", &InputSettings::cursor_state), + make_override("cursor_hide_timeout", + &InputSettings::cursor_hide_timeout), + make_override("motion_controls_enabled", + &InputSettings::motion_controls_enabled), + make_override("background_controller_input", + &InputSettings::background_controller_input)}; + } +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(InputSettings, cursor_state, cursor_hide_timeout, + use_special_pad, special_pad_class, motion_controls_enabled, + use_unified_Input_Config, default_controller_id, + background_controller_input) +// ------------------------------- +// Audio settings +// ------------------------------- +struct AudioSettings { + Setting mic_device{"Default Device"}; + Setting main_output_device{"Default Device"}; + Setting padSpk_output_device{"Default Device"}; + + // TODO add overrides + std::vector GetOverrideableFields() const { + return std::vector{}; + } +}; + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AudioSettings, mic_device, main_output_device, + padSpk_output_device) + +// ------------------------------- +// GPU settings +// ------------------------------- +struct GPUSettings { + Setting window_width{1280}; + Setting window_height{720}; + Setting internal_screen_width{1280}; + Setting internal_screen_height{720}; + Setting null_gpu{false}; + Setting should_copy_gpu_buffers{false}; + Setting readbacks_enabled{false}; + Setting readback_linear_images_enabled{false}; + Setting direct_memory_access_enabled{false}; + Setting should_dump_shaders{false}; + Setting should_patch_shaders{false}; + Setting vblank_frequency{60}; + Setting full_screen{false}; + Setting full_screen_mode{"Windowed"}; + Setting present_mode{"Mailbox"}; + Setting hdr_allowed{false}; + Setting fsr_enabled{false}; + Setting rcas_enabled{true}; + Setting rcas_attenuation{250}; + // TODO add overrides + std::vector GetOverrideableFields() const { + return std::vector{}; + } +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GPUSettings, window_width, window_height, internal_screen_width, + internal_screen_height, null_gpu, should_copy_gpu_buffers, + readbacks_enabled, readback_linear_images_enabled, + direct_memory_access_enabled, should_dump_shaders, + should_patch_shaders, vblank_frequency, full_screen, + full_screen_mode, present_mode, hdr_allowed, fsr_enabled, + rcas_enabled, rcas_attenuation) +// ------------------------------- +// Vulkan settings +// ------------------------------- +struct VulkanSettings { + Setting gpu_id{-1}; + // TODO + std::vector GetOverrideableFields() const { + return std::vector{}; + } +}; +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings, gpu_id) +// ------------------------------- +// User settings +// ------------------------------- +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(User, user_id, user_color, user_name, controller_port) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Users, default_user_id, user) + +// ------------------------------- +// Main manager +// ------------------------------- +class EmulatorSettings { +public: + EmulatorSettings(); + ~EmulatorSettings(); + + static std::shared_ptr GetInstance(); + static void SetInstance(std::shared_ptr instance); + + bool Save(const std::string& serial = "") const; + bool Load(const std::string& serial = ""); + void setDefaultValues(); + + // general accessors + bool AddGameInstallDir(const std::filesystem::path& dir, bool enabled = true); + std::vector GetGameInstallDirs() const; + void SetAllGameInstallDirs(const std::vector& dirs); + std::filesystem::path GetHomeDir(); + void SetHomeDir(const std::filesystem::path& dir); + std::filesystem::path GetSysModulesDir(); + void SetSysModulesDir(const std::filesystem::path& dir); + + // user helpers + UserManager& GetUserManager() { + return m_userManager; + } + const UserManager& GetUserManager() const { + return m_userManager; + } + +private: + GeneralSettings m_general{}; + DebugSettings m_debug{}; + InputSettings m_input{}; + AudioSettings m_audio{}; + GPUSettings m_gpu{}; + VulkanSettings m_vulkan{}; + UserManager m_userManager; + + static std::shared_ptr s_instance; + static std::mutex s_mutex; + + // Generic helper that applies override descriptors for a specific group + template + void ApplyGroupOverrides(Group& group, const nlohmann::json& groupJson, + std::vector& changed) { + for (auto& item : group.GetOverrideableFields()) { + if (!groupJson.contains(item.key)) + continue; + item.apply(&group, groupJson.at(item.key), changed); + } + } + + static void PrintChangedSummary(const std::vector& changed); + +public: +#define SETTING_FORWARD(group, Name, field) \ + auto Get##Name() const { \ + return group.field.value; \ + } \ + void Set##Name(const decltype(group.field.value)& v) { \ + group.field.value = v; \ + } +#define SETTING_FORWARD_BOOL(group, Name, field) \ + auto Is##Name() const { \ + return group.field.value; \ + } \ + void Set##Name(const decltype(group.field.value)& v) { \ + group.field.value = v; \ + } + // General settings + SETTING_FORWARD(m_general, VolumeSlider, volume_slider) + SETTING_FORWARD_BOOL(m_general, Neo, neo_mode) + SETTING_FORWARD(m_general, AddonInstallDir, addon_install_dir) + + // Debug settings + SETTING_FORWARD_BOOL(m_debug, SeparateLoggingEnabled, separate_logging_enabled) + +#undef SETTING_FORWARD +#undef SETTING_FORWARD_BOOL +}; diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index 02da041c3..91598d943 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -4,6 +4,7 @@ #include "common/config.h" #include "common/elf_info.h" #include "common/logging/log.h" +#include "core/emulator_settings.h" #include "core/file_sys/fs.h" #include "core/libraries/kernel/orbis_error.h" #include "core/libraries/kernel/process.h" @@ -17,19 +18,19 @@ s32 PS4_SYSV_ABI sceKernelIsInSandbox() { } s32 PS4_SYSV_ABI sceKernelIsNeoMode() { - return Config::isNeoModeConsole() && + return EmulatorSettings::GetInstance()->IsNeo() && Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode; } s32 PS4_SYSV_ABI sceKernelHasNeoMode() { - return Config::isNeoModeConsole(); + return EmulatorSettings::GetInstance()->IsNeo(); } s32 PS4_SYSV_ABI sceKernelGetMainSocId() { // These hardcoded values are based on hardware observations. // Different models of PS4/PS4 Pro likely return slightly different values. LOG_DEBUG(Lib_Kernel, "called"); - if (Config::isNeoModeConsole()) { + if (EmulatorSettings::GetInstance()->IsNeo()) { return 0x740f30; } return 0x710f10; diff --git a/src/core/user_manager.cpp b/src/core/user_manager.cpp new file mode 100644 index 000000000..b285787d8 --- /dev/null +++ b/src/core/user_manager.cpp @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include "emulator_settings.h" +#include "user_manager.h" + +bool UserManager::AddUser(const User& user) { + for (const auto& u : m_users.user) { + if (u.user_id == user.user_id) + return false; // already exists + } + + m_users.user.push_back(user); + + // Create user home directory and subfolders + const auto user_dir = + EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user.user_id); + + std::error_code ec; + if (!std::filesystem::exists(user_dir)) { + std::filesystem::create_directory(user_dir, ec); + std::filesystem::create_directory(user_dir / "savedata", ec); + std::filesystem::create_directory(user_dir / "trophy", ec); + } + + return true; +} + +bool UserManager::RemoveUser(s32 user_id) { + auto it = std::remove_if(m_users.user.begin(), m_users.user.end(), + [user_id](const User& u) { return u.user_id == user_id; }); + if (it == m_users.user.end()) + return false; // not found + + const auto user_dir = EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user_id); + + if (std::filesystem::exists(user_dir)) { + std::error_code ec; + std::filesystem::remove_all(user_dir, ec); + } + + m_users.user.erase(it, m_users.user.end()); + return true; +} + +bool UserManager::RenameUser(s32 user_id, const std::string& new_name) { + // Find user in the internal list + for (auto& user : m_users.user) { + if (user.user_id == user_id) { + if (user.user_name == new_name) + return true; // no change + + user.user_name = new_name; + return true; + } + } + return false; +} + +User* UserManager::GetUserByID(s32 user_id) { + for (auto& u : m_users.user) { + if (u.user_id == user_id) + return &u; + } + return nullptr; +} + +const std::vector& UserManager::GetAllUsers() const { + return m_users.user; +} + +std::vector UserManager::CreateDefaultUser() { + User default_user; + default_user.user_id = 1; + default_user.user_color = 0; // BLUE + default_user.user_name = "shadPS4"; + default_user.controller_port = 1; + + const auto user_dir = + EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(default_user.user_id); + + if (!std::filesystem::exists(user_dir)) { + std::filesystem::create_directory(user_dir); + std::filesystem::create_directory(user_dir / "savedata"); + std::filesystem::create_directory(user_dir / "trophy"); + } + + return {default_user}; +} + +bool UserManager::SetDefaultUser(u32 user_id) { + auto it = std::find_if(m_users.user.begin(), m_users.user.end(), + [user_id](const User& u) { return u.user_id == user_id; }); + if (it == m_users.user.end()) + return false; + + m_users.default_user_id = user_id; + SetControllerPort(user_id, 1); // Set default user to port 1 + return true; +} + +void UserManager::SetControllerPort(u32 user_id, int port) { + for (auto& u : m_users.user) { + if (u.user_id != user_id && u.controller_port == port) + u.controller_port = -1; + if (u.user_id == user_id) + u.controller_port = port; + } +} \ No newline at end of file diff --git a/src/core/user_manager.h b/src/core/user_manager.h new file mode 100644 index 000000000..a782deb63 --- /dev/null +++ b/src/core/user_manager.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include +#include +#include "common/types.h" + +struct User { + s32 user_id; + u32 user_color; + std::string user_name; + int controller_port; // 1–4 +}; + +struct Users { + int default_user_id = 1; + std::vector user; +}; + +class UserManager { +public: + UserManager() = default; + + bool AddUser(const User& user); + bool RemoveUser(s32 user_id); + bool RenameUser(s32 user_id, const std::string& new_name); + User* GetUserByID(s32 user_id); + const std::vector& GetAllUsers() const; + std::vector CreateDefaultUser(); + bool SetDefaultUser(u32 user_id); + void SetControllerPort(u32 user_id, int port); + + Users& GetUsers() { + return m_users; + } + const Users& GetUsers() const { + return m_users; + } + +private: + Users m_users; +}; \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index fb187cfae..3ebbc4eb1 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -27,6 +27,7 @@ #include "common/singleton.h" #include "core/debugger.h" #include "core/devtools/widget/module_list.h" +#include "core/emulator_settings.h" #include "core/file_format/psf.h" #include "core/file_format/trp.h" #include "core/file_sys/fs.h" @@ -180,7 +181,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, true); // Initialize logging as soon as possible - if (!id.empty() && Config::getSeparateLogFilesEnabled()) { + if (!id.empty() && EmulatorSettings::GetInstance()->IsSeparateLoggingEnabled()) { Common::Log::Initialize(id + ".log"); } else { Common::Log::Initialize(); @@ -203,7 +204,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "Game-specific config exists: {}", has_game_config); LOG_INFO(Config, "General LogType: {}", Config::getLogType()); - LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole()); + LOG_INFO(Config, "General isNeo: {}", EmulatorSettings::GetInstance()->IsNeo()); LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole()); LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork()); LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn()); diff --git a/src/main.cpp b/src/main.cpp index 4d05dfe5a..be4e9fee8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ #ifdef _WIN32 #include #endif +#include int main(int argc, char* argv[]) { #ifdef _WIN32 @@ -28,6 +29,8 @@ int main(int argc, char* argv[]) { IPC::Instance().Init(); // Load configurations + std::shared_ptr emu_settings = std::make_shared(); + EmulatorSettings::SetInstance(emu_settings); const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(user_dir / "config.toml"); From 761306188d48750348c264f709f806aaa26d4264 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 27 Nov 2025 13:01:29 +0200 Subject: [PATCH 02/18] more settings conversion --- src/common/config.cpp | 54 ---------------------------- src/common/config.h | 9 ----- src/core/address_space.cpp | 3 +- src/core/emulator_settings.h | 4 +++ src/core/libraries/np/np_auth.cpp | 3 +- src/core/libraries/np/np_manager.cpp | 3 +- src/core/libraries/np/trophy_ui.cpp | 8 ++--- src/core/memory.cpp | 7 ++-- src/emulator.cpp | 4 +-- 9 files changed, 18 insertions(+), 77 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index c69606996..51beaac32 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -130,10 +130,6 @@ public: // General static ConfigEntry volumeSlider(100); -static ConfigEntry isDevKit(false); -static ConfigEntry extraDmemInMbytes(0); -static ConfigEntry isPSNSignedIn(false); -static ConfigEntry isTrophyPopupDisabled(false); static ConfigEntry trophyNotificationDuration(6.0); static ConfigEntry logFilter(""); static ConfigEntry logType("sync"); @@ -296,20 +292,6 @@ void setVolumeSlider(int volumeValue, bool is_game_specific) { volumeSlider.set(volumeValue, is_game_specific); } -bool isDevKitConsole() { - return isDevKit.get(); -} - -int getExtraDmemInMbytes() { - return extraDmemInMbytes.get(); -} - -void setExtraDmemInMbytes(int value, bool is_game_specific) { - // Disable setting in global config - is_game_specific ? extraDmemInMbytes.game_specific_value = value - : extraDmemInMbytes.base_value = 0; -} - bool getIsFullscreen() { return isFullscreen.get(); } @@ -322,10 +304,6 @@ std::string getPresentMode() { return presentMode.get(); } -bool getisTrophyPopupDisabled() { - return isTrophyPopupDisabled.get(); -} - bool getEnableDiscordRPC() { return enableDiscordRPC; } @@ -613,9 +591,6 @@ void setPresentMode(std::string mode, bool is_game_specific) { presentMode.set(mode, is_game_specific); } -void setisTrophyPopupDisabled(bool disable, bool is_game_specific) { - isTrophyPopupDisabled.set(disable, is_game_specific); -} void setEnableDiscordRPC(bool enable) { enableDiscordRPC = enable; @@ -649,10 +624,6 @@ void setLanguage(u32 language, bool is_game_specific) { m_language.set(language, is_game_specific); } -void setDevKitConsole(bool enable, bool is_game_specific) { - isDevKit.set(enable, is_game_specific); -} - void setLogType(const string& type, bool is_game_specific) { logType.set(type, is_game_specific); } @@ -754,14 +725,6 @@ u32 GetLanguage() { return m_language.get(); } -bool getPSNSignedIn() { - return isPSNSignedIn.get(); -} - -void setPSNSignedIn(bool sign, bool is_game_specific) { - isPSNSignedIn.set(sign, is_game_specific); -} - string getDefaultControllerID() { return defaultControllerID.get(); } @@ -843,12 +806,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { const toml::value& general = data.at("General"); volumeSlider.setFromToml(general, "volumeSlider", is_game_specific); - isDevKit.setFromToml(general, "isDevKit", is_game_specific); - if (is_game_specific) { // do not get this value from the base config - extraDmemInMbytes.setFromToml(general, "extraDmemInMbytes", is_game_specific); - } - isPSNSignedIn.setFromToml(general, "isPSNSignedIn", is_game_specific); - isTrophyPopupDisabled.setFromToml(general, "isTrophyPopupDisabled", is_game_specific); trophyNotificationDuration.setFromToml(general, "trophyNotificationDuration", is_game_specific); enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", enableDiscordRPC); @@ -1030,10 +987,8 @@ void save(const std::filesystem::path& path, bool is_game_specific) { } fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string())); } - // Entries saved by the game-specific settings GUI volumeSlider.setTomlValue(data, "General", "volumeSlider", is_game_specific); - isTrophyPopupDisabled.setTomlValue(data, "General", "isTrophyPopupDisabled", is_game_specific); trophyNotificationDuration.setTomlValue(data, "General", "trophyNotificationDuration", is_game_specific); logFilter.setTomlValue(data, "General", "logFilter", is_game_specific); @@ -1041,11 +996,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { userName.setTomlValue(data, "General", "userName", is_game_specific); isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific); isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific); - isDevKit.setTomlValue(data, "General", "isDevKit", is_game_specific); - if (is_game_specific) { - extraDmemInMbytes.setTomlValue(data, "General", "extraDmemInMbytes", is_game_specific); - } - isPSNSignedIn.setTomlValue(data, "General", "isPSNSignedIn", is_game_specific); isConnectedToNetwork.setTomlValue(data, "General", "isConnectedToNetwork", is_game_specific); cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); @@ -1160,17 +1110,13 @@ void setDefaultValues(bool is_game_specific) { if (is_game_specific) { readbacksEnabled.set(false, is_game_specific); readbackLinearImagesEnabled.set(false, is_game_specific); - isDevKit.set(false, is_game_specific); - isPSNSignedIn.set(false, is_game_specific); isConnectedToNetwork.set(false, is_game_specific); directMemoryAccessEnabled.set(false, is_game_specific); - extraDmemInMbytes.set(0, is_game_specific); } // Entries with game-specific settings that are in both the game-specific and global GUI // GS - General volumeSlider.set(100, is_game_specific); - isTrophyPopupDisabled.set(false, is_game_specific); trophyNotificationDuration.set(6.0, is_game_specific); logFilter.set("", is_game_specific); logType.set("sync", is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index 5ec66682a..b3e861da4 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -73,8 +73,6 @@ bool dumpShaders(); void setDumpShaders(bool enable, bool is_game_specific = false); u32 vblankFreq(); void setVblankFreq(u32 value, bool is_game_specific = false); -bool getisTrophyPopupDisabled(); -void setisTrophyPopupDisabled(bool disable, bool is_game_specific = false); s16 getCursorState(); void setCursorState(s16 cursorState, bool is_game_specific = false); bool vkValidationEnabled(); @@ -116,15 +114,8 @@ void setUseSpecialPad(bool use); bool getUseSpecialPad(); void setSpecialPadClass(int type); int getSpecialPadClass(); -bool getPSNSignedIn(); -void setPSNSignedIn(bool sign, bool is_game_specific = false); bool patchShaders(); // no set bool fpsColor(); // no set -bool isDevKitConsole(); -void setDevKitConsole(bool enable, bool is_game_specific = false); - -int getExtraDmemInMbytes(); -void setExtraDmemInMbytes(int value, bool is_game_specific = false); bool getIsMotionControlsEnabled(); void setIsMotionControlsEnabled(bool use, bool is_game_specific = false); std::string getDefaultControllerID(); diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 3f063ea76..177764c2e 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -9,6 +9,7 @@ #include "common/elf_info.h" #include "common/error.h" #include "core/address_space.h" +#include "core/emulator_settings.h" #include "core/libraries/kernel/memory.h" #include "core/memory.h" #include "libraries/error_codes.h" @@ -183,7 +184,7 @@ struct AddressSpace::Impl { user_size = supported_user_max - USER_MIN - 1; // Increase BackingSize to account for config options. - BackingSize += Config::getExtraDmemInMbytes() * 1_MB; + BackingSize += EmulatorSettings::GetInstance()->GetExtraDmemInMBytes() * 1_MB; // Allocate backing file that represents the total physical memory. backing_handle = CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_ALL_ACCESS, diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index 158409939..8295f9e73 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -318,6 +318,10 @@ public: // General settings SETTING_FORWARD(m_general, VolumeSlider, volume_slider) SETTING_FORWARD_BOOL(m_general, Neo, neo_mode) + SETTING_FORWARD_BOOL(m_general, DevKit, dev_kit_mode) + SETTING_FORWARD(m_general, ExtraDmemInMBytes, extra_dmem_in_mbytes) + SETTING_FORWARD_BOOL(m_general, PSNSignedIn, psn_signed_in) + SETTING_FORWARD_BOOL(m_general, TrophyPopupDisabled, trophy_popup_disabled) SETTING_FORWARD(m_general, AddonInstallDir, addon_install_dir) // Debug settings diff --git a/src/core/libraries/np/np_auth.cpp b/src/core/libraries/np/np_auth.cpp index 0c855546c..ac1d7404d 100644 --- a/src/core/libraries/np/np_auth.cpp +++ b/src/core/libraries/np/np_auth.cpp @@ -4,6 +4,7 @@ #include #include "common/config.h" #include "common/logging/log.h" +#include "core/emulator_settings.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/np/np_auth.h" @@ -361,7 +362,7 @@ s32 PS4_SYSV_ABI sceNpAuthDeleteRequest(s32 req_id) { } void RegisterLib(Core::Loader::SymbolsResolver* sym) { - g_signed_in = Config::getPSNSignedIn(); + g_signed_in = EmulatorSettings::GetInstance()->IsPSNSignedIn(); LIB_FUNCTION("6bwFkosYRQg", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateRequest); LIB_FUNCTION("N+mr7GjTvr8", "libSceNpAuth", 1, "libSceNpAuth", sceNpAuthCreateAsyncRequest); diff --git a/src/core/libraries/np/np_manager.cpp b/src/core/libraries/np/np_manager.cpp index ebc940bf3..65acbef7f 100644 --- a/src/core/libraries/np/np_manager.cpp +++ b/src/core/libraries/np/np_manager.cpp @@ -5,6 +5,7 @@ #include "common/config.h" #include "common/logging/log.h" +#include "core/emulator_settings.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/np/np_error.h" @@ -702,7 +703,7 @@ s32 PS4_SYSV_ABI sceNpRegisterStateCallbackForToolkit(OrbisNpStateCallbackForNpT } void RegisterLib(Core::Loader::SymbolsResolver* sym) { - g_signed_in = Config::getPSNSignedIn(); + g_signed_in = EmulatorSettings::GetInstance()->IsPSNSignedIn(); LIB_FUNCTION("GpLQDNKICac", "libSceNpManager", 1, "libSceNpManager", sceNpCreateRequest); LIB_FUNCTION("eiqMCt9UshI", "libSceNpManager", 1, "libSceNpManager", sceNpCreateAsyncRequest); diff --git a/src/core/libraries/np/trophy_ui.cpp b/src/core/libraries/np/trophy_ui.cpp index b803403c4..ab9d821b8 100644 --- a/src/core/libraries/np/trophy_ui.cpp +++ b/src/core/libraries/np/trophy_ui.cpp @@ -7,15 +7,11 @@ #include #include #include - -#ifdef ENABLE_QT_GUI -#include -#endif - #include "common/assert.h" #include "common/config.h" #include "common/path_util.h" #include "common/singleton.h" +#include "core/emulator_settings.h" #include "core/libraries/np/trophy_ui.h" #include "imgui/imgui_std.h" @@ -284,7 +280,7 @@ void AddTrophyToQueue(const std::filesystem::path& trophyIconPath, const std::st const std::string_view& rarity) { std::lock_guard lock(queueMtx); - if (Config::getisTrophyPopupDisabled()) { + if (EmulatorSettings::GetInstance()->IsTrophyPopupDisabled()) { return; } else if (current_trophy_ui.has_value()) { current_trophy_ui.reset(); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index b9fd7fd7d..fa2b28e51 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/config.h" #include "common/debug.h" +#include "core/emulator_settings.h" #include "core/file_sys/fs.h" #include "core/libraries/kernel/memory.h" #include "core/libraries/kernel/orbis_error.h" @@ -33,11 +34,11 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1 bool use_extended_mem2) { const bool is_neo = ::Libraries::Kernel::sceKernelIsNeoMode(); auto total_size = is_neo ? ORBIS_KERNEL_TOTAL_MEM_PRO : ORBIS_KERNEL_TOTAL_MEM; - if (Config::isDevKitConsole()) { + if (EmulatorSettings::GetInstance()->IsDevKit()) { total_size = is_neo ? ORBIS_KERNEL_TOTAL_MEM_DEV_PRO : ORBIS_KERNEL_TOTAL_MEM_DEV; } - s32 extra_dmem = Config::getExtraDmemInMbytes(); - if (Config::getExtraDmemInMbytes() != 0) { + s32 extra_dmem = EmulatorSettings::GetInstance()->GetExtraDmemInMBytes(); + if (extra_dmem != 0) { LOG_WARNING(Kernel_Vmm, "extraDmemInMbytes is {} MB! Old Direct Size: {:#x} -> New Direct Size: {:#x}", extra_dmem, total_size, total_size + extra_dmem * 1_MB); diff --git a/src/emulator.cpp b/src/emulator.cpp index 3ebbc4eb1..c81f23303 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -205,9 +205,9 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "General LogType: {}", Config::getLogType()); LOG_INFO(Config, "General isNeo: {}", EmulatorSettings::GetInstance()->IsNeo()); - LOG_INFO(Config, "General isDevKit: {}", Config::isDevKitConsole()); + LOG_INFO(Config, "General isDevKit: {}", EmulatorSettings::GetInstance()->IsDevKit()); LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork()); - LOG_INFO(Config, "General isPsnSignedIn: {}", Config::getPSNSignedIn()); + LOG_INFO(Config, "General isPsnSignedIn: {}", EmulatorSettings::GetInstance()->IsPSNSignedIn()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks()); LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages()); From f54367734d45c6431b7104414976d79f497d2065 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 27 Nov 2025 15:25:21 +0200 Subject: [PATCH 03/18] more settings porting --- src/common/config.cpp | 28 ++-------------------------- src/common/config.h | 5 ----- src/core/emulator_settings.h | 10 +++++++--- src/core/libraries/np/trophy_ui.cpp | 4 ++-- 4 files changed, 11 insertions(+), 36 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 51beaac32..9da9b4ae6 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -130,12 +130,10 @@ public: // General static ConfigEntry volumeSlider(100); -static ConfigEntry trophyNotificationDuration(6.0); static ConfigEntry logFilter(""); static ConfigEntry logType("sync"); static ConfigEntry userName("shadPS4"); static ConfigEntry isShowSplash(false); -static ConfigEntry isSideTrophy("right"); static ConfigEntry isConnectedToNetwork(false); static bool enableDiscordRPC = false; static std::filesystem::path sys_modules_path = {}; @@ -328,10 +326,6 @@ std::string getPadSpkOutputDevice() { return padSpkOutputDevice.get(); } -double getTrophyNotificationDuration() { - return trophyNotificationDuration.get(); -} - u32 getWindowWidth() { return windowWidth.get(); } @@ -388,10 +382,6 @@ bool showSplash() { return isShowSplash.get(); } -string sideTrophy() { - return isSideTrophy.get(); -} - bool nullGpu() { return isNullGpu.get(); } @@ -523,10 +513,6 @@ void setShowSplash(bool enable, bool is_game_specific) { isShowSplash.set(enable, is_game_specific); } -void setSideTrophy(string side, bool is_game_specific) { - isSideTrophy.set(side, is_game_specific); -} - void setNullGpu(bool enable, bool is_game_specific) { isNullGpu.set(enable, is_game_specific); } @@ -616,10 +602,6 @@ void setPadSpkOutputDevice(std::string device, bool is_game_specific) { padSpkOutputDevice.set(device, is_game_specific); } -void setTrophyNotificationDuration(double newTrophyNotificationDuration, bool is_game_specific) { - trophyNotificationDuration.set(newTrophyNotificationDuration, is_game_specific); -} - void setLanguage(u32 language, bool is_game_specific) { m_language.set(language, is_game_specific); } @@ -806,14 +788,12 @@ void load(const std::filesystem::path& path, bool is_game_specific) { const toml::value& general = data.at("General"); volumeSlider.setFromToml(general, "volumeSlider", is_game_specific); - trophyNotificationDuration.setFromToml(general, "trophyNotificationDuration", - is_game_specific); enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", enableDiscordRPC); logFilter.setFromToml(general, "logFilter", is_game_specific); logType.setFromToml(general, "logType", is_game_specific); userName.setFromToml(general, "userName", is_game_specific); isShowSplash.setFromToml(general, "showSplash", is_game_specific); - isSideTrophy.setFromToml(general, "sideTrophy", is_game_specific); + isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific); defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific); @@ -989,13 +969,10 @@ void save(const std::filesystem::path& path, bool is_game_specific) { } // Entries saved by the game-specific settings GUI volumeSlider.setTomlValue(data, "General", "volumeSlider", is_game_specific); - trophyNotificationDuration.setTomlValue(data, "General", "trophyNotificationDuration", - is_game_specific); logFilter.setTomlValue(data, "General", "logFilter", is_game_specific); logType.setTomlValue(data, "General", "logType", is_game_specific); userName.setTomlValue(data, "General", "userName", is_game_specific); isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific); - isSideTrophy.setTomlValue(data, "General", "sideTrophy", is_game_specific); isConnectedToNetwork.setTomlValue(data, "General", "isConnectedToNetwork", is_game_specific); cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); @@ -1117,12 +1094,11 @@ void setDefaultValues(bool is_game_specific) { // Entries with game-specific settings that are in both the game-specific and global GUI // GS - General volumeSlider.set(100, is_game_specific); - trophyNotificationDuration.set(6.0, is_game_specific); logFilter.set("", is_game_specific); logType.set("sync", is_game_specific); userName.set("shadPS4", is_game_specific); isShowSplash.set(false, is_game_specific); - isSideTrophy.set("right", is_game_specific); + // GS - Input cursorState.set(HideCursorState::Idle, is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index b3e861da4..6ce56056a 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -57,8 +57,6 @@ bool collectShadersForDebug(); void setCollectShaderForDebug(bool enable, bool is_game_specific = false); bool showSplash(); void setShowSplash(bool enable, bool is_game_specific = false); -std::string sideTrophy(); -void setSideTrophy(std::string side, bool is_game_specific = false); bool nullGpu(); void setNullGpu(bool enable, bool is_game_specific = false); bool copyGPUCmdBuffers(); @@ -97,9 +95,6 @@ std::string getLogType(); void setLogType(const std::string& type, bool is_game_specific = false); std::string getLogFilter(); void setLogFilter(const std::string& type, bool is_game_specific = false); -double getTrophyNotificationDuration(); -void setTrophyNotificationDuration(double newTrophyNotificationDuration, - bool is_game_specific = false); int getCursorHideTimeout(); std::string getMainOutputDevice(); void setMainOutputDevice(std::string device, bool is_game_specific = false); diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index 8295f9e73..2092909df 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -91,7 +91,7 @@ struct GeneralSettings { Setting log_filter{""}; Setting log_type{"sync"}; Setting show_splash{false}; - Setting side_trophy{"right"}; + Setting trophy_notification_side{"right"}; Setting connected_to_network{false}; Setting discord_rpc_enabled{false}; @@ -111,7 +111,8 @@ struct GeneralSettings { make_override("log_filter", &GeneralSettings::log_filter), make_override("log_type", &GeneralSettings::log_type), make_override("show_splash", &GeneralSettings::show_splash), - make_override("side_trophy", &GeneralSettings::side_trophy), + make_override("trophy_notification_side", + &GeneralSettings::trophy_notification_side), make_override("connected_to_network", &GeneralSettings::connected_to_network)}; } @@ -120,7 +121,8 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_ sys_modules_dir, volume_slider, neo_mode, dev_kit_mode, extra_dmem_in_mbytes, psn_signed_in, trophy_popup_disabled, trophy_notification_duration, log_filter, log_type, show_splash, - side_trophy, connected_to_network, discord_rpc_enabled) + trophy_notification_side, connected_to_network, + discord_rpc_enabled) // ------------------------------- // Debug settings @@ -322,6 +324,8 @@ public: SETTING_FORWARD(m_general, ExtraDmemInMBytes, extra_dmem_in_mbytes) SETTING_FORWARD_BOOL(m_general, PSNSignedIn, psn_signed_in) SETTING_FORWARD_BOOL(m_general, TrophyPopupDisabled, trophy_popup_disabled) + SETTING_FORWARD(m_general, TrophyNotificationDuration, trophy_notification_duration) + SETTING_FORWARD(m_general, GetTrophyNotificationSide, trophy_notification_side) SETTING_FORWARD(m_general, AddonInstallDir, addon_install_dir) // Debug settings diff --git a/src/core/libraries/np/trophy_ui.cpp b/src/core/libraries/np/trophy_ui.cpp index ab9d821b8..44153302d 100644 --- a/src/core/libraries/np/trophy_ui.cpp +++ b/src/core/libraries/np/trophy_ui.cpp @@ -32,9 +32,9 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin const std::string_view& rarity) : trophy_name(trophyName), trophy_type(rarity) { - side = Config::sideTrophy(); + side = EmulatorSettings::GetInstance()->GetTrophyNotificationSide(); - trophy_timer = Config::getTrophyNotificationDuration(); + trophy_timer = EmulatorSettings::GetInstance()->GetTrophyNotificationDuration(); if (std::filesystem::exists(trophyIconPath)) { trophy_icon = RefCountedTexture::DecodePngFile(trophyIconPath); From f952d375a493d7032d87f14d9f7c27d433cdb562 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 27 Nov 2025 17:08:55 +0200 Subject: [PATCH 04/18] more settings porting --- src/common/config.cpp | 105 ------------------ src/common/config.h | 15 --- src/core/address_space.cpp | 2 +- src/core/devtools/widget/module_list.h | 3 +- src/core/emulator_settings.cpp | 33 ++++++ src/core/emulator_settings.h | 8 +- .../libraries/app_content/app_content.cpp | 5 +- src/core/libraries/system/systemservice.cpp | 3 +- src/emulator.cpp | 2 +- src/main.cpp | 10 +- 10 files changed, 54 insertions(+), 132 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 9da9b4ae6..abc65e38d 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -133,10 +133,8 @@ static ConfigEntry volumeSlider(100); static ConfigEntry logFilter(""); static ConfigEntry logType("sync"); static ConfigEntry userName("shadPS4"); -static ConfigEntry isShowSplash(false); static ConfigEntry isConnectedToNetwork(false); static bool enableDiscordRPC = false; -static std::filesystem::path sys_modules_path = {}; // Input static ConfigEntry cursorState(HideCursorState::Idle); @@ -194,7 +192,6 @@ static ConfigEntry logEnabled(true); // GUI static std::vector settings_install_dirs = {}; std::vector install_dirs_enabled = {}; -std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path save_data_path = {}; // Settings @@ -223,17 +220,6 @@ void setGameRunning(bool running) { isGameRunning = running; } -std::filesystem::path getSysModulesPath() { - if (sys_modules_path.empty()) { - return Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir); - } - return sys_modules_path; -} - -void setSysModulesPath(const std::filesystem::path& path) { - sys_modules_path = path; -} - int getVolumeSlider() { return volumeSlider.get(); } @@ -378,10 +364,6 @@ bool collectShadersForDebug() { return isShaderDebug.get(); } -bool showSplash() { - return isShowSplash.get(); -} - bool nullGpu() { return isNullGpu.get(); } @@ -509,10 +491,6 @@ void setCollectShaderForDebug(bool enable, bool is_game_specific) { isShaderDebug.set(enable, is_game_specific); } -void setShowSplash(bool enable, bool is_game_specific) { - isShowSplash.set(enable, is_game_specific); -} - void setNullGpu(bool enable, bool is_game_specific) { isNullGpu.set(enable, is_game_specific); } @@ -577,7 +555,6 @@ void setPresentMode(std::string mode, bool is_game_specific) { presentMode.set(mode, is_game_specific); } - void setEnableDiscordRPC(bool enable) { enableDiscordRPC = enable; } @@ -630,79 +607,10 @@ void setIsMotionControlsEnabled(bool use, bool is_game_specific) { isMotionControlsEnabled.set(use, is_game_specific); } -bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) { - for (const auto& install_dir : settings_install_dirs) { - if (install_dir.path == dir) { - return false; - } - } - settings_install_dirs.push_back({dir, enabled}); - return true; -} - -void removeGameInstallDir(const std::filesystem::path& dir) { - auto iterator = - std::find_if(settings_install_dirs.begin(), settings_install_dirs.end(), - [&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; }); - if (iterator != settings_install_dirs.end()) { - settings_install_dirs.erase(iterator); - } -} - -void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled) { - auto iterator = - std::find_if(settings_install_dirs.begin(), settings_install_dirs.end(), - [&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; }); - if (iterator != settings_install_dirs.end()) { - iterator->enabled = enabled; - } -} - -void setAddonInstallDir(const std::filesystem::path& dir) { - settings_addon_install_dir = dir; -} - -void setGameInstallDirs(const std::vector& dirs_config) { - settings_install_dirs.clear(); - for (const auto& dir : dirs_config) { - settings_install_dirs.push_back({dir, true}); - } -} - -void setAllGameInstallDirs(const std::vector& dirs_config) { - settings_install_dirs = dirs_config; -} - void setSaveDataPath(const std::filesystem::path& path) { save_data_path = path; } -const std::vector getGameInstallDirs() { - std::vector enabled_dirs; - for (const auto& dir : settings_install_dirs) { - if (dir.enabled) { - enabled_dirs.push_back(dir.path); - } - } - return enabled_dirs; -} - -const std::vector getGameInstallDirsEnabled() { - std::vector enabled_dirs; - for (const auto& dir : settings_install_dirs) { - enabled_dirs.push_back(dir.enabled); - } - return enabled_dirs; -} - -std::filesystem::path getAddonInstallDir() { - if (settings_addon_install_dir.empty()) { - // Default for users without a config file or a config file from before this option existed - return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "addcont"; - } - return settings_addon_install_dir; -} - u32 GetLanguage() { return m_language.get(); } @@ -792,12 +700,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) { logFilter.setFromToml(general, "logFilter", is_game_specific); logType.setFromToml(general, "logType", is_game_specific); userName.setFromToml(general, "userName", is_game_specific); - isShowSplash.setFromToml(general, "showSplash", is_game_specific); - - isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific); defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific); - sys_modules_path = toml::find_fs_path_or(general, "sysModulesPath", sys_modules_path); } if (data.contains("Input")) { @@ -894,9 +798,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { } save_data_path = toml::find_fs_path_or(gui, "saveDataPath", save_data_path); - - settings_addon_install_dir = - toml::find_fs_path_or(gui, "addonInstallDir", settings_addon_install_dir); } if (data.contains("Settings")) { @@ -972,7 +873,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { logFilter.setTomlValue(data, "General", "logFilter", is_game_specific); logType.setTomlValue(data, "General", "logType", is_game_specific); userName.setTomlValue(data, "General", "userName", is_game_specific); - isShowSplash.setTomlValue(data, "General", "showSplash", is_game_specific); isConnectedToNetwork.setTomlValue(data, "General", "isConnectedToNetwork", is_game_specific); cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); @@ -1052,12 +952,9 @@ void save(const std::filesystem::path& path, bool is_game_specific) { // Non game-specific entries data["General"]["enableDiscordRPC"] = enableDiscordRPC; - data["General"]["sysModulesPath"] = string{fmt::UTF(sys_modules_path.u8string()).data}; data["GUI"]["installDirs"] = install_dirs; data["GUI"]["installDirsEnabled"] = install_dirs_enabled; data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data}; - data["GUI"]["addonInstallDir"] = - string{fmt::UTF(settings_addon_install_dir.u8string()).data}; data["Debug"]["ConfigVersion"] = config_version; data["Keys"]["TrophyKey"] = trophyKey; @@ -1097,8 +994,6 @@ void setDefaultValues(bool is_game_specific) { logFilter.set("", is_game_specific); logType.set("sync", is_game_specific); userName.set("shadPS4", is_game_specific); - isShowSplash.set(false, is_game_specific); - // GS - Input cursorState.set(HideCursorState::Idle, is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index 6ce56056a..f25a7831f 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -55,8 +55,6 @@ bool allowHDR(); void setAllowHDR(bool enable, bool is_game_specific = false); bool collectShadersForDebug(); void setCollectShaderForDebug(bool enable, bool is_game_specific = false); -bool showSplash(); -void setShowSplash(bool enable, bool is_game_specific = false); bool nullGpu(); void setNullGpu(bool enable, bool is_game_specific = false); bool copyGPUCmdBuffers(); @@ -128,8 +126,6 @@ void setRcasAttenuation(int value, bool is_game_specific = false); bool getIsConnectedToNetwork(); void setConnectedToNetwork(bool enable, bool is_game_specific = false); void setUserName(const std::string& name, bool is_game_specific = false); -std::filesystem::path getSysModulesPath(); -void setSysModulesPath(const std::filesystem::path& path); bool getLoadAutoPatches(); void setLoadAutoPatches(bool enable); @@ -146,18 +142,7 @@ bool GetOverrideControllerColor(); void SetOverrideControllerColor(bool enable); int* GetControllerCustomColor(); void SetControllerCustomColor(int r, int b, int g); -void setGameInstallDirs(const std::vector& dirs_config); -void setAllGameInstallDirs(const std::vector& dirs_config); void setSaveDataPath(const std::filesystem::path& path); -// Gui -bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true); -void removeGameInstallDir(const std::filesystem::path& dir); -void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled); -void setAddonInstallDir(const std::filesystem::path& dir); - -const std::vector getGameInstallDirs(); -const std::vector getGameInstallDirsEnabled(); -std::filesystem::path getAddonInstallDir(); void setDefaultValues(bool is_game_specific = false); diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 177764c2e..d3a0d132a 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -506,7 +506,7 @@ enum PosixPageProtection { struct AddressSpace::Impl { Impl() { - BackingSize += Config::getExtraDmemInMbytes() * 1_MB; + BackingSize += EmulatorSettings::GetInstance()->GetExtraDmemInMBytes() * 1_MB; // Allocate virtual address placeholder for our address space. system_managed_size = SystemManagedSize; system_reserved_size = SystemReservedSize; diff --git a/src/core/devtools/widget/module_list.h b/src/core/devtools/widget/module_list.h index 0702ac4db..911618a48 100644 --- a/src/core/devtools/widget/module_list.h +++ b/src/core/devtools/widget/module_list.h @@ -11,6 +11,7 @@ #include "common/config.h" #include "common/elf_info.h" #include "common/path_util.h" +#include "core/emulator_settings.h" namespace Core::Devtools::Widget { @@ -23,7 +24,7 @@ public: bool open = false; static bool IsSystemModule(const std::filesystem::path& path) { - const auto sys_modules_path = Config::getSysModulesPath(); + const auto sys_modules_path = EmulatorSettings::GetInstance()->GetSysModulesDir(); const auto abs_path = std::filesystem::absolute(path).lexically_normal(); const auto abs_sys_path = std::filesystem::absolute(sys_modules_path).lexically_normal(); diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp index 3734eeff6..55009140b 100644 --- a/src/core/emulator_settings.cpp +++ b/src/core/emulator_settings.cpp @@ -82,6 +82,39 @@ void EmulatorSettings::SetAllGameInstallDirs(const std::vector& m_general.install_dirs.value = dirs; } +void EmulatorSettings::RemoveGameInstallDir(const std::filesystem::path& dir) { + auto iterator = + std::find_if(m_general.install_dirs.value.begin(), m_general.install_dirs.value.end(), + [&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; }); + if (iterator != m_general.install_dirs.value.end()) { + m_general.install_dirs.value.erase(iterator); + } +} + +void EmulatorSettings::SetGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled) { + auto iterator = + std::find_if(m_general.install_dirs.value.begin(), m_general.install_dirs.value.end(), + [&dir](const GameInstallDir& install_dir) { return install_dir.path == dir; }); + if (iterator != m_general.install_dirs.value.end()) { + iterator->enabled = enabled; + } +} + +void EmulatorSettings::SetGameInstallDirs(const std::vector& dirs_config) { + m_general.install_dirs.value.clear(); + for (const auto& dir : dirs_config) { + m_general.install_dirs.value.push_back({dir, true}); + } +} + +const std::vector EmulatorSettings::GetGameInstallDirsEnabled() { + std::vector enabled_dirs; + for (const auto& dir : m_general.install_dirs.value) { + enabled_dirs.push_back(dir.enabled); + } + return enabled_dirs; +} + std::filesystem::path EmulatorSettings::GetHomeDir() { if (m_general.home_dir.value.empty()) { return Common::FS::GetUserPath(Common::FS::PathType::HomeDir); diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index 2092909df..f950b5e82 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -264,6 +264,11 @@ public: bool AddGameInstallDir(const std::filesystem::path& dir, bool enabled = true); std::vector GetGameInstallDirs() const; void SetAllGameInstallDirs(const std::vector& dirs); + void RemoveGameInstallDir(const std::filesystem::path& dir); + void SetGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled); + void SetGameInstallDirs(const std::vector& dirs_config); + const std::vector GetGameInstallDirsEnabled(); + std::filesystem::path GetHomeDir(); void SetHomeDir(const std::filesystem::path& dir); std::filesystem::path GetSysModulesDir(); @@ -325,7 +330,8 @@ public: SETTING_FORWARD_BOOL(m_general, PSNSignedIn, psn_signed_in) SETTING_FORWARD_BOOL(m_general, TrophyPopupDisabled, trophy_popup_disabled) SETTING_FORWARD(m_general, TrophyNotificationDuration, trophy_notification_duration) - SETTING_FORWARD(m_general, GetTrophyNotificationSide, trophy_notification_side) + SETTING_FORWARD(m_general, TrophyNotificationSide, trophy_notification_side) + SETTING_FORWARD_BOOL(m_general, ShowSplash, show_splash) SETTING_FORWARD(m_general, AddonInstallDir, addon_install_dir) // Debug settings diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 1523c2703..0d7ee2d92 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -13,6 +13,7 @@ #include "core/libraries/app_content/app_content_error.h" #include "core/libraries/libs.h" #include "core/libraries/system/systemservice.h" +#include "core/emulator_settings.h" namespace Libraries::AppContent { @@ -57,7 +58,7 @@ int PS4_SYSV_ABI sceAppContentAddcontMount(u32 service_label, OrbisAppContentMountPoint* mount_point) { LOG_INFO(Lib_AppContent, "called"); - const auto& addon_path = Config::getAddonInstallDir() / title_id; + const auto& addon_path = EmulatorSettings::GetInstance()->GetAddonInstallDir() / title_id; auto* mnt = Common::Singleton::Instance(); // Determine which loaded additional content this entitlement label is for. @@ -282,7 +283,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar LOG_ERROR(Lib_AppContent, "(DUMMY) called"); auto* param_sfo = Common::Singleton::Instance(); - const auto addons_dir = Config::getAddonInstallDir(); + const auto addons_dir = EmulatorSettings::GetInstance()->GetAddonInstallDir(); if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) { title_id = *value; } else { diff --git a/src/core/libraries/system/systemservice.cpp b/src/core/libraries/system/systemservice.cpp index c02c4b3c3..f02269ff4 100644 --- a/src/core/libraries/system/systemservice.cpp +++ b/src/core/libraries/system/systemservice.cpp @@ -4,6 +4,7 @@ #include "common/config.h" #include "common/logging/log.h" #include "common/singleton.h" +#include "core/emulator_settings.h" #include "core/file_sys/fs.h" #include "core/libraries/libs.h" #include "core/libraries/system/systemservice.h" @@ -17,7 +18,7 @@ std::queue g_event_queue; std::mutex g_event_queue_mutex; bool IsSplashVisible() { - return Config::showSplash() && g_splash_status; + return EmulatorSettings::GetInstance()->IsShowSplash() && g_splash_status; } int PS4_SYSV_ABI sceAppMessagingClearEventFlag() { diff --git a/src/emulator.cpp b/src/emulator.cpp index c81f23303..81284a1db 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -508,7 +508,7 @@ void Emulator::LoadSystemModules(const std::string& game_serial) { {"libSceFreeTypeOt.sprx", nullptr}}); std::vector found_modules; - const auto& sys_module_path = Config::getSysModulesPath(); + const auto& sys_module_path = EmulatorSettings::GetInstance()->GetSysModulesDir(); for (const auto& entry : std::filesystem::directory_iterator(sys_module_path)) { found_modules.push_back(entry.path()); } diff --git a/src/main.cpp b/src/main.cpp index be4e9fee8..cdc2687d9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -134,7 +134,7 @@ int main(int argc, char* argv[]) { exit(1); } - Config::addGameInstallDir(config_path); + EmulatorSettings::GetInstance()->AddGameInstallDir(config_path); Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); std::cout << "Game folder successfully saved.\n"; exit(0); @@ -153,8 +153,8 @@ int main(int argc, char* argv[]) { exit(1); } - Config::setAddonInstallDir(config_path); - Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + EmulatorSettings::GetInstance()->SetAddonInstallDir(config_path); + EmulatorSettings::GetInstance()->Save(); std::cout << "Addon folder successfully saved.\n"; exit(0); }}, @@ -220,7 +220,7 @@ int main(int argc, char* argv[]) { } // If no game directory is set and no command line argument, prompt for it - if (Config::getGameInstallDirs().empty()) { + if (EmulatorSettings::GetInstance()->GetGameInstallDirs().empty()) { std::cerr << "Warning: No game folder set, please set it by calling shadps4" " with the --add-game-folder argument\n"; } @@ -238,7 +238,7 @@ int main(int argc, char* argv[]) { // If not a file, treat it as a game ID and search in install directories recursively bool game_found = false; const int max_depth = 5; - for (const auto& install_dir : Config::getGameInstallDirs()) { + for (const auto& install_dir : EmulatorSettings::GetInstance()->GetGameInstallDirs()) { if (auto found_path = Common::FS::FindGameByID(install_dir, game_path, max_depth)) { eboot_path = *found_path; game_found = true; From 6bb8ad896866c755254356b6b9a07b98b15ad206 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 27 Nov 2025 17:11:21 +0200 Subject: [PATCH 05/18] clang fix (of course) --- src/core/libraries/app_content/app_content.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 0d7ee2d92..00cfa2d1f 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -8,12 +8,12 @@ #include "common/config.h" #include "common/logging/log.h" #include "common/singleton.h" +#include "core/emulator_settings.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "core/libraries/app_content/app_content_error.h" #include "core/libraries/libs.h" #include "core/libraries/system/systemservice.h" -#include "core/emulator_settings.h" namespace Libraries::AppContent { From d39ff10747f23da382d995485f345978c67bdfe2 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 27 Nov 2025 19:26:10 +0200 Subject: [PATCH 06/18] More settings --- src/common/config.cpp | 51 ---------------------- src/common/config.h | 8 ---- src/common/logging/backend.cpp | 5 ++- src/core/emulator_settings.h | 4 ++ src/core/libraries/network/net_ctl_obj.cpp | 11 +++-- src/core/libraries/network/netctl.cpp | 10 +++-- src/emulator.cpp | 7 +-- 7 files changed, 24 insertions(+), 72 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index abc65e38d..b571c8ed4 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -130,11 +130,7 @@ public: // General static ConfigEntry volumeSlider(100); -static ConfigEntry logFilter(""); -static ConfigEntry logType("sync"); static ConfigEntry userName("shadPS4"); -static ConfigEntry isConnectedToNetwork(false); -static bool enableDiscordRPC = false; // Input static ConfigEntry cursorState(HideCursorState::Idle); @@ -288,10 +284,6 @@ std::string getPresentMode() { return presentMode.get(); } -bool getEnableDiscordRPC() { - return enableDiscordRPC; -} - s16 getCursorState() { return cursorState.get(); } @@ -332,14 +324,6 @@ s32 getGpuId() { return gpuId.get(); } -string getLogFilter() { - return logFilter.get(); -} - -string getLogType() { - return logType.get(); -} - string getUserName() { return userName.get(); } @@ -451,14 +435,6 @@ void setVkGuestMarkersEnabled(bool enable, bool is_game_specific) { vkGuestMarkers.set(enable, is_game_specific); } -bool getIsConnectedToNetwork() { - return isConnectedToNetwork.get(); -} - -void setConnectedToNetwork(bool enable, bool is_game_specific) { - isConnectedToNetwork.set(enable, is_game_specific); -} - void setGpuId(s32 selectedGpuId, bool is_game_specific) { gpuId.set(selectedGpuId, is_game_specific); } @@ -555,10 +531,6 @@ void setPresentMode(std::string mode, bool is_game_specific) { presentMode.set(mode, is_game_specific); } -void setEnableDiscordRPC(bool enable) { - enableDiscordRPC = enable; -} - void setCursorState(s16 newCursorState, bool is_game_specific) { cursorState.set(newCursorState, is_game_specific); } @@ -583,14 +555,6 @@ void setLanguage(u32 language, bool is_game_specific) { m_language.set(language, is_game_specific); } -void setLogType(const string& type, bool is_game_specific) { - logType.set(type, is_game_specific); -} - -void setLogFilter(const string& type, bool is_game_specific) { - logFilter.set(type, is_game_specific); -} - void setUserName(const string& name, bool is_game_specific) { userName.set(name, is_game_specific); } @@ -696,11 +660,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { const toml::value& general = data.at("General"); volumeSlider.setFromToml(general, "volumeSlider", is_game_specific); - enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", enableDiscordRPC); - logFilter.setFromToml(general, "logFilter", is_game_specific); - logType.setFromToml(general, "logType", is_game_specific); userName.setFromToml(general, "userName", is_game_specific); - isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific); defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific); } @@ -870,10 +830,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { } // Entries saved by the game-specific settings GUI volumeSlider.setTomlValue(data, "General", "volumeSlider", is_game_specific); - logFilter.setTomlValue(data, "General", "logFilter", is_game_specific); - logType.setTomlValue(data, "General", "logType", is_game_specific); userName.setTomlValue(data, "General", "userName", is_game_specific); - isConnectedToNetwork.setTomlValue(data, "General", "isConnectedToNetwork", is_game_specific); cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific); @@ -951,7 +908,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { } // Non game-specific entries - data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["GUI"]["installDirs"] = install_dirs; data["GUI"]["installDirsEnabled"] = install_dirs_enabled; data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data}; @@ -984,15 +940,12 @@ void setDefaultValues(bool is_game_specific) { if (is_game_specific) { readbacksEnabled.set(false, is_game_specific); readbackLinearImagesEnabled.set(false, is_game_specific); - isConnectedToNetwork.set(false, is_game_specific); directMemoryAccessEnabled.set(false, is_game_specific); } // Entries with game-specific settings that are in both the game-specific and global GUI // GS - General volumeSlider.set(100, is_game_specific); - logFilter.set("", is_game_specific); - logType.set("sync", is_game_specific); userName.set("shadPS4", is_game_specific); // GS - Input @@ -1041,10 +994,6 @@ void setDefaultValues(bool is_game_specific) { // All other entries if (!is_game_specific) { - - // General - enableDiscordRPC = false; - // Input useSpecialPad.base_value = false; specialPadClass.base_value = 1; diff --git a/src/common/config.h b/src/common/config.h index f25a7831f..af5acbee9 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -85,14 +85,8 @@ bool getVkHostMarkersEnabled(); void setVkHostMarkersEnabled(bool enable, bool is_game_specific = false); bool getVkGuestMarkersEnabled(); void setVkGuestMarkersEnabled(bool enable, bool is_game_specific = false); -bool getEnableDiscordRPC(); -void setEnableDiscordRPC(bool enable); bool isRdocEnabled(); void setRdocEnabled(bool enable, bool is_game_specific = false); -std::string getLogType(); -void setLogType(const std::string& type, bool is_game_specific = false); -std::string getLogFilter(); -void setLogFilter(const std::string& type, bool is_game_specific = false); int getCursorHideTimeout(); std::string getMainOutputDevice(); void setMainOutputDevice(std::string device, bool is_game_specific = false); @@ -123,8 +117,6 @@ bool getRcasEnabled(); void setRcasEnabled(bool enable, bool is_game_specific = false); int getRcasAttenuation(); void setRcasAttenuation(int value, bool is_game_specific = false); -bool getIsConnectedToNetwork(); -void setConnectedToNetwork(bool enable, bool is_game_specific = false); void setUserName(const std::string& name, bool is_game_specific = false); bool getLoadAutoPatches(); void setLoadAutoPatches(bool enable); diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 4a85c4cde..808df8599 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -22,6 +22,7 @@ #include "common/path_util.h" #include "common/string_util.h" #include "common/thread.h" +#include "core/emulator_settings.h" namespace Common::Log { @@ -139,7 +140,7 @@ public: const auto& log_dir = GetUserPath(PathType::LogDir); std::filesystem::create_directory(log_dir); Filter filter; - filter.ParseFilterString(Config::getLogFilter()); + filter.ParseFilterString(EmulatorSettings::GetInstance()->GetLogFilter()); const auto& log_file_path = log_file.empty() ? LOG_FILE : log_file; instance = std::unique_ptr( new Impl(log_dir / log_file_path, filter), Deleter); @@ -220,7 +221,7 @@ public: .function = function, .message = std::move(message), }; - if (Config::getLogType() == "async") { + if (EmulatorSettings::GetInstance()->GetLogType() == "async") { message_queue.EmplaceWait(entry); } else { ForEachBackend([&entry](auto& backend) { backend.Write(entry); }); diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index f950b5e82..f2885a044 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -333,6 +333,10 @@ public: SETTING_FORWARD(m_general, TrophyNotificationSide, trophy_notification_side) SETTING_FORWARD_BOOL(m_general, ShowSplash, show_splash) SETTING_FORWARD(m_general, AddonInstallDir, addon_install_dir) + SETTING_FORWARD(m_general, LogFilter, log_filter) + SETTING_FORWARD(m_general, LogType, log_type) + SETTING_FORWARD_BOOL(m_general, ConnectedToNetwork, connected_to_network) + SETTING_FORWARD_BOOL(m_general, DiscorRPCEnabled, discord_rpc_enabled) // Debug settings SETTING_FORWARD_BOOL(m_debug, SeparateLoggingEnabled, separate_logging_enabled) diff --git a/src/core/libraries/network/net_ctl_obj.cpp b/src/core/libraries/network/net_ctl_obj.cpp index a295477b6..f6d94845d 100644 --- a/src/core/libraries/network/net_ctl_obj.cpp +++ b/src/core/libraries/network/net_ctl_obj.cpp @@ -4,6 +4,7 @@ #include #include "common/config.h" #include "common/logging/log.h" +#include "core/emulator_settings.h" #include "core/libraries/network/net_ctl_codes.h" #include "core/libraries/network/net_ctl_obj.h" #include "core/tls.h" @@ -46,8 +47,9 @@ s32 NetCtlInternal::RegisterNpToolkitCallback(OrbisNetCtlCallbackForNpToolkit fu void NetCtlInternal::CheckCallback() { std::scoped_lock lock{m_mutex}; - const auto event = Config::getIsConnectedToNetwork() ? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED - : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; + const auto event = EmulatorSettings::GetInstance()->IsConnectedToNetwork() + ? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED + : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : callbacks) { if (func != nullptr) { Core::ExecuteGuest(func, event, arg); @@ -57,8 +59,9 @@ void NetCtlInternal::CheckCallback() { void NetCtlInternal::CheckNpToolkitCallback() { std::scoped_lock lock{m_mutex}; - const auto event = Config::getIsConnectedToNetwork() ? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED - : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; + const auto event = EmulatorSettings::GetInstance()->IsConnectedToNetwork() + ? ORBIS_NET_CTL_EVENT_TYPE_IPOBTAINED + : ORBIS_NET_CTL_EVENT_TYPE_DISCONNECTED; for (const auto [func, arg] : nptool_callbacks) { if (func != nullptr) { Core::ExecuteGuest(func, event, arg); diff --git a/src/core/libraries/network/netctl.cpp b/src/core/libraries/network/netctl.cpp index 8d60d3627..ecf195004 100644 --- a/src/core/libraries/network/netctl.cpp +++ b/src/core/libraries/network/netctl.cpp @@ -15,6 +15,7 @@ #include #include "common/config.h" #include "common/logging/log.h" +#include "core/emulator_settings.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/network/net_ctl_codes.h" @@ -162,7 +163,7 @@ int PS4_SYSV_ABI sceNetCtlGetIfStat() { int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) { LOG_DEBUG(Lib_NetCtl, "code = {}", code); - if (!Config::getIsConnectedToNetwork()) { + if (!EmulatorSettings::GetInstance()->IsConnectedToNetwork()) { return ORBIS_NET_CTL_ERROR_NOT_CONNECTED; } @@ -180,8 +181,9 @@ int PS4_SYSV_ABI sceNetCtlGetInfo(int code, OrbisNetCtlInfo* info) { info->mtu = 1500; // default value break; case ORBIS_NET_CTL_INFO_LINK: - info->link = Config::getIsConnectedToNetwork() ? ORBIS_NET_CTL_LINK_CONNECTED - : ORBIS_NET_CTL_LINK_DISCONNECTED; + info->link = EmulatorSettings::GetInstance()->IsConnectedToNetwork() + ? ORBIS_NET_CTL_LINK_CONNECTED + : ORBIS_NET_CTL_LINK_DISCONNECTED; break; case ORBIS_NET_CTL_INFO_IP_ADDRESS: { strcpy(info->ip_address, @@ -318,7 +320,7 @@ int PS4_SYSV_ABI sceNetCtlGetScanInfoForSsidScanIpcInt() { } int PS4_SYSV_ABI sceNetCtlGetState(int* state) { - const auto connected = Config::getIsConnectedToNetwork(); + const auto connected = EmulatorSettings::GetInstance()->IsConnectedToNetwork(); LOG_DEBUG(Lib_NetCtl, "connected = {}", connected); const auto current_state = connected ? ORBIS_NET_CTL_STATE_IPOBTAINED : ORBIS_NET_CTL_STATE_DISCONNECTED; diff --git a/src/emulator.cpp b/src/emulator.cpp index 81284a1db..963a2387c 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -203,10 +203,11 @@ void Emulator::Run(std::filesystem::path file, std::vector args, Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml")); LOG_INFO(Config, "Game-specific config exists: {}", has_game_config); - LOG_INFO(Config, "General LogType: {}", Config::getLogType()); + LOG_INFO(Config, "General LogType: {}", EmulatorSettings::GetInstance()->GetLogType()); LOG_INFO(Config, "General isNeo: {}", EmulatorSettings::GetInstance()->IsNeo()); LOG_INFO(Config, "General isDevKit: {}", EmulatorSettings::GetInstance()->IsDevKit()); - LOG_INFO(Config, "General isConnectedToNetwork: {}", Config::getIsConnectedToNetwork()); + LOG_INFO(Config, "General isConnectedToNetwork: {}", + EmulatorSettings::GetInstance()->IsConnectedToNetwork()); LOG_INFO(Config, "General isPsnSignedIn: {}", EmulatorSettings::GetInstance()->IsPSNSignedIn()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks()); @@ -358,7 +359,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, #ifdef ENABLE_DISCORD_RPC // Discord RPC - if (Config::getEnableDiscordRPC()) { + if (EmulatorSettings::GetInstance()->IsDiscorRPCEnabled()) { auto* rpc = Common::Singleton::Instance(); if (rpc->getRPCEnabled() == false) { rpc->init(); From 6ee0ee65adf07e3b42ff229d8af948335b139fb0 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 27 Nov 2025 21:04:46 +0200 Subject: [PATCH 07/18] more settings conversion --- src/common/config.cpp | 24 ------------------- src/common/config.h | 4 ---- src/core/devtools/widget/shader_list.cpp | 5 ++-- src/core/emulator_settings.cpp | 4 ++++ src/core/emulator_settings.h | 11 +++++---- src/core/linker.cpp | 3 ++- .../renderer_vulkan/vk_pipeline_cache.cpp | 7 +++--- 7 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index b571c8ed4..32084cea0 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -180,8 +180,6 @@ static ConfigEntry vkGuestMarkers(false); static ConfigEntry rdocEnable(false); // Debug -static ConfigEntry isDebugDump(false); -static ConfigEntry isShaderDebug(false); static ConfigEntry isFpsColor(true); static ConfigEntry logEnabled(true); @@ -340,14 +338,6 @@ bool getIsMotionControlsEnabled() { return isMotionControlsEnabled.get(); } -bool debugDump() { - return isDebugDump.get(); -} - -bool collectShadersForDebug() { - return isShaderDebug.get(); -} - bool nullGpu() { return isNullGpu.get(); } @@ -455,18 +445,10 @@ void setInternalScreenHeight(u32 height) { internalScreenHeight.base_value = height; } -void setDebugDump(bool enable, bool is_game_specific) { - isDebugDump.set(enable, is_game_specific); -} - void setLoggingEnabled(bool enable, bool is_game_specific) { logEnabled.set(enable, is_game_specific); } -void setCollectShaderForDebug(bool enable, bool is_game_specific) { - isShaderDebug.set(enable, is_game_specific); -} - void setNullGpu(bool enable, bool is_game_specific) { isNullGpu.set(enable, is_game_specific); } @@ -727,8 +709,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { if (data.contains("Debug")) { const toml::value& debug = data.at("Debug"); - isDebugDump.setFromToml(debug, "DebugDump", is_game_specific); - isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific); isFpsColor.setFromToml(debug, "FPSColor", is_game_specific); logEnabled.setFromToml(debug, "logEnabled", is_game_specific); current_version = toml::find_or(debug, "ConfigVersion", current_version); @@ -871,8 +851,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { vkGuestMarkers.setTomlValue(data, "Vulkan", "guestMarkers", is_game_specific); rdocEnable.setTomlValue(data, "Vulkan", "rdocEnable", is_game_specific); - isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific); - isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific); logEnabled.setTomlValue(data, "Debug", "logEnabled", is_game_specific); m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific); @@ -985,8 +963,6 @@ void setDefaultValues(bool is_game_specific) { rdocEnable.set(false, is_game_specific); // GS - Debug - isDebugDump.set(false, is_game_specific); - isShaderDebug.set(false, is_game_specific); logEnabled.set(true, is_game_specific); // GS - Settings diff --git a/src/common/config.h b/src/common/config.h index af5acbee9..ab29c5bd4 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -47,14 +47,10 @@ u32 getInternalScreenWidth(); u32 getInternalScreenHeight(); void setInternalScreenWidth(u32 width); void setInternalScreenHeight(u32 height); -bool debugDump(); -void setDebugDump(bool enable, bool is_game_specific = false); s32 getGpuId(); void setGpuId(s32 selectedGpuId, bool is_game_specific = false); bool allowHDR(); void setAllowHDR(bool enable, bool is_game_specific = false); -bool collectShadersForDebug(); -void setCollectShaderForDebug(bool enable, bool is_game_specific = false); bool nullGpu(); void setNullGpu(bool enable, bool is_game_specific = false); bool copyGPUCmdBuffers(); diff --git a/src/core/devtools/widget/shader_list.cpp b/src/core/devtools/widget/shader_list.cpp index 0285db5a5..19f7a0308 100644 --- a/src/core/devtools/widget/shader_list.cpp +++ b/src/core/devtools/widget/shader_list.cpp @@ -13,6 +13,7 @@ #include "common/string_util.h" #include "core/debug_state.h" #include "core/devtools/options.h" +#include "core/emulator_settings.h" #include "imgui/imgui_std.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/vk_presenter.h" @@ -244,8 +245,8 @@ void ShaderList::Draw() { return; } - if (!Config::collectShadersForDebug()) { - DrawCenteredText("Enable 'CollectShader' in config to see shaders"); + if (!EmulatorSettings::GetInstance()->IsShaderDump()) { + DrawCenteredText("Enable 'shader_dump' in config to see shaders"); End(); return; } diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp index 55009140b..29f1b2b09 100644 --- a/src/core/emulator_settings.cpp +++ b/src/core/emulator_settings.cpp @@ -78,6 +78,10 @@ std::vector EmulatorSettings::GetGameInstallDirs() const return out; } +const std::vector& EmulatorSettings::GetAllGameInstallDirs() const { + return m_general.install_dirs.value; +} + void EmulatorSettings::SetAllGameInstallDirs(const std::vector& dirs) { m_general.install_dirs.value = dirs; } diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index f2885a044..b5a3bbde0 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -130,21 +130,21 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GeneralSettings, install_dirs, addon_install_ struct DebugSettings { Setting separate_logging_enabled{false}; // specific Setting debug_dump{false}; // specific - Setting shader_debug{false}; // specific + Setting shader_dump{false}; // specific Setting fps_color{true}; Setting log_enabled{true}; // specific std::vector GetOverrideableFields() const { return std::vector{ make_override("debug_dump", &DebugSettings::debug_dump), - make_override("shader_debug", &DebugSettings::shader_debug), + make_override("shader_dump", &DebugSettings::shader_dump), make_override("separate_logging_enabled", &DebugSettings::separate_logging_enabled), make_override("log_enabled", &DebugSettings::log_enabled)}; } }; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, separate_logging_enabled, debug_dump, - shader_debug, fps_color, log_enabled) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DebugSettings, separate_logging_enabled, debug_dump, shader_dump, + fps_color, log_enabled) // ------------------------------- // Input settings @@ -268,6 +268,7 @@ public: void SetGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled); void SetGameInstallDirs(const std::vector& dirs_config); const std::vector GetGameInstallDirsEnabled(); + const std::vector& GetAllGameInstallDirs() const; std::filesystem::path GetHomeDir(); void SetHomeDir(const std::filesystem::path& dir); @@ -340,6 +341,8 @@ public: // Debug settings SETTING_FORWARD_BOOL(m_debug, SeparateLoggingEnabled, separate_logging_enabled) + SETTING_FORWARD_BOOL(m_debug, DebugDump, debug_dump) + SETTING_FORWARD_BOOL(m_debug, ShaderDump, shader_dump) #undef SETTING_FORWARD #undef SETTING_FORWARD_BOOL diff --git a/src/core/linker.cpp b/src/core/linker.cpp index b7c9a2895..40450d28a 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -13,6 +13,7 @@ #include "core/aerolib/aerolib.h" #include "core/aerolib/stubs.h" #include "core/devtools/widget/module_list.h" +#include "core/emulator_settings.h" #include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/memory.h" #include "core/libraries/kernel/threads.h" @@ -56,7 +57,7 @@ Linker::Linker() : memory{Memory::Instance()} {} Linker::~Linker() = default; void Linker::Execute(const std::vector& args) { - if (Config::debugDump()) { + if (EmulatorSettings::GetInstance()->IsDebugDump()) { DebugDump(); } diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 4706bff24..69b06479c 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -8,6 +8,7 @@ #include "common/io_file.h" #include "common/path_util.h" #include "core/debug_state.h" +#include "core/emulator_settings.h" #include "shader_recompiler/backend/spirv/emit_spirv.h" #include "shader_recompiler/info.h" #include "shader_recompiler/recompiler.h" @@ -286,7 +287,7 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() { it.value() = std::make_unique(instance, scheduler, desc_heap, profile, graphics_key, *pipeline_cache, infos, runtime_infos, fetch_shader, modules); - if (Config::collectShadersForDebug()) { + if (EmulatorSettings::GetInstance()->IsShaderDump()) { for (auto stage = 0; stage < MaxShaderStages; ++stage) { if (infos[stage]) { auto& m = modules[stage]; @@ -310,7 +311,7 @@ const ComputePipeline* PipelineCache::GetComputePipeline() { it.value() = std::make_unique(instance, scheduler, desc_heap, profile, *pipeline_cache, compute_key, *infos[0], modules[0]); - if (Config::collectShadersForDebug()) { + if (EmulatorSettings::GetInstance()->IsShaderDump()) { auto& m = modules[0]; module_related_pipelines[m].emplace_back(compute_key); } @@ -538,7 +539,7 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::Runtim const auto name = GetShaderName(info.stage, info.pgm_hash, perm_idx); Vulkan::SetObjectName(instance.GetDevice(), module, name); - if (Config::collectShadersForDebug()) { + if (EmulatorSettings::GetInstance()->IsShaderDump()) { DebugState.CollectShader(name, info.l_stage, module, spv, code, patch ? *patch : std::span{}, is_patched); } From 039b1451593aba903dfaf49d2bad88ffd56bac19 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 28 Nov 2025 15:11:41 +0200 Subject: [PATCH 08/18] more settings --- src/common/config.cpp | 23 ------------------- src/common/config.h | 4 ---- src/core/emulator_settings.cpp | 2 +- src/core/emulator_settings.h | 13 ++++++++--- src/emulator.cpp | 2 +- src/main.cpp | 2 +- src/sdl_window.cpp | 3 ++- .../renderer_vulkan/vk_presenter.cpp | 3 ++- 8 files changed, 17 insertions(+), 35 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 32084cea0..1533fa374 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -160,7 +160,6 @@ static ConfigEntry directMemoryAccessEnabled(false); static ConfigEntry shouldDumpShaders(false); static ConfigEntry shouldPatchShaders(false); static ConfigEntry vblankFrequency(60); -static ConfigEntry isFullscreen(false); static ConfigEntry fullscreenMode("Windowed"); static ConfigEntry presentMode("Mailbox"); static ConfigEntry isHDRAllowed(false); @@ -169,7 +168,6 @@ static ConfigEntry rcasEnabled(true); static ConfigEntry rcasAttenuation(250); // Vulkan -static ConfigEntry gpuId(-1); static ConfigEntry vkValidation(false); static ConfigEntry vkValidationCore(true); static ConfigEntry vkValidationSync(false); @@ -270,10 +268,6 @@ void setVolumeSlider(int volumeValue, bool is_game_specific) { volumeSlider.set(volumeValue, is_game_specific); } -bool getIsFullscreen() { - return isFullscreen.get(); -} - string getFullscreenMode() { return fullscreenMode.get(); } @@ -318,9 +312,6 @@ u32 getInternalScreenHeight() { return internalScreenHeight.get(); } -s32 getGpuId() { - return gpuId.get(); -} string getUserName() { return userName.get(); @@ -425,10 +416,6 @@ void setVkGuestMarkersEnabled(bool enable, bool is_game_specific) { vkGuestMarkers.set(enable, is_game_specific); } -void setGpuId(s32 selectedGpuId, bool is_game_specific) { - gpuId.set(selectedGpuId, is_game_specific); -} - void setWindowWidth(u32 width, bool is_game_specific) { windowWidth.set(width, is_game_specific); } @@ -501,10 +488,6 @@ void setVblankFreq(u32 value, bool is_game_specific) { vblankFrequency.set(value, is_game_specific); } -void setIsFullscreen(bool enable, bool is_game_specific) { - isFullscreen.set(enable, is_game_specific); -} - void setFullscreenMode(string mode, bool is_game_specific) { fullscreenMode.set(mode, is_game_specific); } @@ -682,7 +665,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { shouldDumpShaders.setFromToml(gpu, "dumpShaders", is_game_specific); shouldPatchShaders.setFromToml(gpu, "patchShaders", is_game_specific); vblankFrequency.setFromToml(gpu, "vblankFrequency", is_game_specific); - isFullscreen.setFromToml(gpu, "Fullscreen", is_game_specific); fullscreenMode.setFromToml(gpu, "FullscreenMode", is_game_specific); presentMode.setFromToml(gpu, "presentMode", is_game_specific); isHDRAllowed.setFromToml(gpu, "allowHDR", is_game_specific); @@ -694,7 +676,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { if (data.contains("Vulkan")) { const toml::value& vk = data.at("Vulkan"); - gpuId.setFromToml(vk, "gpuId", is_game_specific); vkValidation.setFromToml(vk, "validation", is_game_specific); vkValidationCore.setFromToml(vk, "validation_core", is_game_specific); vkValidationSync.setFromToml(vk, "validation_sync", is_game_specific); @@ -832,7 +813,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { readbackLinearImagesEnabled.setTomlValue(data, "GPU", "readbackLinearImages", is_game_specific); shouldDumpShaders.setTomlValue(data, "GPU", "dumpShaders", is_game_specific); vblankFrequency.setTomlValue(data, "GPU", "vblankFrequency", is_game_specific); - isFullscreen.setTomlValue(data, "GPU", "Fullscreen", is_game_specific); fullscreenMode.setTomlValue(data, "GPU", "FullscreenMode", is_game_specific); presentMode.setTomlValue(data, "GPU", "presentMode", is_game_specific); isHDRAllowed.setTomlValue(data, "GPU", "allowHDR", is_game_specific); @@ -841,7 +821,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { rcasAttenuation.setTomlValue(data, "GPU", "rcasAttenuation", is_game_specific); directMemoryAccessEnabled.setTomlValue(data, "GPU", "directMemoryAccess", is_game_specific); - gpuId.setTomlValue(data, "Vulkan", "gpuId", is_game_specific); vkValidation.setTomlValue(data, "Vulkan", "validation", is_game_specific); vkValidationSync.setTomlValue(data, "Vulkan", "validation_sync", is_game_specific); vkValidationCore.setTomlValue(data, "Vulkan", "validation_core", is_game_specific); @@ -943,7 +922,6 @@ void setDefaultValues(bool is_game_specific) { shouldCopyGPUBuffers.set(false, is_game_specific); shouldDumpShaders.set(false, is_game_specific); vblankFrequency.set(60, is_game_specific); - isFullscreen.set(false, is_game_specific); fullscreenMode.set("Windowed", is_game_specific); presentMode.set("Mailbox", is_game_specific); isHDRAllowed.set(false, is_game_specific); @@ -952,7 +930,6 @@ void setDefaultValues(bool is_game_specific) { rcasAttenuation.set(250, is_game_specific); // GS - Vulkan - gpuId.set(-1, is_game_specific); vkValidation.set(false, is_game_specific); vkValidationCore.set(true, is_game_specific); vkValidationSync.set(false, is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index ab29c5bd4..032570cd5 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -33,8 +33,6 @@ int getVolumeSlider(); void setVolumeSlider(int volumeValue, bool is_game_specific = false); std::string getTrophyKey(); void setTrophyKey(std::string key); -bool getIsFullscreen(); -void setIsFullscreen(bool enable, bool is_game_specific = false); std::string getFullscreenMode(); void setFullscreenMode(std::string mode, bool is_game_specific = false); std::string getPresentMode(); @@ -47,8 +45,6 @@ u32 getInternalScreenWidth(); u32 getInternalScreenHeight(); void setInternalScreenWidth(u32 width); void setInternalScreenHeight(u32 height); -s32 getGpuId(); -void setGpuId(s32 selectedGpuId, bool is_game_specific = false); bool allowHDR(); void setAllowHDR(bool enable, bool is_game_specific = false); bool nullGpu(); diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp index 29f1b2b09..603b89c79 100644 --- a/src/core/emulator_settings.cpp +++ b/src/core/emulator_settings.cpp @@ -348,7 +348,7 @@ bool EmulatorSettings::Load(const std::string& serial) { } } -void EmulatorSettings::setDefaultValues() { +void EmulatorSettings::SetDefaultValues() { m_general = GeneralSettings{}; m_debug = DebugSettings{}; m_input = InputSettings{}; diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index b5a3bbde0..ee1bd48de 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -233,12 +233,15 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GPUSettings, window_width, window_height, int // ------------------------------- struct VulkanSettings { Setting gpu_id{-1}; + Setting full_screen{false}; // TODO std::vector GetOverrideableFields() const { - return std::vector{}; + return std::vector{ + make_override("gpu_id", &VulkanSettings::gpu_id), + make_override("full_screen", &VulkanSettings::full_screen)}; } }; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings, gpu_id) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings, gpu_id,full_screen) // ------------------------------- // User settings // ------------------------------- @@ -258,7 +261,7 @@ public: bool Save(const std::string& serial = "") const; bool Load(const std::string& serial = ""); - void setDefaultValues(); + void SetDefaultValues(); // general accessors bool AddGameInstallDir(const std::filesystem::path& dir, bool enabled = true); @@ -344,6 +347,10 @@ public: SETTING_FORWARD_BOOL(m_debug, DebugDump, debug_dump) SETTING_FORWARD_BOOL(m_debug, ShaderDump, shader_dump) + // Vulkan settings + SETTING_FORWARD(m_vulkan, GpuId, gpu_id) + SETTING_FORWARD_BOOL(m_vulkan, FullScreen, full_screen) + #undef SETTING_FORWARD #undef SETTING_FORWARD_BOOL }; diff --git a/src/emulator.cpp b/src/emulator.cpp index 963a2387c..6a35c4364 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -216,7 +216,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); LOG_INFO(Config, "GPU vblankFrequency: {}", Config::vblankFreq()); LOG_INFO(Config, "GPU shouldCopyGPUBuffers: {}", Config::copyGPUCmdBuffers()); - LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId()); + LOG_INFO(Config, "Vulkan gpuId: {}", EmulatorSettings::GetInstance()->GetGpuId()); LOG_INFO(Config, "Vulkan vkValidation: {}", Config::vkValidationEnabled()); LOG_INFO(Config, "Vulkan vkValidationCore: {}", Config::vkValidationCoreEnabled()); LOG_INFO(Config, "Vulkan vkValidationSync: {}", Config::vkValidationSyncEnabled()); diff --git a/src/main.cpp b/src/main.cpp index cdc2687d9..67854d5a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -117,7 +117,7 @@ int main(int argc, char* argv[]) { exit(1); } // Set fullscreen mode without saving it to config file - Config::setIsFullscreen(is_fullscreen); + EmulatorSettings::GetInstance()->SetFullScreen(is_fullscreen); }}, {"--fullscreen", [&](int& i) { arg_map["-f"](i); }}, {"--add-game-folder", diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 449defdd1..7287651a4 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -12,6 +12,7 @@ #include "common/elf_info.h" #include "core/debug_state.h" #include "core/devtools/layer.h" +#include "core/emulator_settings.h" #include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" #include "imgui/renderer/imgui_core.h" @@ -317,7 +318,7 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ SDL_SetWindowFullscreenMode( window, Config::getFullscreenMode() == "Fullscreen" ? displayMode : NULL); } - SDL_SetWindowFullscreen(window, Config::getIsFullscreen()); + SDL_SetWindowFullscreen(window, EmulatorSettings::GetInstance()->IsFullScreen()); SDL_InitSubSystem(SDL_INIT_GAMEPAD); controller->SetEngine(std::make_unique()); diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 1694d137f..b8c458917 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -7,6 +7,7 @@ #include "common/singleton.h" #include "core/debug_state.h" #include "core/devtools/layer.h" +#include "core/emulator_settings.h" #include "core/libraries/system/systemservice.h" #include "imgui/renderer/imgui_core.h" #include "imgui/renderer/imgui_impl_vulkan.h" @@ -104,7 +105,7 @@ static vk::Rect2D FitImage(s32 frame_width, s32 frame_height, s32 swapchain_widt Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_) : window{window_}, liverpool{liverpool_}, - instance{window, Config::getGpuId(), Config::vkValidationEnabled(), + instance{window, EmulatorSettings::GetInstance()->GetGpuId(), Config::vkValidationEnabled(), Config::getVkCrashDiagnosticEnabled()}, draw_scheduler{instance}, present_scheduler{instance}, flip_scheduler{instance}, swapchain{instance, window}, From abc456b62f32ab74d9ce33ea3685315d10a505e8 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 28 Nov 2025 20:04:20 +0200 Subject: [PATCH 09/18] even more settings --- src/common/config.cpp | 56 ------------------- src/common/config.h | 8 --- src/common/logging/backend.cpp | 3 +- src/core/devtools/widget/shader_list.cpp | 2 +- src/core/emulator_settings.h | 13 ++++- src/emulator.cpp | 2 +- src/sdl_window.cpp | 8 ++- .../renderer_vulkan/vk_presenter.cpp | 4 +- .../renderer_vulkan/vk_rasterizer.cpp | 5 +- .../renderer_vulkan/vk_swapchain.cpp | 7 ++- 10 files changed, 29 insertions(+), 79 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 1533fa374..3831236cb 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -152,7 +152,6 @@ static ConfigEntry windowWidth(1280); static ConfigEntry windowHeight(720); static ConfigEntry internalScreenWidth(1280); static ConfigEntry internalScreenHeight(720); -static ConfigEntry isNullGpu(false); static ConfigEntry shouldCopyGPUBuffers(false); static ConfigEntry readbacksEnabled(false); static ConfigEntry readbackLinearImagesEnabled(false); @@ -160,8 +159,6 @@ static ConfigEntry directMemoryAccessEnabled(false); static ConfigEntry shouldDumpShaders(false); static ConfigEntry shouldPatchShaders(false); static ConfigEntry vblankFrequency(60); -static ConfigEntry fullscreenMode("Windowed"); -static ConfigEntry presentMode("Mailbox"); static ConfigEntry isHDRAllowed(false); static ConfigEntry fsrEnabled(false); static ConfigEntry rcasEnabled(true); @@ -179,7 +176,6 @@ static ConfigEntry rdocEnable(false); // Debug static ConfigEntry isFpsColor(true); -static ConfigEntry logEnabled(true); // GUI static std::vector settings_install_dirs = {}; @@ -239,10 +235,6 @@ int* GetControllerCustomColor() { return controllerCustomColorRGB; } -bool getLoggingEnabled() { - return logEnabled.get(); -} - void SetControllerCustomColor(int r, int b, int g) { controllerCustomColorRGB[0] = r; controllerCustomColorRGB[1] = b; @@ -268,14 +260,6 @@ void setVolumeSlider(int volumeValue, bool is_game_specific) { volumeSlider.set(volumeValue, is_game_specific); } -string getFullscreenMode() { - return fullscreenMode.get(); -} - -std::string getPresentMode() { - return presentMode.get(); -} - s16 getCursorState() { return cursorState.get(); } @@ -312,7 +296,6 @@ u32 getInternalScreenHeight() { return internalScreenHeight.get(); } - string getUserName() { return userName.get(); } @@ -329,10 +312,6 @@ bool getIsMotionControlsEnabled() { return isMotionControlsEnabled.get(); } -bool nullGpu() { - return isNullGpu.get(); -} - bool copyGPUCmdBuffers() { return shouldCopyGPUBuffers.get(); } @@ -365,10 +344,6 @@ bool fpsColor() { return isFpsColor.get(); } -bool isLoggingEnabled() { - return logEnabled.get(); -} - u32 vblankFreq() { if (vblankFrequency.get() < 60) { vblankFrequency = 60; @@ -432,14 +407,6 @@ void setInternalScreenHeight(u32 height) { internalScreenHeight.base_value = height; } -void setLoggingEnabled(bool enable, bool is_game_specific) { - logEnabled.set(enable, is_game_specific); -} - -void setNullGpu(bool enable, bool is_game_specific) { - isNullGpu.set(enable, is_game_specific); -} - void setAllowHDR(bool enable, bool is_game_specific) { isHDRAllowed.set(enable, is_game_specific); } @@ -488,14 +455,6 @@ void setVblankFreq(u32 value, bool is_game_specific) { vblankFrequency.set(value, is_game_specific); } -void setFullscreenMode(string mode, bool is_game_specific) { - fullscreenMode.set(mode, is_game_specific); -} - -void setPresentMode(std::string mode, bool is_game_specific) { - presentMode.set(mode, is_game_specific); -} - void setCursorState(s16 newCursorState, bool is_game_specific) { cursorState.set(newCursorState, is_game_specific); } @@ -657,7 +616,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { windowHeight.setFromToml(gpu, "screenHeight", is_game_specific); internalScreenWidth.setFromToml(gpu, "internalScreenWidth", is_game_specific); internalScreenHeight.setFromToml(gpu, "internalScreenHeight", is_game_specific); - isNullGpu.setFromToml(gpu, "nullGpu", is_game_specific); shouldCopyGPUBuffers.setFromToml(gpu, "copyGPUBuffers", is_game_specific); readbacksEnabled.setFromToml(gpu, "readbacks", is_game_specific); readbackLinearImagesEnabled.setFromToml(gpu, "readbackLinearImages", is_game_specific); @@ -665,8 +623,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { shouldDumpShaders.setFromToml(gpu, "dumpShaders", is_game_specific); shouldPatchShaders.setFromToml(gpu, "patchShaders", is_game_specific); vblankFrequency.setFromToml(gpu, "vblankFrequency", is_game_specific); - fullscreenMode.setFromToml(gpu, "FullscreenMode", is_game_specific); - presentMode.setFromToml(gpu, "presentMode", is_game_specific); isHDRAllowed.setFromToml(gpu, "allowHDR", is_game_specific); fsrEnabled.setFromToml(gpu, "fsrEnabled", is_game_specific); rcasEnabled.setFromToml(gpu, "rcasEnabled", is_game_specific); @@ -691,7 +647,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { const toml::value& debug = data.at("Debug"); isFpsColor.setFromToml(debug, "FPSColor", is_game_specific); - logEnabled.setFromToml(debug, "logEnabled", is_game_specific); current_version = toml::find_or(debug, "ConfigVersion", current_version); } @@ -807,14 +762,11 @@ void save(const std::filesystem::path& path, bool is_game_specific) { windowWidth.setTomlValue(data, "GPU", "screenWidth", is_game_specific); windowHeight.setTomlValue(data, "GPU", "screenHeight", is_game_specific); - isNullGpu.setTomlValue(data, "GPU", "nullGpu", is_game_specific); shouldCopyGPUBuffers.setTomlValue(data, "GPU", "copyGPUBuffers", is_game_specific); readbacksEnabled.setTomlValue(data, "GPU", "readbacks", is_game_specific); readbackLinearImagesEnabled.setTomlValue(data, "GPU", "readbackLinearImages", is_game_specific); shouldDumpShaders.setTomlValue(data, "GPU", "dumpShaders", is_game_specific); vblankFrequency.setTomlValue(data, "GPU", "vblankFrequency", is_game_specific); - fullscreenMode.setTomlValue(data, "GPU", "FullscreenMode", is_game_specific); - presentMode.setTomlValue(data, "GPU", "presentMode", is_game_specific); isHDRAllowed.setTomlValue(data, "GPU", "allowHDR", is_game_specific); fsrEnabled.setTomlValue(data, "GPU", "fsrEnabled", is_game_specific); rcasEnabled.setTomlValue(data, "GPU", "rcasEnabled", is_game_specific); @@ -830,8 +782,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) { vkGuestMarkers.setTomlValue(data, "Vulkan", "guestMarkers", is_game_specific); rdocEnable.setTomlValue(data, "Vulkan", "rdocEnable", is_game_specific); - logEnabled.setTomlValue(data, "Debug", "logEnabled", is_game_specific); - m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific); if (!is_game_specific) { @@ -918,12 +868,9 @@ void setDefaultValues(bool is_game_specific) { // GS - GPU windowWidth.set(1280, is_game_specific); windowHeight.set(720, is_game_specific); - isNullGpu.set(false, is_game_specific); shouldCopyGPUBuffers.set(false, is_game_specific); shouldDumpShaders.set(false, is_game_specific); vblankFrequency.set(60, is_game_specific); - fullscreenMode.set("Windowed", is_game_specific); - presentMode.set("Mailbox", is_game_specific); isHDRAllowed.set(false, is_game_specific); fsrEnabled.set(true, is_game_specific); rcasEnabled.set(true, is_game_specific); @@ -939,9 +886,6 @@ void setDefaultValues(bool is_game_specific) { vkGuestMarkers.set(false, is_game_specific); rdocEnable.set(false, is_game_specific); - // GS - Debug - logEnabled.set(true, is_game_specific); - // GS - Settings m_language.set(1, is_game_specific); diff --git a/src/common/config.h b/src/common/config.h index 032570cd5..8133f86cf 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -33,10 +33,6 @@ int getVolumeSlider(); void setVolumeSlider(int volumeValue, bool is_game_specific = false); std::string getTrophyKey(); void setTrophyKey(std::string key); -std::string getFullscreenMode(); -void setFullscreenMode(std::string mode, bool is_game_specific = false); -std::string getPresentMode(); -void setPresentMode(std::string mode, bool is_game_specific = false); u32 getWindowWidth(); u32 getWindowHeight(); void setWindowWidth(u32 width, bool is_game_specific = false); @@ -47,8 +43,6 @@ void setInternalScreenWidth(u32 width); void setInternalScreenHeight(u32 height); bool allowHDR(); void setAllowHDR(bool enable, bool is_game_specific = false); -bool nullGpu(); -void setNullGpu(bool enable, bool is_game_specific = false); bool copyGPUCmdBuffers(); void setCopyGPUCmdBuffers(bool enable, bool is_game_specific = false); bool readbacks(); @@ -101,8 +95,6 @@ std::string getDefaultControllerID(); void setDefaultControllerID(std::string id); bool getBackgroundControllerInput(); void setBackgroundControllerInput(bool enable, bool is_game_specific = false); -bool getLoggingEnabled(); -void setLoggingEnabled(bool enable, bool is_game_specific = false); bool getFsrEnabled(); void setFsrEnabled(bool enable, bool is_game_specific = false); bool getRcasEnabled(); diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 808df8599..fc9cdd00c 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -184,7 +184,8 @@ public: void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, const char* format, const fmt::format_args& args) { - if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) { + if (!filter.CheckMessage(log_class, log_level) || + !EmulatorSettings::GetInstance()->IsLogEnabled()) { return; } diff --git a/src/core/devtools/widget/shader_list.cpp b/src/core/devtools/widget/shader_list.cpp index 19f7a0308..0cfa6b9e9 100644 --- a/src/core/devtools/widget/shader_list.cpp +++ b/src/core/devtools/widget/shader_list.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index ee1bd48de..546a362fa 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -218,7 +218,10 @@ struct GPUSettings { Setting rcas_attenuation{250}; // TODO add overrides std::vector GetOverrideableFields() const { - return std::vector{}; + return std::vector{ + make_override("null_gpu", &GPUSettings::null_gpu), + make_override("full_screen", &GPUSettings::full_screen), + make_override("present_mode", &GPUSettings::present_mode)}; } }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GPUSettings, window_width, window_height, internal_screen_width, @@ -241,7 +244,7 @@ struct VulkanSettings { make_override("full_screen", &VulkanSettings::full_screen)}; } }; -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings, gpu_id,full_screen) +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(VulkanSettings, gpu_id, full_screen) // ------------------------------- // User settings // ------------------------------- @@ -346,6 +349,12 @@ public: SETTING_FORWARD_BOOL(m_debug, SeparateLoggingEnabled, separate_logging_enabled) SETTING_FORWARD_BOOL(m_debug, DebugDump, debug_dump) SETTING_FORWARD_BOOL(m_debug, ShaderDump, shader_dump) + SETTING_FORWARD_BOOL(m_debug, LogEnabled, log_enabled) + + // GPU Settings + SETTING_FORWARD_BOOL(m_gpu, NullGPU, null_gpu) + SETTING_FORWARD(m_gpu, FullScreenMode, full_screen_mode) + SETTING_FORWARD(m_gpu, PresentMode, present_mode) // Vulkan settings SETTING_FORWARD(m_vulkan, GpuId, gpu_id) diff --git a/src/emulator.cpp b/src/emulator.cpp index 6a35c4364..3e62f6efb 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -209,7 +209,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, LOG_INFO(Config, "General isConnectedToNetwork: {}", EmulatorSettings::GetInstance()->IsConnectedToNetwork()); LOG_INFO(Config, "General isPsnSignedIn: {}", EmulatorSettings::GetInstance()->IsPSNSignedIn()); - LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); + LOG_INFO(Config, "GPU isNullGpu: {}", EmulatorSettings::GetInstance()->IsNullGPU()); LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks()); LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages()); LOG_INFO(Config, "GPU directMemoryAccess: {}", Config::directMemoryAccess()); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 7287651a4..3959588c8 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "SDL3/SDL_events.h" @@ -315,8 +315,10 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ error = true; } if (!error) { - SDL_SetWindowFullscreenMode( - window, Config::getFullscreenMode() == "Fullscreen" ? displayMode : NULL); + SDL_SetWindowFullscreenMode(window, EmulatorSettings::GetInstance()->GetFullScreenMode() == + "Fullscreen" + ? displayMode + : NULL); } SDL_SetWindowFullscreen(window, EmulatorSettings::GetInstance()->IsFullScreen()); diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index b8c458917..129cef6a2 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/config.h" @@ -578,7 +578,7 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) { ImGui::SetCursorPos(ImGui::GetCursorStartPos() + offset); ImGui::Image(game_texture, size); - if (Config::nullGpu()) { + if (EmulatorSettings::GetInstance()->IsNullGPU()) { Core::Devtools::Layer::DrawNullGpuNotice(); } } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 214d6d697..dab9c30cf 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/config.h" #include "common/debug.h" +#include "core/emulator_settings.h" #include "core/memory.h" #include "shader_recompiler/runtime_info.h" #include "video_core/amdgpu/liverpool.h" @@ -38,7 +39,7 @@ Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, texture_cache{instance, scheduler, liverpool_, buffer_cache, page_manager}, liverpool{liverpool_}, memory{Core::Memory::Instance()}, pipeline_cache{instance, scheduler, liverpool} { - if (!Config::nullGpu()) { + if (!EmulatorSettings::GetInstance()->IsNullGPU()) { liverpool->BindRasterizer(this); } memory->SetRasterizer(this); diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 4dd3bd502..3fc857bd9 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -6,6 +6,7 @@ #include "common/assert.h" #include "common/config.h" #include "common/logging/log.h" +#include "core/emulator_settings.h" #include "imgui/renderer/imgui_core.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -199,7 +200,7 @@ void Swapchain::FindPresentMode() { return; } - const auto requested_mode = Config::getPresentMode(); + const auto requested_mode = EmulatorSettings::GetInstance()->GetPresentMode(); if (requested_mode == "Mailbox") { present_mode = vk::PresentModeKHR::eMailbox; } else if (requested_mode == "Fifo") { @@ -208,7 +209,7 @@ void Swapchain::FindPresentMode() { present_mode = vk::PresentModeKHR::eImmediate; } else { LOG_ERROR(Render_Vulkan, "Unknown present mode {}, defaulting to Mailbox.", - Config::getPresentMode()); + EmulatorSettings::GetInstance()->GetPresentMode()); present_mode = vk::PresentModeKHR::eMailbox; } From d1e9b47fc3141a3c5e983c5e4f20b421ac3ea4bb Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 29 Nov 2025 15:07:26 +0200 Subject: [PATCH 10/18] removed load before init singleton --- src/core/emulator_settings.cpp | 2 +- src/main.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp index 603b89c79..e63c5c1d6 100644 --- a/src/core/emulator_settings.cpp +++ b/src/core/emulator_settings.cpp @@ -41,7 +41,7 @@ void EmulatorSettings::PrintChangedSummary(const std::vector& chang // ctor/dtor + singleton // -------------------- EmulatorSettings::EmulatorSettings() { - Load(); + // Load(); } EmulatorSettings::~EmulatorSettings() { Save(); diff --git a/src/main.cpp b/src/main.cpp index 67854d5a0..54b70b093 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,6 +31,7 @@ int main(int argc, char* argv[]) { // Load configurations std::shared_ptr emu_settings = std::make_shared(); EmulatorSettings::SetInstance(emu_settings); + emu_settings->Load(); const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(user_dir / "config.toml"); From ad1e58ed16d8e6bcb609a21a5db3221462f953fd Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sat, 29 Nov 2025 11:03:46 +0100 Subject: [PATCH 11/18] Squashed multiple controllers PR --- src/common/config.cpp | 88 +++-- src/common/config.h | 3 +- src/core/libraries/np/np_manager.cpp | 5 +- src/core/libraries/pad/pad.cpp | 101 +++--- src/core/libraries/system/userservice.cpp | 50 ++- src/core/libraries/system/userservice.h | 4 +- src/emulator.cpp | 4 +- src/emulator.h | 4 +- src/imgui/renderer/imgui_impl_sdl3.cpp | 7 +- src/input/controller.cpp | 233 +++++++++----- src/input/controller.h | 67 ++-- src/input/input_handler.cpp | 219 +++++++------ src/input/input_handler.h | 107 ++++++- src/sdl_window.cpp | 370 +++++++--------------- src/sdl_window.h | 25 +- 15 files changed, 688 insertions(+), 599 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 3831236cb..d1b75be32 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -46,7 +46,7 @@ std::optional get_optional(const toml::value& v, const std::string& key) { if constexpr (std::is_same_v) { if (it->second.is_integer()) { - return static_cast(toml::get(it->second)); + return static_cast(toml::get(it->second)); } } else if constexpr (std::is_same_v) { if (it->second.is_integer()) { @@ -54,15 +54,19 @@ std::optional get_optional(const toml::value& v, const std::string& key) { } } else if constexpr (std::is_same_v) { if (it->second.is_floating()) { - return toml::get(it->second); + return toml::get(it->second); } } else if constexpr (std::is_same_v) { if (it->second.is_string()) { - return toml::get(it->second); + return toml::get(it->second); } } else if constexpr (std::is_same_v) { if (it->second.is_boolean()) { - return toml::get(it->second); + return toml::get(it->second); + } + } else if constexpr (std::is_same_v>) { + if (it->second.is_array()) { + return toml::get(it->second); } } else { static_assert([] { return false; }(), "Unsupported type in get_optional"); @@ -114,6 +118,9 @@ public: void set(const T value, bool is_game_specific = false) { is_game_specific ? game_specific_value = value : base_value = value; } + void setDefault(bool is_game_specific = false) { + is_game_specific ? game_specific_value = default_value : base_value = default_value; + } void setTomlValue(toml::ordered_value& data, const std::string& header, const std::string& key, bool is_game_specific = false) { if (is_game_specific) { @@ -130,7 +137,12 @@ public: // General static ConfigEntry volumeSlider(100); -static ConfigEntry userName("shadPS4"); +static ConfigEntry> userNames({ + "shadPS4", + "shadps4-2", + "shadPS4-3", + "shadPS4-4", +}); // Input static ConfigEntry cursorState(HideCursorState::Idle); @@ -296,8 +308,18 @@ u32 getInternalScreenHeight() { return internalScreenHeight.get(); } -string getUserName() { - return userName.get(); +void setUserName(int id, string name) { + auto temp = userNames.get(); + temp[id] = name; + userNames.set(temp); +} + +std::array const getUserNames() { + return userNames.get(); +} + +std::string getUserName(int id) { + return userNames.get()[id]; } bool getUseSpecialPad() { @@ -479,10 +501,6 @@ void setLanguage(u32 language, bool is_game_specific) { m_language.set(language, is_game_specific); } -void setUserName(const string& name, bool is_game_specific) { - userName.set(name, is_game_specific); -} - void setUseSpecialPad(bool use) { useSpecialPad.base_value = use; } @@ -584,7 +602,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { const toml::value& general = data.at("General"); volumeSlider.setFromToml(general, "volumeSlider", is_game_specific); - userName.setFromToml(general, "userName", is_game_specific); + userNames.setFromToml(general, "userNames", is_game_specific); defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific); } @@ -746,7 +764,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { } // Entries saved by the game-specific settings GUI volumeSlider.setTomlValue(data, "General", "volumeSlider", is_game_specific); - userName.setTomlValue(data, "General", "userName", is_game_specific); + userNames.setTomlValue(data, "General", "userNames", is_game_specific); cursorState.setTomlValue(data, "Input", "cursorState", is_game_specific); cursorHideTimeout.setTomlValue(data, "Input", "cursorHideTimeout", is_game_specific); @@ -845,36 +863,36 @@ void setDefaultValues(bool is_game_specific) { // Entries with game-specific settings that are in the game-specific setings GUI but not in // the global settings GUI if (is_game_specific) { - readbacksEnabled.set(false, is_game_specific); - readbackLinearImagesEnabled.set(false, is_game_specific); - directMemoryAccessEnabled.set(false, is_game_specific); + readbacksEnabled.setDefault(is_game_specific); + readbackLinearImagesEnabled.setDefault(is_game_specific); + directMemoryAccessEnabled.setDefault(is_game_specific); } // Entries with game-specific settings that are in both the game-specific and global GUI // GS - General - volumeSlider.set(100, is_game_specific); - userName.set("shadPS4", is_game_specific); + volumeSlider.setDefault(is_game_specific); + userNames.setDefault(is_game_specific); // GS - Input - cursorState.set(HideCursorState::Idle, is_game_specific); - cursorHideTimeout.set(5, is_game_specific); - isMotionControlsEnabled.set(true, is_game_specific); - backgroundControllerInput.set(false, is_game_specific); - usbDeviceBackend.set(UsbBackendType::Real, is_game_specific); + cursorState.setDefault(is_game_specific); + cursorHideTimeout.setDefault(is_game_specific); + isMotionControlsEnabled.setDefault(is_game_specific); + backgroundControllerInput.setDefault(is_game_specific); + usbDeviceBackend.setDefault(is_game_specific); // GS - Audio - micDevice.set("Default Device", is_game_specific); + micDevice.setDefault(is_game_specific); // GS - GPU - windowWidth.set(1280, is_game_specific); - windowHeight.set(720, is_game_specific); - shouldCopyGPUBuffers.set(false, is_game_specific); - shouldDumpShaders.set(false, is_game_specific); - vblankFrequency.set(60, is_game_specific); - isHDRAllowed.set(false, is_game_specific); - fsrEnabled.set(true, is_game_specific); - rcasEnabled.set(true, is_game_specific); - rcasAttenuation.set(250, is_game_specific); + windowWidth.setDefault(is_game_specific); + windowHeight.setDefault(is_game_specific); + shouldCopyGPUBuffers.setDefault(is_game_specific); + shouldDumpShaders.setDefault(is_game_specific); + vblankFrequency.setDefault(is_game_specific); + isHDRAllowed.setDefault(is_game_specific); + fsrEnabled.setDefault(is_game_specific); + rcasEnabled.setDefault(is_game_specific); + rcasAttenuation.setDefault(is_game_specific); // GS - Vulkan vkValidation.set(false, is_game_specific); @@ -887,7 +905,7 @@ void setDefaultValues(bool is_game_specific) { rdocEnable.set(false, is_game_specific); // GS - Settings - m_language.set(1, is_game_specific); + m_language.setDefault(is_game_specific); // All other entries if (!is_game_specific) { @@ -924,6 +942,8 @@ hotkey_pause = f9 hotkey_reload_inputs = f8 hotkey_toggle_mouse_to_joystick = f7 hotkey_toggle_mouse_to_gyro = f6 +hotkey_add_virtual_user = f5 +hotkey_remove_virtual_user = f4 hotkey_quit = lctrl, lshift, end )"; } diff --git a/src/common/config.h b/src/common/config.h index 8133f86cf..fa3cd5407 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -111,7 +111,8 @@ void setUsbDeviceBackend(int value, bool is_game_specific = false); // TODO std::filesystem::path GetSaveDataPath(); -std::string getUserName(); +std::string getUserName(int id); +std::array const getUserNames(); bool GetUseUnifiedInputConfig(); void SetUseUnifiedInputConfig(bool use); bool GetOverrideControllerColor(); diff --git a/src/core/libraries/np/np_manager.cpp b/src/core/libraries/np/np_manager.cpp index 65acbef7f..69f8eea0e 100644 --- a/src/core/libraries/np/np_manager.cpp +++ b/src/core/libraries/np/np_manager.cpp @@ -627,7 +627,8 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use return ORBIS_NP_ERROR_SIGNED_OUT; } memset(np_id, 0, sizeof(OrbisNpId)); - strncpy(np_id->handle.data, Config::getUserName().c_str(), sizeof(np_id->handle.data)); + strncpy(np_id->handle.data, Config::getUserName(user_id - 1).c_str(), + sizeof(np_id->handle.data)); return ORBIS_OK; } @@ -641,7 +642,7 @@ s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId return ORBIS_NP_ERROR_SIGNED_OUT; } memset(online_id, 0, sizeof(OrbisNpOnlineId)); - strncpy(online_id->data, Config::getUserName().c_str(), sizeof(online_id->data)); + strncpy(online_id->data, Config::getUserName(user_id - 1).c_str(), sizeof(online_id->data)); return ORBIS_OK; } diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 09f404969..eb6f0ea9e 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/config.h" @@ -274,7 +274,7 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP g_opened = true; scePadResetLightBar(userId); scePadResetOrientation(userId); - return 1; // dummy + return userId; // TODO: userId shouldn't be used as the handle too } int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, @@ -287,7 +287,7 @@ int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED; } - return 1; // dummy + return userId; // dummy } int PS4_SYSV_ABI scePadOpenExt2() { @@ -301,12 +301,16 @@ int PS4_SYSV_ABI scePadOutputReport() { } int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { - LOG_TRACE(Lib_Pad, "called"); + LOG_TRACE(Lib_Pad, "handle: {}", handle); + auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle); + if (!controller_id) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } int connected_count = 0; bool connected = false; Input::State states[64]; - auto* controller = Common::Singleton::Instance(); - const auto* engine = controller->GetEngine(); + auto controllers = *Common::Singleton::Instance(); + auto const& controller = controllers[*controller_id]; int ret_num = controller->ReadStates(states, num, &connected, &connected_count); if (!connected) { @@ -321,9 +325,6 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { pData[i].rightStick.y = states[i].axes[static_cast(Input::Axis::RightY)]; pData[i].analogButtons.l2 = states[i].axes[static_cast(Input::Axis::TriggerLeft)]; pData[i].analogButtons.r2 = states[i].axes[static_cast(Input::Axis::TriggerRight)]; - pData[i].acceleration.x = states[i].acceleration.x; - pData[i].acceleration.y = states[i].acceleration.y; - pData[i].acceleration.z = states[i].acceleration.z; pData[i].angularVelocity.x = states[i].angularVelocity.x; pData[i].angularVelocity.y = states[i].angularVelocity.y; pData[i].angularVelocity.z = states[i].angularVelocity.z; @@ -335,8 +336,8 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { pData[i].angularVelocity.y = states[i].angularVelocity.y; pData[i].angularVelocity.z = states[i].angularVelocity.z; - if (engine && handle == 1) { - const auto gyro_poll_rate = engine->GetAccelPollRate(); + if (handle == 1) { + const auto gyro_poll_rate = controller->accel_poll_rate; if (gyro_poll_rate != 0.0f) { auto now = std::chrono::steady_clock::now(); float deltaTime = std::chrono::duration_cast( @@ -431,24 +432,34 @@ int PS4_SYSV_ABI scePadReadHistory() { } int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { - LOG_TRACE(Lib_Pad, "called"); - if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE) { + LOG_TRACE(Lib_Pad, "handle: {}", handle); + auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle); + if (!controller_id) { return ORBIS_PAD_ERROR_INVALID_HANDLE; } - auto* controller = Common::Singleton::Instance(); - const auto* engine = controller->GetEngine(); + auto controllers = *Common::Singleton::Instance(); int connectedCount = 0; bool isConnected = false; Input::State state; + auto const& controller = controllers[*controller_id]; controller->ReadState(&state, &isConnected, &connectedCount); pData->buttons = state.buttonsState; - pData->leftStick.x = state.axes[static_cast(Input::Axis::LeftX)]; - pData->leftStick.y = state.axes[static_cast(Input::Axis::LeftY)]; - pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; - pData->rightStick.x = state.axes[static_cast(Input::Axis::RightX)]; - pData->rightStick.y = state.axes[static_cast(Input::Axis::RightY)]; - pData->analogButtons.l2 = state.axes[static_cast(Input::Axis::TriggerLeft)]; - pData->analogButtons.r2 = state.axes[static_cast(Input::Axis::TriggerRight)]; + + auto getAxisValue = [&state, &controller](Input::Axis a) { + auto i = static_cast(a); + if (controller->axis_smoothing_ticks[i] > 0) { + --controller->axis_smoothing_ticks[i]; + return (state.axes[i] + controller->axis_smoothing_values[i]) / 2; + } + return state.axes[i]; + }; + + pData->leftStick.x = getAxisValue(Input::Axis::LeftX); + pData->leftStick.y = getAxisValue(Input::Axis::LeftY); + pData->rightStick.x = getAxisValue(Input::Axis::RightX); + pData->rightStick.y = getAxisValue(Input::Axis::RightY); + pData->analogButtons.l2 = getAxisValue(Input::Axis::TriggerLeft); + pData->analogButtons.r2 = getAxisValue(Input::Axis::TriggerRight); pData->acceleration.x = state.acceleration.x * 0.098; pData->acceleration.y = state.acceleration.y * 0.098; pData->acceleration.z = state.acceleration.z * 0.098; @@ -457,21 +468,19 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { pData->angularVelocity.z = state.angularVelocity.z; pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f}; - // Only do this on handle 1 for now - if (engine && handle == 1) { - auto now = std::chrono::steady_clock::now(); - float deltaTime = - std::chrono::duration_cast(now - controller->GetLastUpdate()) - .count() / - 1000000.0f; - controller->SetLastUpdate(now); - Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); - Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; - GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime, - lastOrientation, outputOrientation); - pData->orientation = outputOrientation; - controller->SetLastOrientation(outputOrientation); - } + auto now = std::chrono::steady_clock::now(); + float deltaTime = + std::chrono::duration_cast(now - controller->GetLastUpdate()) + .count() / + 1000000.0f; + controller->SetLastUpdate(now); + Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); + Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; + GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime, + lastOrientation, outputOrientation); + pData->orientation = outputOrientation; + controller->SetLastOrientation(outputOrientation); + pData->touchData.touchNum = (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); @@ -536,12 +545,13 @@ int PS4_SYSV_ABI scePadReadStateExt() { int PS4_SYSV_ABI scePadResetLightBar(s32 handle) { LOG_INFO(Lib_Pad, "(DUMMY) called"); - if (handle != 1) { + auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle); + if (!controller_id) { return ORBIS_PAD_ERROR_INVALID_HANDLE; } - auto* controller = Common::Singleton::Instance(); + auto controllers = *Common::Singleton::Instance(); int* rgb = Config::GetControllerCustomColor(); - controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]); + controllers[*controller_id]->SetLightBarRGB(rgb[0], rgb[1], rgb[2]); return ORBIS_OK; } @@ -558,7 +568,8 @@ int PS4_SYSV_ABI scePadResetLightBarAllByPortType() { int PS4_SYSV_ABI scePadResetOrientation(s32 handle) { LOG_INFO(Lib_Pad, "scePadResetOrientation called handle = {}", handle); - if (handle != 1) { + auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle); + if (!controller_id) { return ORBIS_PAD_ERROR_INVALID_HANDLE; } @@ -614,6 +625,10 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar if (Config::GetOverrideControllerColor()) { return ORBIS_OK; } + auto controller_id = GamepadSelect::GetControllerIndexFromUserID(handle); + if (!controller_id) { + return ORBIS_PAD_ERROR_INVALID_HANDLE; + } if (pParam != nullptr) { LOG_DEBUG(Lib_Pad, "called handle = {} rgb = {} {} {}", handle, pParam->r, pParam->g, pParam->b); @@ -623,7 +638,7 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING; } - auto* controller = Common::Singleton::Instance(); + auto* controller = Common::Singleton::Instance(); controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b); return ORBIS_OK; } @@ -691,7 +706,7 @@ int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pP if (pParam != nullptr) { LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle, pParam->smallMotor, pParam->largeMotor); - auto* controller = Common::Singleton::Instance(); + auto* controller = Common::Singleton::Instance(); controller->SetVibration(pParam->smallMotor, pParam->largeMotor); return ORBIS_OK; } diff --git a/src/core/libraries/system/userservice.cpp b/src/core/libraries/system/userservice.cpp index 508b1d7e5..13f181b5f 100644 --- a/src/core/libraries/system/userservice.cpp +++ b/src/core/libraries/system/userservice.cpp @@ -1,12 +1,17 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include "common/config.h" #include "common/logging/log.h" +#include "common/singleton.h" #include "core/libraries/libs.h" #include "core/libraries/system/userservice.h" #include "core/libraries/system/userservice_error.h" +#include "core/tls.h" +#include "input/controller.h" namespace Libraries::UserService { @@ -105,15 +110,23 @@ int PS4_SYSV_ABI sceUserServiceGetDiscPlayerFlag() { return ORBIS_OK; } +std::queue user_service_event_queue = {}; + +void AddUserServiceEvent(const OrbisUserServiceEvent e) { + LOG_DEBUG(Lib_UserService, "Event added to queue: {} {}", (u8)e.event, e.userId); + user_service_event_queue.push(e); +} + s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) { LOG_TRACE(Lib_UserService, "(DUMMY) called"); - // fake a loggin event - static bool logged_in = false; - if (!logged_in) { - logged_in = true; - event->event = OrbisUserServiceEventType::Login; - event->userId = 1; + if (!user_service_event_queue.empty()) { + OrbisUserServiceEvent& temp = user_service_event_queue.front(); + event->event = temp.event; + event->userId = temp.userId; + user_service_event_queue.pop(); + LOG_INFO(Lib_UserService, "Event processed by the game: {} {}", (u8)temp.event, + temp.userId); return ORBIS_OK; } @@ -567,17 +580,22 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() { } s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) { - LOG_DEBUG(Lib_UserService, "called"); + // LOG_DEBUG(Lib_UserService, "called"); if (userIdList == nullptr) { LOG_ERROR(Lib_UserService, "user_id is null"); return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; } // TODO only first user, do the others as well - userIdList->user_id[0] = 1; - userIdList->user_id[1] = ORBIS_USER_SERVICE_USER_ID_INVALID; - userIdList->user_id[2] = ORBIS_USER_SERVICE_USER_ID_INVALID; - userIdList->user_id[3] = ORBIS_USER_SERVICE_USER_ID_INVALID; - + auto controllers = *Common::Singleton::Instance(); + int li = 0; + for (int ci = 0; ci < 4; ci++) { + if (controllers[ci]->user_id != -1) { + userIdList->user_id[li++] = controllers[ci]->user_id; + } + } + for (; li < 4; li++) { + userIdList->user_id[li] = -1; + } return ORBIS_OK; } @@ -1048,7 +1066,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserColor(int user_id, OrbisUserServiceUserCol LOG_ERROR(Lib_UserService, "color is null"); return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; } - *color = OrbisUserServiceUserColor::Blue; + *color = (OrbisUserServiceUserColor)(user_id - 1); return ORBIS_OK; } @@ -1068,12 +1086,12 @@ int PS4_SYSV_ABI sceUserServiceGetUserGroupNum() { } s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::size_t size) { - LOG_DEBUG(Lib_UserService, "called user_id = {} ,size = {} ", user_id, size); + LOG_DEBUG(Lib_UserService, "called user_id = {}, size = {} ", user_id, size); if (user_name == nullptr) { LOG_ERROR(Lib_UserService, "user_name is null"); return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT; } - std::string name = Config::getUserName(); + std::string name = Config::getUserName(user_id - 1); if (size < name.length()) { LOG_ERROR(Lib_UserService, "buffer is too short"); return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT; diff --git a/src/core/libraries/system/userservice.h b/src/core/libraries/system/userservice.h index 30920e002..2a97e719c 100644 --- a/src/core/libraries/system/userservice.h +++ b/src/core/libraries/system/userservice.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later // reference : // https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/master/include/orbis/_types/user.h @@ -57,6 +57,8 @@ struct OrbisUserServiceEvent { OrbisUserServiceUserId userId; }; +void AddUserServiceEvent(const OrbisUserServiceEvent e); + int PS4_SYSV_ABI sceUserServiceInitializeForShellCore(); int PS4_SYSV_ABI sceUserServiceTerminateForShellCore(); int PS4_SYSV_ABI sceUserServiceDestroyUser(); diff --git a/src/emulator.cpp b/src/emulator.cpp index 3e62f6efb..21730cbec 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -259,7 +259,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, // Initialize components memory = Core::Memory::Instance(); - controller = Common::Singleton::Instance(); + controllers = Common::Singleton::Instance(); linker = Common::Singleton::Instance(); // Load renderdoc module @@ -301,7 +301,7 @@ void Emulator::Run(std::filesystem::path file, std::vector args, } } window = std::make_unique( - Config::getWindowWidth(), Config::getWindowHeight(), controller, window_title); + Config::getWindowWidth(), Config::getWindowHeight(), controllers, window_title); g_window = window.get(); diff --git a/src/emulator.h b/src/emulator.h index f4dd32c20..0db8ccb6c 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -43,7 +43,7 @@ private: void LoadSystemModules(const std::string& game_serial); Core::MemoryManager* memory; - Input::GameController* controller; + Input::GameControllers* controllers; Core::Linker* linker; std::unique_ptr window; std::chrono::steady_clock::time_point start_time; diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index fbca90efb..9be20a165 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later // Based on imgui_impl_sdl3.cpp from Dear ImGui repository @@ -737,9 +737,8 @@ static void UpdateGamepads() { ImGuiIO& io = ImGui::GetIO(); SdlData* bd = GetBackendData(); - auto controller = Common::Singleton::Instance(); - auto engine = controller->GetEngine(); - SDL_Gamepad* SDLGamepad = engine->m_gamepad; + auto controllers = *Common::Singleton::Instance(); + SDL_Gamepad* SDLGamepad = controllers[0]->m_sdl_gamepad; // Update list of gamepads to use if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) { if (SDLGamepad) { diff --git a/src/input/controller.cpp b/src/input/controller.cpp index 6657f4036..03740e571 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -1,66 +1,21 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include +#include #include "common/config.h" #include "common/logging/log.h" +#include "controller.h" #include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" +#include "core/libraries/system/userservice.h" #include "input/controller.h" static std::string SelectedGamepad = ""; namespace Input { -using Libraries::Pad::OrbisPadButtonDataOffset; - -void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) { - if (isPressed) { - buttonsState |= button; - } else { - buttonsState &= ~button; - } -} - -void State::OnAxis(Axis axis, int value) { - const auto toggle = [&](const auto button) { - if (value > 0) { - buttonsState |= button; - } else { - buttonsState &= ~button; - } - }; - switch (axis) { - case Axis::TriggerLeft: - toggle(OrbisPadButtonDataOffset::L2); - break; - case Axis::TriggerRight: - toggle(OrbisPadButtonDataOffset::R2); - break; - default: - break; - } - axes[static_cast(axis)] = value; -} - -void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) { - touchpad[touchIndex].state = isDown; - touchpad[touchIndex].x = static_cast(x * 1920); - touchpad[touchIndex].y = static_cast(y * 941); -} - -void State::OnGyro(const float gyro[3]) { - angularVelocity.x = gyro[0]; - angularVelocity.y = gyro[1]; - angularVelocity.z = gyro[2]; -} - -void State::OnAccel(const float accel[3]) { - acceleration.x = accel[0]; - acceleration.y = accel[1]; - acceleration.z = accel[2]; -} - GameController::GameController() { m_states_num = 0; m_last_state = State(); @@ -110,7 +65,8 @@ State GameController::GetLastState() const { return m_last_state; } const u32 last = (m_first_state + m_states_num - 1) % MAX_STATES; - return m_states[last]; + auto copy = m_states[last]; + return copy; } void GameController::AddState(const State& state) { @@ -126,22 +82,50 @@ void GameController::AddState(const State& state) { m_states_num++; } -void GameController::CheckButton(int id, OrbisPadButtonDataOffset button, bool is_pressed) { +void GameController::CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button, + bool is_pressed) { std::scoped_lock lock{m_mutex}; auto state = GetLastState(); - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnButton(button, is_pressed); + if (is_pressed) { + state.buttonsState |= button; + } else { + state.buttonsState &= ~button; + } AddState(state); } void GameController::Axis(int id, Input::Axis axis, int value) { + using Libraries::Pad::OrbisPadButtonDataOffset; + std::scoped_lock lock{m_mutex}; auto state = GetLastState(); state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnAxis(axis, value); + int axis_id = static_cast(axis); + if (std::abs(state.axes[axis_id] - value) > 120) { + LOG_DEBUG(Input, "Keyboard axis change detected"); + axis_smoothing_ticks[axis_id] = GameController::max_smoothing_ticks; + axis_smoothing_values[axis_id] = state.axes[axis_id]; + } + state.axes[axis_id] = value; + + if (axis == Input::Axis::TriggerLeft) { + if (value > 0) { + state.buttonsState |= OrbisPadButtonDataOffset::L2; + } else { + state.buttonsState &= ~OrbisPadButtonDataOffset::L2; + } + } + + if (axis == Input::Axis::TriggerRight) { + if (value > 0) { + state.buttonsState |= OrbisPadButtonDataOffset::R2; + } else { + state.buttonsState &= ~OrbisPadButtonDataOffset::R2; + } + } AddState(state); } @@ -152,7 +136,9 @@ void GameController::Gyro(int id, const float gyro[3]) { state.time = Libraries::Kernel::sceKernelGetProcessTime(); // Update the angular velocity (gyro data) - state.OnGyro(gyro); + state.angularVelocity.x = gyro[0]; // X-axis + state.angularVelocity.y = gyro[1]; // Y-axis + state.angularVelocity.z = gyro[2]; // Z-axis AddState(state); } @@ -162,7 +148,9 @@ void GameController::Acceleration(int id, const float acceleration[3]) { state.time = Libraries::Kernel::sceKernelGetProcessTime(); // Update the acceleration values - state.OnAccel(acceleration); + state.acceleration.x = acceleration[0]; // X-axis + state.acceleration.y = acceleration[1]; // Y-axis + state.acceleration.z = acceleration[2]; // Z-axis AddState(state); } @@ -203,33 +191,116 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler } void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) { - if (!m_engine) { - return; + if (m_sdl_gamepad != nullptr) { + SDL_SetGamepadLED(m_sdl_gamepad, r, g, b); } - std::scoped_lock _{m_mutex}; - m_engine->SetLightBarRGB(r, g, b); } -void GameController::SetVibration(u8 smallMotor, u8 largeMotor) { - if (!m_engine) { - return; +bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) { + if (m_sdl_gamepad != nullptr) { + return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF, + (largeMotor / 255.0f) * 0xFFFF, -1); } - std::scoped_lock _{m_mutex}; - m_engine->SetVibration(smallMotor, largeMotor); + return true; } void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) { if (touchIndex < 2) { std::scoped_lock lock{m_mutex}; auto state = GetLastState(); - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.OnTouchpad(touchIndex, touchDown, x, y); + + state.touchpad[touchIndex].state = touchDown; + state.touchpad[touchIndex].x = static_cast(x * 1920); + state.touchpad[touchIndex].y = static_cast(y * 941); AddState(state); } } +bool is_first_check = true; + +void GameControllers::TryOpenSDLControllers(GameControllers& controllers) { + using namespace Libraries::UserService; + int controller_count; + SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count); + + std::unordered_set assigned_ids; + std::array slot_taken{false, false, false, false}; + + for (int i = 0; i < 4; i++) { + SDL_Gamepad* pad = controllers[i]->m_sdl_gamepad; + if (pad) { + SDL_JoystickID id = SDL_GetGamepadID(pad); + bool still_connected = false; + for (int j = 0; j < controller_count; j++) { + if (new_joysticks[j] == id) { + still_connected = true; + assigned_ids.insert(id); + slot_taken[i] = true; + break; + } + } + if (!still_connected) { + AddUserServiceEvent({OrbisUserServiceEventType::Logout, i + 1}); + SDL_CloseGamepad(pad); + controllers[i]->m_sdl_gamepad = nullptr; + controllers[i]->user_id = -1; + slot_taken[i] = false; + } else { + controllers[i]->player_index = i; + } + } + } + + for (int j = 0; j < controller_count; j++) { + SDL_JoystickID id = new_joysticks[j]; + if (assigned_ids.contains(id)) + continue; + + SDL_Gamepad* pad = SDL_OpenGamepad(id); + if (!pad) + continue; + + for (int i = 0; i < 4; i++) { + if (!slot_taken[i]) { + auto* c = controllers[i]; + c->m_sdl_gamepad = pad; + LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i, + SDL_GetGamepadID(pad)); + c->user_id = i + 1; + slot_taken[i] = true; + c->player_index = i; + AddUserServiceEvent({OrbisUserServiceEventType::Login, i + 1}); + + if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_GYRO, true)) { + c->gyro_poll_rate = + SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_GYRO); + LOG_INFO(Input, "Gyro initialized, poll rate: {}", c->gyro_poll_rate); + } else { + LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad {}", + c->user_id); + } + if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) { + c->accel_poll_rate = + SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_ACCEL); + LOG_INFO(Input, "Accel initialized, poll rate: {}", c->accel_poll_rate); + } else { + LOG_ERROR(Input, "Failed to initialize accel controls for gamepad {}", + c->user_id); + } + break; + } + } + } + if (is_first_check) [[unlikely]] { + is_first_check = false; + if (controller_count == 0) { + controllers[0]->user_id = 1; + AddUserServiceEvent({OrbisUserServiceEventType::Login, 1}); + } + } +} u8 GameController::GetTouchCount() { std::scoped_lock lock{m_mutex}; return m_touch_count; @@ -293,21 +364,9 @@ void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpd m_last_update = lastUpdate; } -void GameController::SetEngine(std::unique_ptr engine) { - std::scoped_lock _{m_mutex}; - m_engine = std::move(engine); - if (m_engine) { - m_engine->Init(); - } -} - -Engine* GameController::GetEngine() { - return m_engine.get(); -} - u32 GameController::Poll() { + std::scoped_lock lock{m_mutex}; if (m_connected) { - std::scoped_lock lock{m_mutex}; auto time = Libraries::Kernel::sceKernelGetProcessTime(); if (m_states_num == 0) { auto diff = (time - m_last_state.time) / 1000; @@ -325,6 +384,12 @@ u32 GameController::Poll() { return 100; } +u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) { + s32 index = SDL_GetGamepadPlayerIndex(SDL_GetGamepadFromID(id)); + LOG_TRACE(Input, "Gamepad index: {}", index); + return index; +} + } // namespace Input namespace GamepadSelect { @@ -343,6 +408,12 @@ int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) { return -1; } +std::optional GetControllerIndexFromUserID(s32 user_id) { + if (user_id < 1 || user_id > 4) + return std::nullopt; + return static_cast(user_id - 1); +} + int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) { char GUIDbuf[33]; for (int i = 0; i < gamepadCount; i++) { diff --git a/src/input/controller.h b/src/input/controller.h index dfde521be..a843db04c 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -1,15 +1,17 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include -#include #include #include +#include "SDL3/SDL_joystick.h" +#include "common/assert.h" #include "common/types.h" #include "core/libraries/pad/pad.h" +struct SDL_Gamepad; + namespace Input { enum class Axis { @@ -30,14 +32,7 @@ struct TouchpadEntry { u16 y{}; }; -class State { -public: - void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool); - void OnAxis(Axis, int); - void OnTouchpad(int touchIndex, bool isDown, float x, float y); - void OnGyro(const float[3]); - void OnAccel(const float[3]); - +struct State { Libraries::Pad::OrbisPadButtonDataOffset buttonsState{}; u64 time = 0; int axes[static_cast(Axis::AxisMax)] = {128, 128, 128, 128, 0, 0}; @@ -47,25 +42,16 @@ public: Libraries::Pad::OrbisFQuaternion orientation = {0.0f, 0.0f, 0.0f, 1.0f}; }; -class Engine { -public: - virtual ~Engine() = default; - virtual void Init() = 0; - virtual void SetLightBarRGB(u8 r, u8 g, u8 b) = 0; - virtual void SetVibration(u8 smallMotor, u8 largeMotor) = 0; - virtual State ReadState() = 0; - virtual float GetAccelPollRate() const = 0; - virtual float GetGyroPollRate() const = 0; - SDL_Gamepad* m_gamepad; -}; - inline int GetAxis(int min, int max, int value) { - return std::clamp((255 * (value - min)) / (max - min), 0, 255); + int v = (255 * (value - min)) / (max - min); + return (v < 0 ? 0 : (v > 255 ? 255 : v)); } constexpr u32 MAX_STATES = 32; class GameController { + friend class GameControllers; + public: GameController(); virtual ~GameController() = default; @@ -79,10 +65,8 @@ public: void Gyro(int id, const float gyro[3]); void Acceleration(int id, const float acceleration[3]); void SetLightBarRGB(u8 r, u8 g, u8 b); - void SetVibration(u8 smallMotor, u8 largeMotor); + bool SetVibration(u8 smallMotor, u8 largeMotor); void SetTouchpadState(int touchIndex, bool touchDown, float x, float y); - void SetEngine(std::unique_ptr); - Engine* GetEngine(); u32 Poll(); u8 GetTouchCount(); @@ -104,6 +88,14 @@ public: Libraries::Pad::OrbisFQuaternion& lastOrientation, Libraries::Pad::OrbisFQuaternion& orientation); + float gyro_poll_rate; + float accel_poll_rate; + u32 user_id = -1; // ORBIS_USER_SERVICE_USER_ID_INVALID + SDL_Gamepad* m_sdl_gamepad = nullptr; + static constexpr int max_smoothing_ticks = 2; + int axis_smoothing_ticks[static_cast(Input::Axis::AxisMax)]{0}; + int axis_smoothing_values[static_cast(Input::Axis::AxisMax)]{0}; + private: struct StateInternal { bool obtained = false; @@ -125,13 +117,32 @@ private: std::chrono::steady_clock::time_point m_last_update = {}; Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f}; - std::unique_ptr m_engine = nullptr; + u8 player_index = -1; +}; + +class GameControllers { + std::array controllers; + +public: + GameControllers() + : controllers({new GameController(), new GameController(), new GameController(), + new GameController()}) {}; + virtual ~GameControllers() = default; + GameController* operator[](const size_t& i) const { + if (i > 3) { + UNREACHABLE_MSG("Index {} is out of bounds for GameControllers!", i); + } + return controllers[i]; + } + static void TryOpenSDLControllers(GameControllers& controllers); + static u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id); }; } // namespace Input namespace GamepadSelect { +std::optional GetControllerIndexFromUserID(s32 user_id); int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID); std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index); std::string GetSelectedGamepad(); diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index d38b45ddd..b3e0ca204 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "input_handler.h" @@ -22,6 +22,7 @@ #include "common/elf_info.h" #include "common/io_file.h" #include "common/path_util.h" +#include "common/singleton.h" #include "input/controller.h" #include "input/input_mouse.h" @@ -61,54 +62,11 @@ std::list> pressed_keys; std::list toggled_keys; static std::vector connections; -auto output_array = std::array{ - // Important: these have to be the first, or else they will update in the wrong order - ControllerOutput(LEFTJOYSTICK_HALFMODE), - ControllerOutput(RIGHTJOYSTICK_HALFMODE), - ControllerOutput(KEY_TOGGLE), - ControllerOutput(MOUSE_GYRO_ROLL_MODE), - - // Button mappings - ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle - ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle - ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross - ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square - ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1 - ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3 - ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1 - ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3 - ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options - ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad - ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad - ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right - - // Axis mappings - // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false), - // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false), - // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false), - // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY), - - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER), - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER), - - ControllerOutput(HOTKEY_FULLSCREEN), - ControllerOutput(HOTKEY_PAUSE), - ControllerOutput(HOTKEY_SIMPLE_FPS), - ControllerOutput(HOTKEY_QUIT), - ControllerOutput(HOTKEY_RELOAD_INPUTS), - ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK), - ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO), - ControllerOutput(HOTKEY_RENDERDOC), - - ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID), +std::array output_arrays = { + ControllerAllOutputs(0), + ControllerAllOutputs(1), + ControllerAllOutputs(2), + ControllerAllOutputs(3), }; void ControllerOutput::LinkJoystickAxes() { @@ -215,6 +173,14 @@ InputBinding GetBindingFromString(std::string& line) { return InputBinding(keys[0], keys[1], keys[2]); } +std::optional parseInt(const std::string& s) { + try { + return std::stoi(s); + } catch (...) { + return std::nullopt; + } +}; + void ParseInputConfig(const std::string game_id = "") { std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id; const auto config_file = Config::GetFoolproofInputConfigFile(game_id_or_default); @@ -270,21 +236,39 @@ void ParseInputConfig(const std::string game_id = "") { std::string output_string = line.substr(0, equal_pos); std::string input_string = line.substr(equal_pos + 1); - // Remove trailing semicolon from input_string - if (!input_string.empty() && input_string[input_string.length() - 1] == ';' && - input_string != ";") { - line = line.substr(0, line.length() - 1); + s8 input_gamepad_id = -1, output_gamepad_id = -1; // -1 means it's not specified + + // todo: here the inputs and outputs are formatted and split, we need to extract the + // controller ID now + + // input gamepad id is only for controllers, it's discarded otherwise + std::size_t input_colon_pos = input_string.find(':'); + if (input_colon_pos != std::string::npos) { + auto temp = parseInt(input_string.substr(input_colon_pos + 1)); + if (!temp) { + LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line); + } else { + input_gamepad_id = *temp; + } + input_string = input_string.substr(0, input_colon_pos); + } + + // if not provided, assume it's for all gamepads, if the input is a controller and that also + // doesn't have an ID, and for the first otherwise + std::size_t output_colon_pos = output_string.find(':'); + if (output_colon_pos != std::string::npos) { + auto temp = parseInt(output_string.substr(output_colon_pos + 1)); + if (!temp) { + LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line); + } else { + output_gamepad_id = *temp; + } + output_string = output_string.substr(0, output_colon_pos); } std::size_t comma_pos = input_string.find(','); - auto parseInt = [](const std::string& s) -> std::optional { - try { - return std::stoi(s); - } catch (...) { - return std::nullopt; - } - }; + // todo: make remapping work for special bindings for gamepads that are not the first if (output_string == "mouse_to_joystick") { if (input_string == "left") { SetMouseToJoystick(1); @@ -307,7 +291,7 @@ void ParseInputConfig(const std::string game_id = "") { return; } ControllerOutput* toggle_out = - &*std::ranges::find(output_array, ControllerOutput(KEY_TOGGLE)); + &*std::ranges::find(output_arrays[0].data, ControllerOutput(KEY_TOGGLE)); BindingConnection toggle_connection = BindingConnection( InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]); connections.insert(connections.end(), toggle_connection); @@ -400,40 +384,67 @@ void ParseInputConfig(const std::string game_id = "") { return; } if (button_it != string_to_cbutton_map.end()) { + // todo add new shit here connection = BindingConnection( - binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second))); - connections.insert(connections.end(), connection); + binding, + &*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data, + ControllerOutput(button_it->second))); } else if (axis_it != string_to_axis_map.end()) { + // todo add new shit here int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value; connection = BindingConnection( binding, - &*std::ranges::find(output_array, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, - axis_it->second.axis, - axis_it->second.value >= 0)), + &*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data, + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, + axis_it->second.axis, + axis_it->second.value >= 0)), value_to_set); - connections.insert(connections.end(), connection); } else { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); return; } + // todo make the following: if the input binding contains a controller input, and gamepad ID + // isn't specified for either inputs or output (both are -1), then multiply the binding and + // add it to all 4 controllers + if (connection.HasGamepadInput() && input_gamepad_id == -1 && output_gamepad_id == -1) { + for (int i = 0; i < 4; i++) { + BindingConnection copy = connection.CopyWithChangedGamepadId(i + 1); + copy.output = &*std::ranges::find(output_arrays[i].data, *connection.output); + connections.push_back(copy); + } + } else { + connections.push_back(connection); + } LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount); }; while (std::getline(global_config_stream, line)) { ProcessLine(); } + lineCount = 0; while (std::getline(config_stream, line)) { ProcessLine(); } config_stream.close(); std::sort(connections.begin(), connections.end()); for (auto& c : connections) { + // todo add new shit here LOG_DEBUG(Input, "Binding: {} : {}", c.output->ToString(), c.binding.ToString()); } LOG_DEBUG(Input, "Done parsing the input config!"); } +BindingConnection BindingConnection::CopyWithChangedGamepadId(u8 gamepad) { + BindingConnection copy = *this; + for (auto& key : copy.binding.keys) { + if (key.type == InputType::Controller || key.type == InputType::Axis) { + key.gamepad_id = gamepad; + } + } + return copy; +} + u32 GetMouseWheelEvent(const SDL_Event& event) { if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) { LOG_WARNING(Input, "Something went wrong with wheel input parsing!"); @@ -452,6 +463,8 @@ u32 GetMouseWheelEvent(const SDL_Event& event) { } InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { + // todo add new shit here + u8 gamepad = 1; switch (e.type) { case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: @@ -466,20 +479,19 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { e.type == SDL_EVENT_MOUSE_WHEEL, 0); case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: - return InputEvent(InputType::Controller, static_cast(e.gbutton.button), e.gbutton.down, - 0); // clang made me do it + gamepad = GameControllers::GetGamepadIndexFromJoystickId(e.gbutton.which) + 1; + return InputEvent({InputType::Controller, (u32)e.gbutton.button, gamepad}, e.gbutton.down, + 0); case SDL_EVENT_GAMEPAD_AXIS_MOTION: - return InputEvent(InputType::Axis, static_cast(e.gaxis.axis), true, - e.gaxis.value / 256); // this too + gamepad = GameControllers::GetGamepadIndexFromJoystickId(e.gaxis.which) + 1; + return InputEvent({InputType::Axis, (u32)e.gaxis.axis, gamepad}, true, e.gaxis.value / 256); default: return InputEvent(); } } -GameController* ControllerOutput::controller = nullptr; -void ControllerOutput::SetControllerOutputController(GameController* c) { - ControllerOutput::controller = c; -} +GameControllers ControllerOutput::controllers = + *Common::Singleton::Instance(); void ToggleKeyInList(InputID input) { if (input.type == InputType::Axis) { @@ -526,7 +538,7 @@ void ControllerOutput::AddUpdate(InputEvent event) { *new_param = (event.active ? event.axis_value : 0) + *new_param; } } -void ControllerOutput::FinalizeUpdate() { +void ControllerOutput::FinalizeUpdate(u8 gamepad_index) { auto PushSDLEvent = [&](u32 event_type) { if (new_button_state) { SDL_Event e; @@ -542,6 +554,7 @@ void ControllerOutput::FinalizeUpdate() { old_button_state = new_button_state; old_param = *new_param; if (button != SDL_GAMEPAD_BUTTON_INVALID) { + auto controller = controllers[gamepad_index]; switch (button) { case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f); @@ -582,6 +595,12 @@ void ControllerOutput::FinalizeUpdate() { case HOTKEY_RENDERDOC: PushSDLEvent(SDL_EVENT_RDOC_CAPTURE); break; + case HOTKEY_ADD_VIRTUAL_USER: + PushSDLEvent(SDL_EVENT_ADD_VIRTUAL_USER); + break; + case HOTKEY_REMOVE_VIRTUAL_USER: + PushSDLEvent(SDL_EVENT_REMOVE_VIRTUAL_USER); + break; case HOTKEY_QUIT: PushSDLEvent(SDL_EVENT_QUIT_DIALOG); break; @@ -622,18 +641,20 @@ void ControllerOutput::FinalizeUpdate() { break; case Axis::TriggerLeft: ApplyDeadzone(new_param, lefttrigger_deadzone); - controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); - controller->CheckButton(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20); + controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); + controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::L2, + *new_param > 0x20); return; case Axis::TriggerRight: ApplyDeadzone(new_param, righttrigger_deadzone); - controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); - controller->CheckButton(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20); + controllers[gamepad_index]->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param)); + controllers[gamepad_index]->CheckButton(0, OrbisPadButtonDataOffset::R2, + *new_param > 0x20); return; default: break; } - controller->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier)); + controllers[gamepad_index]->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier)); } } @@ -765,22 +786,30 @@ InputEvent BindingConnection::ProcessBinding() { } void ActivateOutputsFromInputs() { - // Reset values and flags - for (auto& it : pressed_keys) { - it.second = false; - } - for (auto& it : output_array) { - it.ResetUpdate(); - } - // Iterate over all inputs, and update their respecive outputs accordingly - for (auto& it : connections) { - it.output->AddUpdate(it.ProcessBinding()); - } + // todo find a better solution + for (int i = 0; i < 4; i++) { - // Update all outputs - for (auto& it : output_array) { - it.FinalizeUpdate(); + // Reset values and flags + for (auto& it : pressed_keys) { + it.second = false; + } + for (auto& it : output_arrays[i].data) { + it.ResetUpdate(); + } + + // Iterate over all inputs, and update their respecive outputs accordingly + for (auto& it : connections) { + // only update this when it's the correct pass + if (it.output->gamepad_id == i) { + it.output->AddUpdate(it.ProcessBinding()); + } + } + + // Update all outputs + for (auto& it : output_arrays[i].data) { + it.FinalizeUpdate(i); + } } } diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 0d95d1c4a..b63b65e92 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -7,6 +7,7 @@ #include #include #include +#include #include "SDL3/SDL_events.h" #include "SDL3/SDL_timer.h" @@ -37,6 +38,8 @@ #define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 8 #define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9 #define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10 +#define SDL_EVENT_ADD_VIRTUAL_USER SDL_EVENT_USER + 11 +#define SDL_EVENT_REMOVE_VIRTUAL_USER SDL_EVENT_USER + 12 #define LEFTJOYSTICK_HALFMODE 0x00010000 #define RIGHTJOYSTICK_HALFMODE 0x00020000 @@ -53,6 +56,8 @@ #define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006 #define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007 #define HOTKEY_RENDERDOC 0xf0000008 +#define HOTKEY_ADD_VIRTUAL_USER 0xf0000009 +#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000a #define SDL_UNMAPPED UINT32_MAX - 1 @@ -73,21 +78,24 @@ class InputID { public: InputType type; u32 sdl_id; - InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : type(d), sdl_id(i) {} + u8 gamepad_id; + InputID(InputType d = InputType::Count, u32 i = (u32)-1, u8 g = 1) + : type(d), sdl_id(i), gamepad_id(g) {} bool operator==(const InputID& o) const { - return type == o.type && sdl_id == o.sdl_id; + return type == o.type && sdl_id == o.sdl_id && gamepad_id == o.gamepad_id; } bool operator!=(const InputID& o) const { - return type != o.type || sdl_id != o.sdl_id; + return type != o.type || sdl_id != o.sdl_id || gamepad_id != o.gamepad_id; } bool operator<=(const InputID& o) const { return type <= o.type && sdl_id <= o.sdl_id; + return std::tie(gamepad_id, type, sdl_id) <= std::tie(o.gamepad_id, o.type, o.sdl_id); } bool IsValid() const { return *this != InputID(); } std::string ToString() { - return fmt::format("({}: {:x})", input_type_names[static_cast(type)], sdl_id); + return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id); } }; @@ -142,6 +150,8 @@ const std::map string_to_cbutton_map = { {"hotkey_toggle_mouse_to_joystick", HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK}, {"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO}, {"hotkey_renderdoc_capture", HOTKEY_RENDERDOC}, + {"hotkey_add_virtual_user", HOTKEY_ADD_VIRTUAL_USER}, + {"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER}, }; const std::map string_to_axis_map = { @@ -392,7 +402,7 @@ public: inline bool IsEmpty() { return !(keys[0].IsValid() || keys[1].IsValid() || keys[2].IsValid()); } - std::string ToString() { // todo add device type + std::string ToString() { switch (KeyCount()) { case 1: return fmt::format("({})", keys[0].ToString()); @@ -411,14 +421,15 @@ public: }; class ControllerOutput { - static GameController* controller; + static GameControllers controllers; public: - static void SetControllerOutputController(GameController* c); + static void GetGetGamepadIndexFromSDLJoystickID(const SDL_JoystickID id) {} static void LinkJoystickAxes(); u32 button; u32 axis; + u8 gamepad_id; // these are only used as s8, // but I added some padding to avoid overflow if it's activated by multiple inputs // axis_plus and axis_minus pairs share a common new_param, the other outputs have their own @@ -432,6 +443,7 @@ public: new_param = new s16(0); old_param = 0; positive_axis = p; + gamepad_id = 0; } ControllerOutput(const ControllerOutput& o) : button(o.button), axis(o.axis) { new_param = new s16(*o.new_param); @@ -457,7 +469,7 @@ public: void ResetUpdate(); void AddUpdate(InputEvent event); - void FinalizeUpdate(); + void FinalizeUpdate(u8 gamepad_index); }; class BindingConnection { public: @@ -472,6 +484,13 @@ public: output = out; toggle = t; } + BindingConnection& operator=(const BindingConnection& o) { + binding = o.binding; + output = o.output; + axis_param = o.axis_param; + toggle = o.toggle; + return *this; + } bool operator<(const BindingConnection& other) const { // a button is a higher priority than an axis, as buttons can influence axes // (e.g. joystick_halfmode) @@ -485,9 +504,79 @@ public: } return false; } + bool HasGamepadInput() { + for (auto& key : binding.keys) { + if (key.type == InputType::Controller || key.type == InputType::Axis) { + return true; + } + } + return false; + } + BindingConnection CopyWithChangedGamepadId(u8 gamepad); InputEvent ProcessBinding(); }; +class ControllerAllOutputs { +public: + static constexpr u64 output_count = 37; + std::array data = { + // Important: these have to be the first, or else they will update in the wrong order + ControllerOutput(LEFTJOYSTICK_HALFMODE), + ControllerOutput(RIGHTJOYSTICK_HALFMODE), + ControllerOutput(KEY_TOGGLE), + ControllerOutput(MOUSE_GYRO_ROLL_MODE), + + // Button mappings + ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle + ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle + ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross + ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square + ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1 + ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3 + ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1 + ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3 + ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right + + // Axis mappings + // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false), + // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false), + // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false), + // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY), + + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER), + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER), + + ControllerOutput(HOTKEY_FULLSCREEN), + ControllerOutput(HOTKEY_PAUSE), + ControllerOutput(HOTKEY_SIMPLE_FPS), + ControllerOutput(HOTKEY_QUIT), + ControllerOutput(HOTKEY_RELOAD_INPUTS), + ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK), + ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO), + ControllerOutput(HOTKEY_RENDERDOC), + ControllerOutput(HOTKEY_ADD_VIRTUAL_USER), + ControllerOutput(HOTKEY_REMOVE_VIRTUAL_USER), + + ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID), + }; + ControllerAllOutputs(u8 g) { + for (int i = 0; i < output_count; i++) { + data[i].gamepad_id = g; + } + } +}; + // Updates the list of pressed keys with the given input. // Returns whether the list was updated or not. bool UpdatePressedKeys(InputEvent event); diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 3959588c8..268c06aa7 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -15,6 +15,7 @@ #include "core/emulator_settings.h" #include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" +#include "core/libraries/system/userservice.h" #include "imgui/renderer/imgui_core.h" #include "input/controller.h" #include "input/input_handler.h" @@ -26,258 +27,55 @@ #include "SDL3/SDL_metal.h" #endif -namespace Input { - -using Libraries::Pad::OrbisPadButtonDataOffset; - -static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) { - using OPBDO = OrbisPadButtonDataOffset; - - switch (button) { - case SDL_GAMEPAD_BUTTON_DPAD_DOWN: - return OPBDO::Down; - case SDL_GAMEPAD_BUTTON_DPAD_UP: - return OPBDO::Up; - case SDL_GAMEPAD_BUTTON_DPAD_LEFT: - return OPBDO::Left; - case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: - return OPBDO::Right; - case SDL_GAMEPAD_BUTTON_SOUTH: - return OPBDO::Cross; - case SDL_GAMEPAD_BUTTON_NORTH: - return OPBDO::Triangle; - case SDL_GAMEPAD_BUTTON_WEST: - return OPBDO::Square; - case SDL_GAMEPAD_BUTTON_EAST: - return OPBDO::Circle; - case SDL_GAMEPAD_BUTTON_START: - return OPBDO::Options; - case SDL_GAMEPAD_BUTTON_TOUCHPAD: - return OPBDO::TouchPad; - case SDL_GAMEPAD_BUTTON_BACK: - return OPBDO::TouchPad; - case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: - return OPBDO::L1; - case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: - return OPBDO::R1; - case SDL_GAMEPAD_BUTTON_LEFT_STICK: - return OPBDO::L3; - case SDL_GAMEPAD_BUTTON_RIGHT_STICK: - return OPBDO::R3; - default: - return OPBDO::None; - } -} - -static SDL_GamepadAxis InputAxisToSDL(Axis axis) { - switch (axis) { - case Axis::LeftX: - return SDL_GAMEPAD_AXIS_LEFTX; - case Axis::LeftY: - return SDL_GAMEPAD_AXIS_LEFTY; - case Axis::RightX: - return SDL_GAMEPAD_AXIS_RIGHTX; - case Axis::RightY: - return SDL_GAMEPAD_AXIS_RIGHTY; - case Axis::TriggerLeft: - return SDL_GAMEPAD_AXIS_LEFT_TRIGGER; - case Axis::TriggerRight: - return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; - default: - UNREACHABLE(); - } -} - -SDLInputEngine::~SDLInputEngine() { - if (m_gamepad) { - SDL_CloseGamepad(m_gamepad); - } -} - -void SDLInputEngine::Init() { - if (m_gamepad) { - SDL_CloseGamepad(m_gamepad); - m_gamepad = nullptr; - } - - int gamepad_count; - SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count); - if (!gamepads) { - LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); - return; - } - if (gamepad_count == 0) { - LOG_INFO(Input, "No gamepad found!"); - SDL_free(gamepads); - return; - } - - int selectedIndex = GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, - GamepadSelect::GetSelectedGamepad()); - int defaultIndex = - GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, Config::getDefaultControllerID()); - - // If user selects a gamepad in the GUI, use that, otherwise try the default - if (!m_gamepad) { - if (selectedIndex != -1) { - m_gamepad = SDL_OpenGamepad(gamepads[selectedIndex]); - LOG_INFO(Input, "Opening gamepad selected in GUI."); - } else if (defaultIndex != -1) { - m_gamepad = SDL_OpenGamepad(gamepads[defaultIndex]); - LOG_INFO(Input, "Opening default gamepad."); - } else { - m_gamepad = SDL_OpenGamepad(gamepads[0]); - LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count); - } - } - - if (!m_gamepad) { - if (!m_gamepad) { - LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError()); - SDL_free(gamepads); - return; - } - } - - SDL_Joystick* joystick = SDL_GetGamepadJoystick(m_gamepad); - Uint16 vendor = SDL_GetJoystickVendor(joystick); - Uint16 product = SDL_GetJoystickProduct(joystick); - - bool isDualSense = (vendor == 0x054C && product == 0x0CE6); - - LOG_INFO(Input, "Gamepad Vendor: {:04X}, Product: {:04X}", vendor, product); - if (isDualSense) { - LOG_INFO(Input, "Detected DualSense Controller"); - } - - if (Config::getIsMotionControlsEnabled()) { - if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) { - m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO); - LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate); - } else { - LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad, error: {}", - SDL_GetError()); - SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, false); - } - if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, true)) { - m_accel_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_ACCEL); - LOG_INFO(Input, "Accel initialized, poll rate: {}", m_accel_poll_rate); - } else { - LOG_ERROR(Input, "Failed to initialize accel controls for gamepad, error: {}", - SDL_GetError()); - SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, false); - } - } - - SDL_free(gamepads); - - int* rgb = Config::GetControllerCustomColor(); - - if (isDualSense) { - if (SDL_SetJoystickLED(joystick, rgb[0], rgb[1], rgb[2]) == 0) { - LOG_INFO(Input, "Set DualSense LED to R:{} G:{} B:{}", rgb[0], rgb[1], rgb[2]); - } else { - LOG_ERROR(Input, "Failed to set DualSense LED: {}", SDL_GetError()); - } - } else { - SetLightBarRGB(rgb[0], rgb[1], rgb[2]); - } -} - -void SDLInputEngine::SetLightBarRGB(u8 r, u8 g, u8 b) { - if (m_gamepad) { - SDL_SetGamepadLED(m_gamepad, r, g, b); - } -} - -void SDLInputEngine::SetVibration(u8 smallMotor, u8 largeMotor) { - if (m_gamepad) { - const auto low_freq = (smallMotor / 255.0f) * 0xFFFF; - const auto high_freq = (largeMotor / 255.0f) * 0xFFFF; - SDL_RumbleGamepad(m_gamepad, low_freq, high_freq, -1); - } -} - -State SDLInputEngine::ReadState() { - State state{}; - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - - // Buttons - for (u8 i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) { - auto orbisButton = SDLGamepadToOrbisButton(i); - if (orbisButton == OrbisPadButtonDataOffset::None) { - continue; - } - state.OnButton(orbisButton, SDL_GetGamepadButton(m_gamepad, (SDL_GamepadButton)i)); - } - - // Axes - for (int i = 0; i < static_cast(Axis::AxisMax); ++i) { - const auto axis = static_cast(i); - const auto value = SDL_GetGamepadAxis(m_gamepad, InputAxisToSDL(axis)); - switch (axis) { - case Axis::TriggerLeft: - case Axis::TriggerRight: - state.OnAxis(axis, GetAxis(0, 0x8000, value)); - break; - default: - state.OnAxis(axis, GetAxis(-0x8000, 0x8000, value)); - break; - } - } - - // Touchpad - if (SDL_GetNumGamepadTouchpads(m_gamepad) > 0) { - for (int finger = 0; finger < 2; ++finger) { - bool down; - float x, y; - if (SDL_GetGamepadTouchpadFinger(m_gamepad, 0, finger, &down, &x, &y, NULL)) { - state.OnTouchpad(finger, down, x, y); - } - } - } - - // Gyro - if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_GYRO)) { - float gyro[3]; - if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_GYRO, gyro, 3)) { - state.OnGyro(gyro); - } - } - - // Accel - if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_ACCEL)) { - float accel[3]; - if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_ACCEL, accel, 3)) { - state.OnAccel(accel); - } - } - - return state; -} - -float SDLInputEngine::GetGyroPollRate() const { - return m_gyro_poll_rate; -} - -float SDLInputEngine::GetAccelPollRate() const { - return m_accel_poll_rate; -} - -} // namespace Input - namespace Frontend { using namespace Libraries::Pad; +static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) { + switch (button) { + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + return OrbisPadButtonDataOffset::Down; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + return OrbisPadButtonDataOffset::Up; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + return OrbisPadButtonDataOffset::Left; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + return OrbisPadButtonDataOffset::Right; + case SDL_GAMEPAD_BUTTON_SOUTH: + return OrbisPadButtonDataOffset::Cross; + case SDL_GAMEPAD_BUTTON_NORTH: + return OrbisPadButtonDataOffset::Triangle; + case SDL_GAMEPAD_BUTTON_WEST: + return OrbisPadButtonDataOffset::Square; + case SDL_GAMEPAD_BUTTON_EAST: + return OrbisPadButtonDataOffset::Circle; + case SDL_GAMEPAD_BUTTON_START: + return OrbisPadButtonDataOffset::Options; + case SDL_GAMEPAD_BUTTON_TOUCHPAD: + return OrbisPadButtonDataOffset::TouchPad; + case SDL_GAMEPAD_BUTTON_BACK: + return OrbisPadButtonDataOffset::TouchPad; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + return OrbisPadButtonDataOffset::L1; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + return OrbisPadButtonDataOffset::R1; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + return OrbisPadButtonDataOffset::L3; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + return OrbisPadButtonDataOffset::R3; + default: + return OrbisPadButtonDataOffset::None; + } +} + static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) { auto* controller = reinterpret_cast(userdata); return controller->Poll(); } -WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_, +WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controllers_, std::string_view window_title) - : width{width_}, height{height_}, controller{controller_} { + : width{width_}, height{height_}, controllers{*controllers_} { if (!SDL_SetHint(SDL_HINT_APP_NAME, "shadPS4")) { UNREACHABLE_MSG("Failed to set SDL window hint: {}", SDL_GetError()); } @@ -323,7 +121,6 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ SDL_SetWindowFullscreen(window, EmulatorSettings::GetInstance()->IsFullScreen()); SDL_InitSubSystem(SDL_INIT_GAMEPAD); - controller->SetEngine(std::make_unique()); #if defined(SDL_PLATFORM_WIN32) window_info.type = WindowSystemType::Windows; @@ -348,9 +145,9 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window)); #endif // input handler init-s - Input::ControllerOutput::SetControllerOutputController(controller); Input::ControllerOutput::LinkJoystickAxes(); Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); + Input::GameControllers::TryOpenSDLControllers(controllers); if (Config::getBackgroundControllerInput()) { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); @@ -392,34 +189,32 @@ void WindowSDL::WaitEvent() { break; case SDL_EVENT_GAMEPAD_ADDED: case SDL_EVENT_GAMEPAD_REMOVED: - controller->SetEngine(std::make_unique()); - break; - case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: - case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: - case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: - controller->SetTouchpadState(event.gtouchpad.finger, - event.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event.gtouchpad.x, - event.gtouchpad.y); + // todo handle userserviceevents here + Input::GameControllers::TryOpenSDLControllers(controllers); break; case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: case SDL_EVENT_GAMEPAD_AXIS_MOTION: + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: OnGamepadEvent(&event); break; - // i really would have appreciated ANY KIND OF DOCUMENTATION ON THIS - // AND IT DOESN'T EVEN USE PROPER ENUMS - case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: { + int controller_id = + Input::GameControllers::GetGamepadIndexFromJoystickId(event.gsensor.which); switch ((SDL_SensorType)event.gsensor.sensor) { case SDL_SENSOR_GYRO: - controller->Gyro(0, event.gsensor.data); + controllers[controller_id]->Gyro(0, event.gsensor.data); break; case SDL_SENSOR_ACCEL: - controller->Acceleration(0, event.gsensor.data); + controllers[controller_id]->Acceleration(0, event.gsensor.data); break; default: break; } break; + } case SDL_EVENT_QUIT: is_open = false; break; @@ -444,7 +239,7 @@ void WindowSDL::WaitEvent() { } break; case SDL_EVENT_CHANGE_CONTROLLER: - controller->GetEngine()->Init(); + UNREACHABLE_MSG("todo"); break; case SDL_EVENT_TOGGLE_SIMPLE_FPS: Overlay::ToggleSimpleFps(); @@ -460,6 +255,29 @@ void WindowSDL::WaitEvent() { SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), Input::ToggleMouseModeTo(Input::MouseMode::Gyro)); break; + case SDL_EVENT_ADD_VIRTUAL_USER: + for (int i = 0; i < 4; i++) { + if (controllers[i]->user_id == -1) { + controllers[i]->user_id = i + 1; + Libraries::UserService::AddUserServiceEvent( + {Libraries::UserService::OrbisUserServiceEventType::Login, + (s32)controllers[i]->user_id}); + break; + } + } + break; + case SDL_EVENT_REMOVE_VIRTUAL_USER: + LOG_INFO(Input, "Remove user"); + for (int i = 3; i >= 0; i--) { + if (controllers[i]->user_id != -1) { + Libraries::UserService::AddUserServiceEvent( + {Libraries::UserService::OrbisUserServiceEventType::Logout, + (s32)controllers[i]->user_id}); + controllers[i]->user_id = -1; + break; + } + } + break; case SDL_EVENT_RDOC_CAPTURE: VideoCore::TriggerCapture(); break; @@ -469,8 +287,10 @@ void WindowSDL::WaitEvent() { } void WindowSDL::InitTimers() { - SDL_AddTimer(100, &PollController, controller); - SDL_AddTimer(33, Input::MousePolling, (void*)controller); + for (int i = 0; i < 4; ++i) { + SDL_AddTimer(250, &PollController, controllers[i]); + } + SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]); } void WindowSDL::RequestKeyboard() { @@ -538,10 +358,38 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) { // as it would break the entire touchpad handling // You can still bind other things to it though if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) { - controller->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down); + controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gbutton.which)] + ->CheckButton(0, OrbisPadButtonDataOffset::TouchPad, input_down); return; } + switch (event->type) { + case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: + switch ((SDL_SensorType)event->gsensor.sensor) { + case SDL_SENSOR_GYRO: + controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)] + ->Gyro(0, event->gsensor.data); + break; + case SDL_SENSOR_ACCEL: + controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gsensor.which)] + ->Acceleration(0, event->gsensor.data); + break; + default: + break; + } + return; + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: + case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: + case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: + controllers[Input::GameControllers::GetGamepadIndexFromJoystickId(event->gtouchpad.which)] + ->SetTouchpadState(event->gtouchpad.finger, + event->type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event->gtouchpad.x, + event->gtouchpad.y); + return; + default: + break; + } + // add/remove it from the list bool inputs_changed = Input::UpdatePressedKeys(input_event); diff --git a/src/sdl_window.h b/src/sdl_window.h index 3a4341de5..fe18ec8e5 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -14,23 +14,8 @@ struct SDL_Gamepad; union SDL_Event; namespace Input { - -class SDLInputEngine : public Engine { -public: - ~SDLInputEngine() override; - void Init() override; - void SetLightBarRGB(u8 r, u8 g, u8 b) override; - void SetVibration(u8 smallMotor, u8 largeMotor) override; - float GetGyroPollRate() const override; - float GetAccelPollRate() const override; - State ReadState() override; - -private: - float m_gyro_poll_rate = 0.0f; - float m_accel_poll_rate = 0.0f; -}; - -} // namespace Input +class GameController; +} namespace Frontend { @@ -62,7 +47,7 @@ class WindowSDL { int keyboard_grab = 0; public: - explicit WindowSDL(s32 width, s32 height, Input::GameController* controller, + explicit WindowSDL(s32 width, s32 height, Input::GameControllers* controllers, std::string_view window_title); ~WindowSDL(); @@ -100,7 +85,7 @@ private: private: s32 width; s32 height; - Input::GameController* controller; + Input::GameControllers controllers{}; WindowSystemInfo window_info{}; SDL_Window* window{}; bool is_shown{}; From 4977b99d9a8ce74f90af6f6381cfeb045715d96b Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 29 Nov 2025 15:40:28 +0200 Subject: [PATCH 12/18] save after adding a game dir via command line --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 54b70b093..ce3950638 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -136,7 +136,7 @@ int main(int argc, char* argv[]) { } EmulatorSettings::GetInstance()->AddGameInstallDir(config_path); - Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + EmulatorSettings::GetInstance()->Save(); std::cout << "Game folder successfully saved.\n"; exit(0); }}, From 44b78612f86c58f5bef937cfdb82a57e403371e9 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 29 Nov 2025 15:56:44 +0200 Subject: [PATCH 13/18] fixed creating new default json file if not exists --- src/core/emulator_settings.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp index e63c5c1d6..5ae11062f 100644 --- a/src/core/emulator_settings.cpp +++ b/src/core/emulator_settings.cpp @@ -297,9 +297,11 @@ bool EmulatorSettings::Load(const std::string& serial) { if (gj.contains("Users")) m_userManager.GetUsers() = gj.at("Users").get(); } else { + SetDefaultValues(); // ensure a default user exists if (m_userManager.GetUsers().user.empty()) m_userManager.GetUsers().user = m_userManager.CreateDefaultUser(); + Save(); } // Load per-game overrides and apply From 85d4fef6c95b5e83621219de583a7b98f4d37121 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 29 Nov 2025 20:10:12 +0200 Subject: [PATCH 14/18] fixed linux? --- src/core/emulator_settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/emulator_settings.cpp b/src/core/emulator_settings.cpp index 5ae11062f..f1eb89e1a 100644 --- a/src/core/emulator_settings.cpp +++ b/src/core/emulator_settings.cpp @@ -16,7 +16,7 @@ namespace nlohmann { template <> struct adl_serializer { static void to_json(json& j, const std::filesystem::path& p) { - j = p.u8string(); + j = p.string(); } static void from_json(const json& j, std::filesystem::path& p) { p = j.get(); From 88c30ab9d68536240a85ab94add065d130e3106c Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:28:13 +0100 Subject: [PATCH 15/18] copyright 2025 --- src/common/path_util.cpp | 2 +- src/core/address_space.cpp | 2 +- src/core/devtools/widget/module_list.h | 2 +- src/core/libraries/app_content/app_content.cpp | 2 +- src/core/libraries/network/net_ctl_obj.cpp | 2 +- src/core/libraries/network/netctl.cpp | 2 +- src/core/libraries/system/systemservice.cpp | 2 +- src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index c74aed0a5..5e2ff6fd1 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index d3a0d132a..4e87c7517 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include diff --git a/src/core/devtools/widget/module_list.h b/src/core/devtools/widget/module_list.h index 911618a48..184d5d369 100644 --- a/src/core/devtools/widget/module_list.h +++ b/src/core/devtools/widget/module_list.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 00cfa2d1f..66ede7c4f 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include diff --git a/src/core/libraries/network/net_ctl_obj.cpp b/src/core/libraries/network/net_ctl_obj.cpp index f6d94845d..23bfff69b 100644 --- a/src/core/libraries/network/net_ctl_obj.cpp +++ b/src/core/libraries/network/net_ctl_obj.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include diff --git a/src/core/libraries/network/netctl.cpp b/src/core/libraries/network/netctl.cpp index ecf195004..5fedcb141 100644 --- a/src/core/libraries/network/netctl.cpp +++ b/src/core/libraries/network/netctl.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #ifdef WIN32 diff --git a/src/core/libraries/system/systemservice.cpp b/src/core/libraries/system/systemservice.cpp index f244947be..a430518de 100644 --- a/src/core/libraries/system/systemservice.cpp +++ b/src/core/libraries/system/systemservice.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 8e16885b8..6d2036d6b 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include From fca94497b10d3e192e704f0fa36962b3d70e1cf4 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 9 Dec 2025 18:22:01 +0200 Subject: [PATCH 16/18] savedata path is not homedir / userid / savedata / gameid --- src/common/config.cpp | 74 ------------------- src/common/config.h | 2 - .../libraries/save_data/save_instance.cpp | 9 ++- src/core/libraries/save_data/savedata.cpp | 11 ++- src/main.cpp | 5 +- 5 files changed, 17 insertions(+), 84 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 089188b4c..f1fe62a1f 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -192,11 +192,6 @@ static ConfigEntry pipelineCacheArchive(false); static ConfigEntry isFpsColor(true); static ConfigEntry showFpsCounter(false); -// GUI -static std::vector settings_install_dirs = {}; -std::vector install_dirs_enabled = {}; -std::filesystem::path save_data_path = {}; - // Settings ConfigEntry m_language(1); // english @@ -264,13 +259,6 @@ void setTrophyKey(string key) { trophyKey = key; } -std::filesystem::path GetSaveDataPath() { - if (save_data_path.empty()) { - return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "savedata"; - } - return save_data_path; -} - void setVolumeSlider(int volumeValue, bool is_game_specific) { volumeSlider.set(volumeValue, is_game_specific); } @@ -540,10 +528,6 @@ void setIsMotionControlsEnabled(bool use, bool is_game_specific) { isMotionControlsEnabled.set(use, is_game_specific); } -void setSaveDataPath(const std::filesystem::path& path) { - save_data_path = path; -} - u32 GetLanguage() { return m_language.get(); } @@ -697,32 +681,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) { current_version = toml::find_or(debug, "ConfigVersion", current_version); } - if (data.contains("GUI")) { - const toml::value& gui = data.at("GUI"); - - const auto install_dir_array = - toml::find_or>(gui, "installDirs", {}); - - try { - install_dirs_enabled = toml::find>(gui, "installDirsEnabled"); - } catch (...) { - // If it does not exist, assume that all are enabled. - install_dirs_enabled.resize(install_dir_array.size(), true); - } - - if (install_dirs_enabled.size() < install_dir_array.size()) { - install_dirs_enabled.resize(install_dir_array.size(), true); - } - - settings_install_dirs.clear(); - for (size_t i = 0; i < install_dir_array.size(); i++) { - settings_install_dirs.push_back( - {std::filesystem::path{install_dir_array[i]}, install_dirs_enabled[i]}); - } - - save_data_path = toml::find_fs_path_or(gui, "saveDataPath", save_data_path); - } - if (data.contains("Settings")) { const toml::value& settings = data.at("Settings"); m_language.setFromToml(settings, "consoleLanguage", is_game_specific); @@ -834,39 +792,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) { m_language.setTomlValue(data, "Settings", "consoleLanguage", is_game_specific); if (!is_game_specific) { - std::vector install_dirs; - std::vector install_dirs_enabled; - - // temporary structure for ordering - struct DirEntry { - string path_str; - bool enabled; - }; - - std::vector sorted_dirs; - for (const auto& dirInfo : settings_install_dirs) { - sorted_dirs.push_back( - {string{fmt::UTF(dirInfo.path.u8string()).data}, dirInfo.enabled}); - } - - // Sort directories alphabetically - std::sort(sorted_dirs.begin(), sorted_dirs.end(), [](const DirEntry& a, const DirEntry& b) { - return std::lexicographical_compare( - a.path_str.begin(), a.path_str.end(), b.path_str.begin(), b.path_str.end(), - [](char a_char, char b_char) { - return std::tolower(a_char) < std::tolower(b_char); - }); - }); - - for (const auto& entry : sorted_dirs) { - install_dirs.push_back(entry.path_str); - install_dirs_enabled.push_back(entry.enabled); - } - // Non game-specific entries - data["GUI"]["installDirs"] = install_dirs; - data["GUI"]["installDirsEnabled"] = install_dirs_enabled; - data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data}; data["Debug"]["ConfigVersion"] = config_version; data["Keys"]["TrophyKey"] = trophyKey; diff --git a/src/common/config.h b/src/common/config.h index db61c74b5..7780719ef 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -116,7 +116,6 @@ int getUsbDeviceBackend(); void setUsbDeviceBackend(int value, bool is_game_specific = false); // TODO -std::filesystem::path GetSaveDataPath(); std::string getUserName(int id); std::array const getUserNames(); bool GetUseUnifiedInputConfig(); @@ -125,7 +124,6 @@ bool GetOverrideControllerColor(); void SetOverrideControllerColor(bool enable); int* GetControllerCustomColor(); void SetControllerCustomColor(int r, int b, int g); -void setSaveDataPath(const std::filesystem::path& path); void setDefaultValues(bool is_game_specific = false); diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index 75a644fdb..25ea6493d 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include @@ -9,6 +9,7 @@ #include "common/config.h" #include "common/path_util.h" #include "common/singleton.h" +#include "core/emulator_settings.h" #include "core/file_sys/fs.h" #include "save_backup.h" #include "save_instance.h" @@ -48,12 +49,14 @@ namespace Libraries::SaveData { fs::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial) { - return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial; + return EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user_id) / "savedata" / + game_serial; } fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial, std::string_view dir_name) { - return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name; + return EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user_id) / "savedata" / + game_serial / dir_name; } uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) { diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 7fba8ed21..fdf0b9d6a 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -1,9 +1,10 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include +#include #include @@ -15,6 +16,7 @@ #include "common/logging/log.h" #include "common/path_util.h" #include "common/string_util.h" +#include "core/emulator_settings.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" @@ -439,7 +441,8 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info, LOG_INFO(Lib_SaveData, "called with invalid block size"); } - const auto root_save = Config::GetSaveDataPath(); + const auto root_save = EmulatorSettings::GetInstance()->GetHomeDir() / + std::to_string(mount_info->userId) / "savedata"; fs::create_directories(root_save); const auto available = fs::space(root_save).available; @@ -487,7 +490,9 @@ static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup return Error::PARAMETER; } LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view()); - const std::string_view mount_point_str{mountPoint->data}; + + std::string mount_point_str = mountPoint->data.to_string(); + for (auto& instance : g_mount_slots) { if (instance.has_value()) { const auto& slot_name = instance->GetMountPoint(); diff --git a/src/main.cpp b/src/main.cpp index 615e961f3..ea6dcfe8c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,7 +43,7 @@ int main(int argc, char* argv[]) { bool waitForDebugger = false; std::optional waitPid; - +#if 0 // Map of argument strings to lambda functions std::unordered_map> arg_map = { {"-h", @@ -263,11 +263,12 @@ int main(int argc, char* argv[]) { if (waitPid.has_value()) { Core::Debugger::WaitForPid(waitPid.value()); } - +#endif // Run the emulator with the resolved eboot path Core::Emulator* emulator = Common::Singleton::Instance(); emulator->executableName = argv[0]; emulator->waitForDebuggerBeforeRun = waitForDebugger; + const char* const eboot_path = "D:/ps4/shadps4games/CUSA18992/eboot.bin"; emulator->Run(eboot_path, game_args, game_folder); return 0; From 868ad608b223fb9f98251ecc55f66b33f0dcc9a6 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 9 Dec 2025 18:24:22 +0200 Subject: [PATCH 17/18] GetValidUsers function to check users with valid home directories --- src/core/emulator_settings.h | 3 --- src/core/user_manager.cpp | 18 ++++++++++++++++++ src/core/user_manager.h | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/core/emulator_settings.h b/src/core/emulator_settings.h index 546a362fa..2a3f422ca 100644 --- a/src/core/emulator_settings.h +++ b/src/core/emulator_settings.h @@ -285,9 +285,6 @@ public: UserManager& GetUserManager() { return m_userManager; } - const UserManager& GetUserManager() const { - return m_userManager; - } private: GeneralSettings m_general{}; diff --git a/src/core/user_manager.cpp b/src/core/user_manager.cpp index b285787d8..6134b6ab4 100644 --- a/src/core/user_manager.cpp +++ b/src/core/user_manager.cpp @@ -24,6 +24,7 @@ bool UserManager::AddUser(const User& user) { std::filesystem::create_directory(user_dir, ec); std::filesystem::create_directory(user_dir / "savedata", ec); std::filesystem::create_directory(user_dir / "trophy", ec); + std::filesystem::create_directory(user_dir / "trophy/data", ec); } return true; @@ -86,6 +87,7 @@ std::vector UserManager::CreateDefaultUser() { std::filesystem::create_directory(user_dir); std::filesystem::create_directory(user_dir / "savedata"); std::filesystem::create_directory(user_dir / "trophy"); + std::filesystem::create_directory(user_dir / "trophy/data"); } return {default_user}; @@ -109,4 +111,20 @@ void UserManager::SetControllerPort(u32 user_id, int port) { if (u.user_id == user_id) u.controller_port = port; } +} +// Returns a list of users that have valid home directories +std::vector UserManager::GetValidUsers() const { + std::vector result; + result.reserve(m_users.user.size()); + + const auto home_dir = EmulatorSettings::GetInstance()->GetHomeDir(); + + for (const auto& user : m_users.user) { + const auto user_dir = home_dir / std::to_string(user.user_id); + if (std::filesystem::exists(user_dir)) { + result.push_back(user); + } + } + + return result; } \ No newline at end of file diff --git a/src/core/user_manager.h b/src/core/user_manager.h index a782deb63..23c5de466 100644 --- a/src/core/user_manager.h +++ b/src/core/user_manager.h @@ -31,6 +31,7 @@ public: std::vector CreateDefaultUser(); bool SetDefaultUser(u32 user_id); void SetControllerPort(u32 user_id, int port); + std::vector GetValidUsers() const; Users& GetUsers() { return m_users; From a5af64c2581ed347ee25c605c88142947ef47974 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 9 Dec 2025 19:28:46 +0200 Subject: [PATCH 18/18] misc --- src/main.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ea6dcfe8c..615e961f3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,7 +43,7 @@ int main(int argc, char* argv[]) { bool waitForDebugger = false; std::optional waitPid; -#if 0 + // Map of argument strings to lambda functions std::unordered_map> arg_map = { {"-h", @@ -263,12 +263,11 @@ int main(int argc, char* argv[]) { if (waitPid.has_value()) { Core::Debugger::WaitForPid(waitPid.value()); } -#endif + // Run the emulator with the resolved eboot path Core::Emulator* emulator = Common::Singleton::Instance(); emulator->executableName = argv[0]; emulator->waitForDebuggerBeforeRun = waitForDebugger; - const char* const eboot_path = "D:/ps4/shadps4games/CUSA18992/eboot.bin"; emulator->Run(eboot_path, game_args, game_folder); return 0;