From b6d2748ed7b22be7791060c3b9d901dca19d8848 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 26 Nov 2025 20:44:20 +0200 Subject: [PATCH] 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");