mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-06-03 22:45:00 -06:00
added config mode support
This commit is contained in:
parent
a8f51584bf
commit
faf9cce67e
@ -1,17 +1,21 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <map>
|
||||||
#include <common/path_util.h>
|
#include <common/path_util.h>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "emulator_settings.h"
|
#include "emulator_settings.h"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// ── Singleton storage ─────────────────────────────────────────────────
|
||||||
std::shared_ptr<EmulatorSettings> EmulatorSettings::s_instance = nullptr;
|
std::shared_ptr<EmulatorSettings> EmulatorSettings::s_instance = nullptr;
|
||||||
std::mutex EmulatorSettings::s_mutex;
|
std::mutex EmulatorSettings::s_mutex;
|
||||||
|
|
||||||
|
// ── nlohmann helpers for std::filesystem::path ───────────────────────
|
||||||
namespace nlohmann {
|
namespace nlohmann {
|
||||||
template <>
|
template <>
|
||||||
struct adl_serializer<std::filesystem::path> {
|
struct adl_serializer<std::filesystem::path> {
|
||||||
@ -24,40 +28,35 @@ struct adl_serializer<std::filesystem::path> {
|
|||||||
};
|
};
|
||||||
} // namespace nlohmann
|
} // namespace nlohmann
|
||||||
|
|
||||||
// --------------------
|
// ── Helpers ───────────────────────────────────────────────────────────
|
||||||
// Print summary
|
|
||||||
// --------------------
|
|
||||||
void EmulatorSettings::PrintChangedSummary(const std::vector<std::string>& changed) {
|
void EmulatorSettings::PrintChangedSummary(const std::vector<std::string>& changed) {
|
||||||
if (changed.empty()) {
|
if (changed.empty()) {
|
||||||
LOG_DEBUG(EmuSettings, "[Settings] No game-specific overrides applied");
|
LOG_DEBUG(EmuSettings, "No game-specific overrides applied");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LOG_DEBUG(EmuSettings, "[Settings] Game-specific overrides applied:");
|
LOG_DEBUG(EmuSettings, "Game-specific overrides applied:");
|
||||||
for (const auto& k : changed) {
|
for (const auto& k : changed)
|
||||||
LOG_DEBUG(EmuSettings, " * {}", k);
|
LOG_DEBUG(EmuSettings, " * {}", k);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------
|
// ── Singleton ────────────────────────────────────────────────────────
|
||||||
// ctor/dtor + singleton
|
EmulatorSettings::EmulatorSettings() = default;
|
||||||
// --------------------
|
|
||||||
EmulatorSettings::EmulatorSettings() {
|
|
||||||
// Load();
|
|
||||||
}
|
|
||||||
EmulatorSettings::~EmulatorSettings() {
|
EmulatorSettings::~EmulatorSettings() {
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<EmulatorSettings> EmulatorSettings::GetInstance() {
|
std::shared_ptr<EmulatorSettings> EmulatorSettings::GetInstance() {
|
||||||
std::lock_guard<std::mutex> lock(s_mutex);
|
std::lock_guard lock(s_mutex);
|
||||||
if (!s_instance)
|
if (!s_instance)
|
||||||
s_instance = std::make_shared<EmulatorSettings>();
|
s_instance = std::make_shared<EmulatorSettings>();
|
||||||
return s_instance;
|
return s_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulatorSettings::SetInstance(std::shared_ptr<EmulatorSettings> instance) {
|
void EmulatorSettings::SetInstance(std::shared_ptr<EmulatorSettings> instance) {
|
||||||
std::lock_guard<std::mutex> lock(s_mutex);
|
std::lock_guard lock(s_mutex);
|
||||||
s_instance = instance;
|
s_instance = std::move(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
@ -153,88 +152,89 @@ void EmulatorSettings::SetFontsDir(const std::filesystem::path& dir) {
|
|||||||
m_general.font_dir.value = dir;
|
m_general.font_dir.value = dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------
|
// ── Game-specific override management ────────────────────────────────
|
||||||
// Save
|
void EmulatorSettings::ClearGameSpecificOverrides() {
|
||||||
// --------------------
|
ClearGroupOverrides(m_general);
|
||||||
|
ClearGroupOverrides(m_debug);
|
||||||
|
ClearGroupOverrides(m_input);
|
||||||
|
ClearGroupOverrides(m_audio);
|
||||||
|
ClearGroupOverrides(m_gpu);
|
||||||
|
ClearGroupOverrides(m_vulkan);
|
||||||
|
LOG_DEBUG(EmuSettings, "All game-specific overrides cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulatorSettings::ResetGameSpecificValue(const std::string& key) {
|
||||||
|
// Walk every overrideable group until we find the matching key.
|
||||||
|
auto tryGroup = [&key](auto& group) {
|
||||||
|
for (auto& item : group.GetOverrideableFields()) {
|
||||||
|
if (key == item.key) {
|
||||||
|
item.reset_game_specific(&group);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (tryGroup(m_general))
|
||||||
|
return;
|
||||||
|
if (tryGroup(m_debug))
|
||||||
|
return;
|
||||||
|
if (tryGroup(m_input))
|
||||||
|
return;
|
||||||
|
if (tryGroup(m_audio))
|
||||||
|
return;
|
||||||
|
if (tryGroup(m_gpu))
|
||||||
|
return;
|
||||||
|
if (tryGroup(m_vulkan))
|
||||||
|
return;
|
||||||
|
LOG_WARNING(EmuSettings, "ResetGameSpecificValue: key '{}' not found", key);
|
||||||
|
}
|
||||||
|
|
||||||
bool EmulatorSettings::Save(const std::string& serial) const {
|
bool EmulatorSettings::Save(const std::string& serial) const {
|
||||||
try {
|
try {
|
||||||
if (!serial.empty()) {
|
if (!serial.empty()) {
|
||||||
const std::filesystem::path cfgDir =
|
const auto cfgDir = Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs);
|
||||||
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs);
|
|
||||||
std::filesystem::create_directories(cfgDir);
|
std::filesystem::create_directories(cfgDir);
|
||||||
const std::filesystem::path path = cfgDir / (serial + ".json");
|
const auto path = cfgDir / (serial + ".json");
|
||||||
|
|
||||||
json j = json::object();
|
json j = json::object();
|
||||||
|
|
||||||
// Only write overrideable fields for each group
|
|
||||||
json generalObj = json::object();
|
json generalObj = json::object();
|
||||||
for (auto& item : m_general.GetOverrideableFields()) {
|
SaveGroupGameSpecific(m_general, generalObj);
|
||||||
json whole = m_general;
|
|
||||||
if (whole.contains(item.key))
|
|
||||||
generalObj[item.key] = whole[item.key];
|
|
||||||
}
|
|
||||||
j["General"] = generalObj;
|
j["General"] = generalObj;
|
||||||
|
|
||||||
// Debug
|
|
||||||
json debugObj = json::object();
|
json debugObj = json::object();
|
||||||
for (auto& item : m_debug.GetOverrideableFields()) {
|
SaveGroupGameSpecific(m_debug, debugObj);
|
||||||
json whole = m_debug;
|
|
||||||
if (whole.contains(item.key))
|
|
||||||
debugObj[item.key] = whole[item.key];
|
|
||||||
}
|
|
||||||
j["Debug"] = debugObj;
|
j["Debug"] = debugObj;
|
||||||
|
|
||||||
// Input
|
|
||||||
json inputObj = json::object();
|
json inputObj = json::object();
|
||||||
for (auto& item : m_input.GetOverrideableFields()) {
|
SaveGroupGameSpecific(m_input, inputObj);
|
||||||
json whole = m_input;
|
|
||||||
if (whole.contains(item.key))
|
|
||||||
inputObj[item.key] = whole[item.key];
|
|
||||||
}
|
|
||||||
j["Input"] = inputObj;
|
j["Input"] = inputObj;
|
||||||
|
|
||||||
// Audio
|
|
||||||
json audioObj = json::object();
|
json audioObj = json::object();
|
||||||
for (auto& item : m_audio.GetOverrideableFields()) {
|
SaveGroupGameSpecific(m_audio, audioObj);
|
||||||
json whole = m_audio;
|
|
||||||
if (whole.contains(item.key))
|
|
||||||
audioObj[item.key] = whole[item.key];
|
|
||||||
}
|
|
||||||
j["Audio"] = audioObj;
|
j["Audio"] = audioObj;
|
||||||
|
|
||||||
// GPU
|
|
||||||
json gpuObj = json::object();
|
json gpuObj = json::object();
|
||||||
for (auto& item : m_gpu.GetOverrideableFields()) {
|
SaveGroupGameSpecific(m_gpu, gpuObj);
|
||||||
json whole = m_gpu;
|
|
||||||
if (whole.contains(item.key))
|
|
||||||
gpuObj[item.key] = whole[item.key];
|
|
||||||
}
|
|
||||||
j["GPU"] = gpuObj;
|
j["GPU"] = gpuObj;
|
||||||
|
|
||||||
// Vulkan
|
|
||||||
json vulkanObj = json::object();
|
json vulkanObj = json::object();
|
||||||
for (auto& item : m_vulkan.GetOverrideableFields()) {
|
SaveGroupGameSpecific(m_vulkan, vulkanObj);
|
||||||
json whole = m_vulkan;
|
|
||||||
if (whole.contains(item.key))
|
|
||||||
vulkanObj[item.key] = whole[item.key];
|
|
||||||
}
|
|
||||||
j["Vulkan"] = vulkanObj;
|
j["Vulkan"] = vulkanObj;
|
||||||
|
|
||||||
std::ofstream out(path);
|
std::ofstream out(path);
|
||||||
if (!out.is_open()) {
|
if (!out) {
|
||||||
LOG_ERROR(EmuSettings, "Failed to open file for writing: {}", path.string());
|
LOG_ERROR(EmuSettings, "Failed to open game config for writing: {}", path.string());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
out << std::setw(4) << j;
|
out << std::setw(4) << j;
|
||||||
out.flush();
|
return !out.fail();
|
||||||
if (out.fail()) {
|
|
||||||
LOG_ERROR(EmuSettings, "Failed to write settings to: {}", path.string());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
const std::filesystem::path path =
|
// ── Global config.json ─────────────────────────────────────
|
||||||
|
const auto path =
|
||||||
Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.json";
|
Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.json";
|
||||||
|
|
||||||
json j;
|
json j;
|
||||||
j["General"] = m_general;
|
j["General"] = m_general;
|
||||||
j["Debug"] = m_debug;
|
j["Debug"] = m_debug;
|
||||||
@ -243,19 +243,13 @@ bool EmulatorSettings::Save(const std::string& serial) const {
|
|||||||
j["GPU"] = m_gpu;
|
j["GPU"] = m_gpu;
|
||||||
j["Vulkan"] = m_vulkan;
|
j["Vulkan"] = m_vulkan;
|
||||||
j["Users"] = m_userManager.GetUsers();
|
j["Users"] = m_userManager.GetUsers();
|
||||||
|
|
||||||
std::ofstream out(path);
|
std::ofstream out(path);
|
||||||
if (!out.is_open()) {
|
if (!out) {
|
||||||
LOG_ERROR(EmuSettings, "Failed to open file for writing: {}", path.string());
|
LOG_ERROR(EmuSettings, "Failed to open config for writing: {}", path.string());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
out << std::setw(4) << j;
|
out << std::setw(4) << j;
|
||||||
out.flush();
|
return !out.fail();
|
||||||
if (out.fail()) {
|
|
||||||
LOG_ERROR(EmuSettings, "Failed to write settings to: {}", path.string());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR(EmuSettings, "Error saving settings: {}", e.what());
|
LOG_ERROR(EmuSettings, "Error saving settings: {}", e.what());
|
||||||
@ -263,146 +257,90 @@ bool EmulatorSettings::Save(const std::string& serial) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------
|
// ── Load ──────────────────────────────────────────────────────────────
|
||||||
// Load
|
|
||||||
// --------------------
|
|
||||||
bool EmulatorSettings::Load(const std::string& serial) {
|
bool EmulatorSettings::Load(const std::string& serial) {
|
||||||
try {
|
try {
|
||||||
// If serial is empty, load ONLY global settings
|
|
||||||
if (serial.empty()) {
|
if (serial.empty()) {
|
||||||
const std::filesystem::path userDir =
|
// ── Global config ──────────────────────────────────────────
|
||||||
Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
const auto userDir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||||
const std::filesystem::path configPath = userDir / "config.json";
|
const auto configPath = userDir / "config.json";
|
||||||
|
LOG_DEBUG(EmuSettings, "Loading global config from: {}", configPath.string());
|
||||||
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loading global settings from: {}",
|
if (std::ifstream in{configPath}; in.good()) {
|
||||||
configPath.string());
|
|
||||||
|
|
||||||
// Load global config if exists
|
|
||||||
if (std::ifstream globalIn{configPath}; globalIn.good()) {
|
|
||||||
json gj;
|
json gj;
|
||||||
globalIn >> gj;
|
in >> gj;
|
||||||
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Global config JSON size: {}", gj.size());
|
auto mergeGroup = [&gj](auto& group, const char* section) {
|
||||||
|
if (!gj.contains(section))
|
||||||
|
return;
|
||||||
|
json current = group;
|
||||||
|
current.update(gj.at(section));
|
||||||
|
group = current.get<std::remove_reference_t<decltype(group)>>();
|
||||||
|
};
|
||||||
|
|
||||||
if (gj.contains("General")) {
|
mergeGroup(m_general, "General");
|
||||||
json current = m_general;
|
mergeGroup(m_debug, "Debug");
|
||||||
current.update(gj.at("General"));
|
mergeGroup(m_input, "Input");
|
||||||
m_general = current.get<GeneralSettings>();
|
mergeGroup(m_audio, "Audio");
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded General settings");
|
mergeGroup(m_gpu, "GPU");
|
||||||
}
|
mergeGroup(m_vulkan, "Vulkan");
|
||||||
if (gj.contains("Debug")) {
|
|
||||||
json current = m_debug;
|
if (gj.contains("Users"))
|
||||||
current.update(gj.at("Debug"));
|
|
||||||
m_debug = current.get<DebugSettings>();
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Debug settings");
|
|
||||||
}
|
|
||||||
if (gj.contains("Input")) {
|
|
||||||
json current = m_input;
|
|
||||||
current.update(gj.at("Input"));
|
|
||||||
m_input = current.get<InputSettings>();
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Input settings");
|
|
||||||
}
|
|
||||||
if (gj.contains("Audio")) {
|
|
||||||
json current = m_audio;
|
|
||||||
current.update(gj.at("Audio"));
|
|
||||||
m_audio = current.get<AudioSettings>();
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Audio settings");
|
|
||||||
}
|
|
||||||
if (gj.contains("GPU")) {
|
|
||||||
json current = m_gpu;
|
|
||||||
current.update(gj.at("GPU"));
|
|
||||||
m_gpu = current.get<GPUSettings>();
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded GPU settings");
|
|
||||||
}
|
|
||||||
if (gj.contains("Vulkan")) {
|
|
||||||
json current = m_vulkan;
|
|
||||||
current.update(gj.at("Vulkan"));
|
|
||||||
m_vulkan = current.get<VulkanSettings>();
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Vulkan settings");
|
|
||||||
}
|
|
||||||
if (gj.contains("Users")) {
|
|
||||||
m_userManager.GetUsers() = gj.at("Users").get<Users>();
|
m_userManager.GetUsers() = gj.at("Users").get<Users>();
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Loaded Users");
|
LOG_DEBUG(EmuSettings, "Global config loaded successfully");
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
LOG_DEBUG(EmuSettings,
|
LOG_DEBUG(EmuSettings, "Global config not found – using defaults");
|
||||||
"[EmulatorSettings] Global config not found, setting defaults");
|
|
||||||
SetDefaultValues();
|
SetDefaultValues();
|
||||||
// ensure a default user exists
|
|
||||||
if (m_userManager.GetUsers().user.empty())
|
if (m_userManager.GetUsers().user.empty())
|
||||||
m_userManager.GetUsers().user = m_userManager.CreateDefaultUser();
|
m_userManager.GetUsers().user = m_userManager.CreateDefaultUser();
|
||||||
Save();
|
Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
// If serial is provided, ONLY apply game-specific overrides
|
|
||||||
// WITHOUT reloading global settings!
|
|
||||||
else {
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying game-specific overrides for: {}",
|
|
||||||
serial);
|
|
||||||
|
|
||||||
const std::filesystem::path gamePath =
|
} else {
|
||||||
|
// ── Per-game override file ─────────────────────────────────
|
||||||
|
// Never reloads global settings. Only applies
|
||||||
|
// game_specific_value overrides on top of the already-loaded
|
||||||
|
// base configuration.
|
||||||
|
const auto gamePath =
|
||||||
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (serial + ".json");
|
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (serial + ".json");
|
||||||
|
LOG_DEBUG(EmuSettings, "Applying game config: {}", gamePath.string());
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Game config path: {}", gamePath.string());
|
|
||||||
|
|
||||||
if (!std::filesystem::exists(gamePath)) {
|
if (!std::filesystem::exists(gamePath)) {
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] No game-specific config found");
|
LOG_DEBUG(EmuSettings, "No game-specific config found for {}", serial);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ifstream in(gamePath);
|
std::ifstream in(gamePath);
|
||||||
if (!in.is_open()) {
|
if (!in) {
|
||||||
LOG_ERROR(EmuSettings, "[EmulatorSettings] Failed to open game config file");
|
LOG_ERROR(EmuSettings, "Failed to open game config: {}", gamePath.string());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
json gj;
|
json gj;
|
||||||
in >> gj;
|
in >> gj;
|
||||||
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Game config JSON: {}", gj.dump(2));
|
|
||||||
|
|
||||||
std::vector<std::string> changed;
|
std::vector<std::string> changed;
|
||||||
|
|
||||||
if (gj.contains("General")) {
|
// ApplyGroupOverrides now correctly stores values as
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying General overrides");
|
// game_specific_value (see make_override in the header).
|
||||||
ApplyGroupOverrides<GeneralSettings>(m_general, gj.at("General"), changed);
|
// ConfigMode::Default will then resolve them at getter call
|
||||||
}
|
// time without ever touching the base values.
|
||||||
if (gj.contains("Debug")) {
|
if (gj.contains("General"))
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying Debug overrides");
|
ApplyGroupOverrides(m_general, gj.at("General"), changed);
|
||||||
ApplyGroupOverrides<DebugSettings>(m_debug, gj.at("Debug"), changed);
|
if (gj.contains("Debug"))
|
||||||
}
|
ApplyGroupOverrides(m_debug, gj.at("Debug"), changed);
|
||||||
if (gj.contains("Input")) {
|
if (gj.contains("Input"))
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying Input overrides");
|
ApplyGroupOverrides(m_input, gj.at("Input"), changed);
|
||||||
ApplyGroupOverrides<InputSettings>(m_input, gj.at("Input"), changed);
|
if (gj.contains("Audio"))
|
||||||
}
|
ApplyGroupOverrides(m_audio, gj.at("Audio"), changed);
|
||||||
if (gj.contains("Audio")) {
|
if (gj.contains("GPU"))
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying Audio overrides");
|
ApplyGroupOverrides(m_gpu, gj.at("GPU"), changed);
|
||||||
ApplyGroupOverrides<AudioSettings>(m_audio, gj.at("Audio"), changed);
|
if (gj.contains("Vulkan"))
|
||||||
}
|
ApplyGroupOverrides(m_vulkan, gj.at("Vulkan"), changed);
|
||||||
if (gj.contains("GPU")) {
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying GPU overrides");
|
|
||||||
ApplyGroupOverrides<GPUSettings>(m_gpu, gj.at("GPU"), changed);
|
|
||||||
|
|
||||||
// Debug: Print specific GPU values
|
|
||||||
auto gpuJson = gj["GPU"];
|
|
||||||
if (gpuJson.contains("fsr_enabled")) {
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] GPU/fsr_enabled JSON: {}",
|
|
||||||
gpuJson["fsr_enabled"].dump());
|
|
||||||
}
|
|
||||||
if (gpuJson.contains("rcas_enabled")) {
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] GPU/rcas_enabled JSON: {}",
|
|
||||||
gpuJson["rcas_enabled"].dump());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (gj.contains("Vulkan")) {
|
|
||||||
LOG_DEBUG(EmuSettings, "[EmulatorSettings] Applying Vulkan overrides");
|
|
||||||
ApplyGroupOverrides<VulkanSettings>(m_vulkan, gj.at("Vulkan"), changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintChangedSummary(changed);
|
PrintChangedSummary(changed);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
@ -422,19 +360,15 @@ void EmulatorSettings::SetDefaultValues() {
|
|||||||
|
|
||||||
std::vector<std::string> EmulatorSettings::GetAllOverrideableKeys() const {
|
std::vector<std::string> EmulatorSettings::GetAllOverrideableKeys() const {
|
||||||
std::vector<std::string> keys;
|
std::vector<std::string> keys;
|
||||||
|
auto addGroup = [&keys](const auto& fields) {
|
||||||
auto addKeys = [&keys](const std::vector<OverrideItem>& items) {
|
for (const auto& item : fields)
|
||||||
for (const auto& item : items) {
|
|
||||||
keys.push_back(item.key);
|
keys.push_back(item.key);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
addGroup(m_general.GetOverrideableFields());
|
||||||
addKeys(m_general.GetOverrideableFields());
|
addGroup(m_debug.GetOverrideableFields());
|
||||||
addKeys(m_debug.GetOverrideableFields());
|
addGroup(m_input.GetOverrideableFields());
|
||||||
addKeys(m_input.GetOverrideableFields());
|
addGroup(m_audio.GetOverrideableFields());
|
||||||
addKeys(m_audio.GetOverrideableFields());
|
addGroup(m_gpu.GetOverrideableFields());
|
||||||
addKeys(m_gpu.GetOverrideableFields());
|
addGroup(m_vulkan.GetOverrideableFields());
|
||||||
addKeys(m_vulkan.GetOverrideableFields());
|
|
||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
@ -15,12 +15,47 @@
|
|||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "core/user_manager.h"
|
#include "core/user_manager.h"
|
||||||
|
|
||||||
// -------------------------------
|
enum class ConfigMode {
|
||||||
// Generic Setting wrapper
|
Default,
|
||||||
// -------------------------------
|
Global,
|
||||||
|
Clean,
|
||||||
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct Setting {
|
struct Setting {
|
||||||
|
T default_value{};
|
||||||
T value{};
|
T value{};
|
||||||
|
std::optional<T> game_specific_value{};
|
||||||
|
|
||||||
|
Setting() = default;
|
||||||
|
// Single-argument ctor: initialises both default_value and value so
|
||||||
|
// that CleanMode can always recover the intended factory default.
|
||||||
|
/*implicit*/ Setting(T init) : default_value(std::move(init)), value(default_value) {}
|
||||||
|
|
||||||
|
/// Return the active value under the given mode.
|
||||||
|
T get(ConfigMode mode = ConfigMode::Default) const {
|
||||||
|
switch (mode) {
|
||||||
|
case ConfigMode::Default:
|
||||||
|
return game_specific_value.value_or(value);
|
||||||
|
case ConfigMode::Global:
|
||||||
|
return value;
|
||||||
|
case ConfigMode::Clean:
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write v to the base layer.
|
||||||
|
/// Game-specific overrides are applied exclusively via Load(serial)
|
||||||
|
void set(const T& v) {
|
||||||
|
value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Discard the game-specific override; subsequent get(Default) will
|
||||||
|
/// fall back to the base value.
|
||||||
|
void reset_game_specific() {
|
||||||
|
game_specific_value = std::nullopt;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -33,16 +68,19 @@ void from_json(const nlohmann::json& j, Setting<T>& s) {
|
|||||||
s.value = j.get<T>();
|
s.value = j.get<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------
|
|
||||||
// Helper to describe a per-field override action
|
|
||||||
// -------------------------------
|
|
||||||
struct OverrideItem {
|
struct OverrideItem {
|
||||||
const char* key;
|
const char* key;
|
||||||
// apply(basePtrToStruct, jsonEntry, changedFields)
|
std::function<void(void* group_ptr, const nlohmann::json& entry,
|
||||||
std::function<void(void*, const nlohmann::json&, std::vector<std::string>&)> apply;
|
std::vector<std::string>& changed)>
|
||||||
|
apply;
|
||||||
|
/// Return the value that should be written to the per-game config file.
|
||||||
|
/// Falls back to base value if no game-specific override is set.
|
||||||
|
std::function<nlohmann::json(const void* group_ptr)> get_for_save;
|
||||||
|
|
||||||
|
/// Clear game_specific_value for this field.
|
||||||
|
std::function<void(void* group_ptr)> reset_game_specific;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper factory: create an OverrideItem binding a pointer-to-member
|
|
||||||
template <typename Struct, typename T>
|
template <typename Struct, typename T>
|
||||||
inline OverrideItem make_override(const char* key, Setting<T> Struct::* member) {
|
inline OverrideItem make_override(const char* key, Setting<T> Struct::* member) {
|
||||||
return OverrideItem{
|
return OverrideItem{
|
||||||
@ -50,31 +88,41 @@ inline OverrideItem make_override(const char* key, Setting<T> Struct::* member)
|
|||||||
[member, key](void* base, const nlohmann::json& entry, std::vector<std::string>& changed) {
|
[member, key](void* base, const nlohmann::json& entry, std::vector<std::string>& changed) {
|
||||||
LOG_DEBUG(EmuSettings, "[make_override] Processing key: {}", key);
|
LOG_DEBUG(EmuSettings, "[make_override] Processing key: {}", key);
|
||||||
LOG_DEBUG(EmuSettings, "[make_override] Entry JSON: {}", entry.dump());
|
LOG_DEBUG(EmuSettings, "[make_override] Entry JSON: {}", entry.dump());
|
||||||
|
|
||||||
Struct* obj = reinterpret_cast<Struct*>(base);
|
Struct* obj = reinterpret_cast<Struct*>(base);
|
||||||
Setting<T>& dst = obj->*member;
|
Setting<T>& dst = obj->*member;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse the value from JSON
|
|
||||||
T newValue = entry.get<T>();
|
T newValue = entry.get<T>();
|
||||||
|
|
||||||
LOG_DEBUG(EmuSettings, "[make_override] Parsed value: {}", newValue);
|
LOG_DEBUG(EmuSettings, "[make_override] Parsed value: {}", newValue);
|
||||||
LOG_DEBUG(EmuSettings, "[make_override] Current value: {}", dst.value);
|
LOG_DEBUG(EmuSettings, "[make_override] Current value: {}", dst.value);
|
||||||
|
|
||||||
if (dst.value != newValue) {
|
if (dst.value != newValue) {
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << key << " ( " << dst.value << " → " << newValue << " )";
|
oss << key << " ( " << dst.value << " → " << newValue << " )";
|
||||||
changed.push_back(oss.str());
|
changed.push_back(oss.str());
|
||||||
LOG_DEBUG(EmuSettings, "[make_override] Recorded change: {}", oss.str());
|
LOG_DEBUG(EmuSettings, "[make_override] Recorded change: {}", oss.str());
|
||||||
}
|
}
|
||||||
|
dst.game_specific_value = newValue;
|
||||||
dst.value = newValue;
|
|
||||||
LOG_DEBUG(EmuSettings, "[make_override] Successfully updated {}", key);
|
LOG_DEBUG(EmuSettings, "[make_override] Successfully updated {}", key);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
LOG_ERROR(EmuSettings, "[make_override] ERROR parsing {}: {}", key, e.what());
|
LOG_ERROR(EmuSettings, "[make_override] ERROR parsing {}: {}", key, e.what());
|
||||||
LOG_ERROR(EmuSettings, "[make_override] Entry was: {}", entry.dump());
|
LOG_ERROR(EmuSettings, "[make_override] Entry was: {}", entry.dump());
|
||||||
LOG_ERROR(EmuSettings, "[make_override] Type name: {}", entry.type_name());
|
LOG_ERROR(EmuSettings, "[make_override] Type name: {}", entry.type_name());
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// --- get_for_save -------------------------------------------
|
||||||
|
// Returns game_specific_value when present, otherwise base value.
|
||||||
|
// This means a freshly-opened game-specific dialog still shows
|
||||||
|
// useful (current-global) values rather than empty entries.
|
||||||
|
[member](const void* base) -> nlohmann::json {
|
||||||
|
const Struct* obj = reinterpret_cast<const Struct*>(base);
|
||||||
|
const Setting<T>& src = obj->*member;
|
||||||
|
return nlohmann::json(src.game_specific_value.value_or(src.value));
|
||||||
|
},
|
||||||
|
|
||||||
|
// --- reset_game_specific ------------------------------------
|
||||||
|
[member](void* base) {
|
||||||
|
Struct* obj = reinterpret_cast<Struct*>(base);
|
||||||
|
(obj->*member).reset_game_specific();
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,6 +387,23 @@ public:
|
|||||||
bool Load(const std::string& serial = "");
|
bool Load(const std::string& serial = "");
|
||||||
void SetDefaultValues();
|
void SetDefaultValues();
|
||||||
|
|
||||||
|
// Config mode
|
||||||
|
ConfigMode GetConfigMode() const {
|
||||||
|
return m_configMode;
|
||||||
|
}
|
||||||
|
void SetConfigMode(ConfigMode mode) {
|
||||||
|
m_configMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Game-specific override management
|
||||||
|
/// Clears all per-game overrides. Call this when a game exits so
|
||||||
|
/// the emulator reverts to global settings.
|
||||||
|
void ClearGameSpecificOverrides();
|
||||||
|
|
||||||
|
/// Reset a single field's game-specific override by its JSON ke
|
||||||
|
void ResetGameSpecificValue(const std::string& key);
|
||||||
|
|
||||||
// general accessors
|
// general accessors
|
||||||
bool AddGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
|
bool AddGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
|
||||||
std::vector<std::filesystem::path> GetGameInstallDirs() const;
|
std::vector<std::filesystem::path> GetGameInstallDirs() const;
|
||||||
@ -369,11 +434,12 @@ private:
|
|||||||
GPUSettings m_gpu{};
|
GPUSettings m_gpu{};
|
||||||
VulkanSettings m_vulkan{};
|
VulkanSettings m_vulkan{};
|
||||||
UserManager m_userManager;
|
UserManager m_userManager;
|
||||||
|
ConfigMode m_configMode{ConfigMode::Default};
|
||||||
|
|
||||||
static std::shared_ptr<EmulatorSettings> s_instance;
|
static std::shared_ptr<EmulatorSettings> s_instance;
|
||||||
static std::mutex s_mutex;
|
static std::mutex s_mutex;
|
||||||
|
|
||||||
// Generic helper that applies override descriptors for a specific group
|
/// Apply overrideable fields from groupJson into group.game_specific_value.
|
||||||
template <typename Group>
|
template <typename Group>
|
||||||
void ApplyGroupOverrides(Group& group, const nlohmann::json& groupJson,
|
void ApplyGroupOverrides(Group& group, const nlohmann::json& groupJson,
|
||||||
std::vector<std::string>& changed) {
|
std::vector<std::string>& changed) {
|
||||||
@ -384,6 +450,20 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write all overrideable fields from group into out (for game-specific save).
|
||||||
|
template <typename Group>
|
||||||
|
static void SaveGroupGameSpecific(const Group& group, nlohmann::json& out) {
|
||||||
|
for (auto& item : group.GetOverrideableFields())
|
||||||
|
out[item.key] = item.get_for_save(&group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard every game-specific override in group.
|
||||||
|
template <typename Group>
|
||||||
|
static void ClearGroupOverrides(Group& group) {
|
||||||
|
for (auto& item : group.GetOverrideableFields())
|
||||||
|
item.reset_game_specific(&group);
|
||||||
|
}
|
||||||
|
|
||||||
static void PrintChangedSummary(const std::vector<std::string>& changed);
|
static void PrintChangedSummary(const std::vector<std::string>& changed);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -407,23 +487,24 @@ public:
|
|||||||
return m_vulkan.GetOverrideableFields();
|
return m_vulkan.GetOverrideableFields();
|
||||||
}
|
}
|
||||||
std::vector<std::string> GetAllOverrideableKeys() const;
|
std::vector<std::string> GetAllOverrideableKeys() const;
|
||||||
|
|
||||||
#define SETTING_FORWARD(group, Name, field) \
|
#define SETTING_FORWARD(group, Name, field) \
|
||||||
auto Get##Name() const { \
|
auto Get##Name() const { \
|
||||||
return group.field.value; \
|
return (group).field.get(m_configMode); \
|
||||||
} \
|
} \
|
||||||
void Set##Name(const decltype(group.field.value)& v) { \
|
void Set##Name(const decltype((group).field.value)& v) { \
|
||||||
group.field.value = v; \
|
(group).field.value = v; \
|
||||||
}
|
}
|
||||||
#define SETTING_FORWARD_BOOL(group, Name, field) \
|
#define SETTING_FORWARD_BOOL(group, Name, field) \
|
||||||
auto Is##Name() const { \
|
bool Is##Name() const { \
|
||||||
return group.field.value; \
|
return (group).field.get(m_configMode); \
|
||||||
} \
|
} \
|
||||||
void Set##Name(const decltype(group.field.value)& v) { \
|
void Set##Name(bool v) { \
|
||||||
group.field.value = v; \
|
(group).field.value = v; \
|
||||||
}
|
}
|
||||||
#define SETTING_FORWARD_BOOL_READONLY(group, Name, field) \
|
#define SETTING_FORWARD_BOOL_READONLY(group, Name, field) \
|
||||||
auto Is##Name() const { \
|
bool Is##Name() const { \
|
||||||
return group.field.value; \
|
return (group).field.get(m_configMode); \
|
||||||
}
|
}
|
||||||
|
|
||||||
// General settings
|
// General settings
|
||||||
@ -516,4 +597,5 @@ public:
|
|||||||
|
|
||||||
#undef SETTING_FORWARD
|
#undef SETTING_FORWARD
|
||||||
#undef SETTING_FORWARD_BOOL
|
#undef SETTING_FORWARD_BOOL
|
||||||
|
#undef SETTING_FORWARD_BOOL_READONLY
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue
Block a user