// 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; } 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; } 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); } 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{}; }