Files
eden/src/frontend_common/config.cpp
T
xbzk ecb2ae4076 [settings] move program_args and debug_knobs back to debugging category and make them pergameable (#4378)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

I have moved debug_knobs and program_args to System category, to make them pergameable without hassle.
But the hassle came: They were automatically readded to Qt's System tab, doubling them.
If it were to face hassle either way, the right thing is to move them back to Debugging category, then make them exceptionally pergameable.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4378
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
2026-09-08 20:05:56 +02:00

1090 lines
37 KiB
C++

// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging.h"
#include "common/settings.h"
#include "common/settings_common.h"
#include "common/settings_enums.h"
#include "config.h"
#ifdef _WIN32
#include "common/string_util.h"
#endif
#include <boost/algorithm/string/replace.hpp>
#include "common/assert.h"
namespace FS = Common::FS;
Config::Config(const ConfigType config_type)
: type(config_type), global{config_type == ConfigType::GlobalConfig} {}
void Config::Initialize(const std::string& config_name) {
const std::filesystem::path fs_config_loc = FS::GetEdenPath(FS::EdenPath::ConfigDir);
const auto config_file = fmt::format("{}.ini", config_name);
switch (type) {
case ConfigType::GlobalConfig:
config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
void(FS::CreateParentDir(config_loc));
SetUpIni();
Reload();
break;
case ConfigType::PerGameConfig:
config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
void(FS::CreateParentDir(config_loc));
SetUpIni();
Reload();
break;
case ConfigType::InputProfile:
config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
void(FS::CreateParentDir(config_loc));
SetUpIni();
break;
}
}
void Config::Initialize(const std::optional<std::string> config_path) {
const std::filesystem::path default_sdl_config_path =
FS::GetEdenPath(FS::EdenPath::ConfigDir) / "sdl2-config.ini";
config_loc = config_path.value_or(FS::PathToUTF8String(default_sdl_config_path));
void(FS::CreateParentDir(config_loc));
SetUpIni();
Reload();
}
void Config::WriteToIni() const {
std::string config_type;
switch (type) {
case ConfigType::GlobalConfig:
config_type = "Global";
break;
case ConfigType::PerGameConfig:
config_type = "Game Specific";
break;
case ConfigType::InputProfile:
config_type = "Input Profile";
break;
}
LOG_INFO(Config, "Writing {} configuration to: {}", config_type, config_loc);
FILE* fp = nullptr;
#ifdef _WIN32
fp = _wfopen(Common::UTF8ToUTF16W(config_loc).data(), L"wb");
#else
fp = fopen(config_loc.c_str(), "wb");
#endif
if (fp == nullptr) {
LOG_ERROR(Frontend, "Config file could not be saved!");
return;
}
CSimpleIniA::FileWriter writer(fp);
const SI_Error rc = config->Save(writer, false);
if (rc < 0) {
LOG_ERROR(Frontend, "Config file could not be saved!");
}
fclose(fp);
}
void Config::SetUpIni() {
config = std::make_unique<CSimpleIniA>();
config->SetUnicode(true);
config->SetSpaces(false);
FILE* fp = nullptr;
#ifdef _WIN32
_wfopen_s(&fp, Common::UTF8ToUTF16W(config_loc).data(), L"rb, ccs=UTF-8");
if (fp == nullptr) {
fp = _wfopen(Common::UTF8ToUTF16W(config_loc).data(), L"wb, ccs=UTF-8");
}
#else
fp = fopen(config_loc.c_str(), "rb");
if (fp == nullptr) {
fp = fopen(config_loc.c_str(), "wb");
}
#endif
if (fp == nullptr) {
LOG_ERROR(Frontend, "Config file could not be loaded!");
return;
}
if (SI_Error rc = config->LoadFile(fp); rc < 0) {
LOG_ERROR(Frontend, "Config file could not be loaded!");
}
fclose(fp);
}
bool Config::IsCustomConfig() const {
return type == ConfigType::PerGameConfig;
}
void Config::ReadPlayerValues(const std::size_t player_index) {
std::string player_prefix;
if (type != ConfigType::InputProfile) {
player_prefix.append("player_").append(ToString(player_index)).append("_");
}
const auto profile_name = ReadStringSetting(std::string(player_prefix).append("profile_name"));
auto& player = Settings::values.players.GetValue()[player_index];
if (IsCustomConfig()) {
if (profile_name.empty()) {
// Use the global input config
player = Settings::values.players.GetValue(true)[player_index];
player.profile_name = "";
return;
}
player.profile_name = profile_name;
}
if (player_prefix.empty() && Settings::IsConfiguringGlobal()) {
const auto controller = Settings::ControllerType(
ReadIntegerSetting(std::string(player_prefix).append("type"),
u8(Settings::ControllerType::ProController)));
if (controller == Settings::ControllerType::LeftJoycon ||
controller == Settings::ControllerType::RightJoycon) {
player.controller_type = controller;
}
} else {
if (global) {
auto& player_global = Settings::values.players.GetValue(true)[player_index];
player_global.profile_name = profile_name;
}
std::string connected_key = player_prefix;
player.connected = ReadBooleanSetting(connected_key.append("connected"),
std::make_optional(player_index == 0));
player.controller_type = Settings::ControllerType(
ReadIntegerSetting(std::string(player_prefix).append("type"),
u8(Settings::ControllerType::ProController)));
player.vibration_enabled = ReadBooleanSetting(
std::string(player_prefix).append("vibration_enabled"), std::make_optional(true));
player.vibration_strength =
int(ReadIntegerSetting(std::string(player_prefix).append("vibration_strength"), 100));
player.body_color_left = u32(ReadIntegerSetting(
std::string(player_prefix).append("body_color_left"), Settings::JOYCON_BODY_NEON_BLUE));
player.body_color_right = u32(ReadIntegerSetting(
std::string(player_prefix).append("body_color_right"), Settings::JOYCON_BODY_NEON_RED));
player.button_color_left =
u32(ReadIntegerSetting(std::string(player_prefix).append("button_color_left"),
Settings::JOYCON_BUTTONS_NEON_BLUE));
player.button_color_right =
u32(ReadIntegerSetting(std::string(player_prefix).append("button_color_right"),
Settings::JOYCON_BUTTONS_NEON_RED));
}
}
void Config::ReadTouchscreenValues() {
Settings::values.touchscreen.enabled =
ReadBooleanSetting(std::string("touchscreen_enabled"), std::make_optional(true));
Settings::values.touchscreen.rotation_angle =
u32(ReadIntegerSetting(std::string("touchscreen_angle"), 0));
Settings::values.touchscreen.diameter_x =
u32(ReadIntegerSetting(std::string("touchscreen_diameter_x"), 90));
Settings::values.touchscreen.diameter_y =
u32(ReadIntegerSetting(std::string("touchscreen_diameter_y"), 90));
}
void Config::ReadAudioValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Audio));
ReadCategory(Settings::Category::Audio);
ReadCategory(Settings::Category::UiAudio);
EndGroup();
}
void Config::ReadControlValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
ReadCategory(Settings::Category::Controls);
Settings::values.players.SetGlobal(!IsCustomConfig());
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
ReadPlayerValues(p);
}
// Disable docked mode if handheld is selected
const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
if (controller_type == Settings::ControllerType::Handheld) {
Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
}
if (IsCustomConfig()) {
EndGroup();
return;
}
ReadTouchscreenValues();
ReadMotionTouchValues();
EndGroup();
}
void Config::ReadMotionTouchValues() {
Settings::values.touch_from_button_maps.clear();
int num_touch_from_button_maps = BeginArray(std::string("touch_from_button_maps"));
if (num_touch_from_button_maps > 0) {
for (int i = 0; i < num_touch_from_button_maps; ++i) {
SetArrayIndex(i);
Settings::TouchFromButtonMap map;
map.name = ReadStringSetting(std::string("name"), std::string("default"));
int const num_touch_maps = BeginArray(std::string("entries"));
map.buttons.resize(num_touch_maps);
for (int j = 0; j < num_touch_maps; j++) {
SetArrayIndex(j);
map.buttons[j] = ReadStringSetting(std::string("bind"));
}
EndArray(); // entries
Settings::values.touch_from_button_maps.emplace_back(std::move(map));
}
} else {
Settings::values.touch_from_button_maps.emplace_back(Settings::TouchFromButtonMap{"default", {}});
num_touch_from_button_maps = 1;
}
EndArray(); // touch_from_button_maps
Settings::values.touch_from_button_map_index = (std::min)(Settings::values.touch_from_button_map_index.GetValue(), u32(num_touch_from_button_maps - 1));
}
void Config::ReadCoreValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Core));
ReadCategory(Settings::Category::Core);
EndGroup();
}
void Config::ReadDataStorageValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
using namespace Common::FS;
const auto setPath = [this](const EdenPath& path, const char* setting) {
SetEdenPath(path, ReadStringSetting(std::string(setting)));
};
setPath(EdenPath::NANDDir, "nand_directory");
setPath(EdenPath::SDMCDir, "sdmc_directory");
setPath(EdenPath::LoadDir, "load_directory");
setPath(EdenPath::DumpDir, "dump_directory");
setPath(EdenPath::TASDir, "tas_directory");
const auto save_dir_setting = ReadStringSetting(std::string("save_directory"));
if (save_dir_setting.empty()) {
SetEdenPath(EdenPath::SaveDir, GetEdenPathString(EdenPath::NANDDir));
} else {
SetEdenPath(EdenPath::SaveDir, save_dir_setting);
}
ReadCategory(Settings::Category::DataStorage);
EndGroup();
}
void Config::ReadDebuggingValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
if (global) {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
Settings::values.record_frame_times =
ReadBooleanSetting(std::string("record_frame_times"), std::make_optional(false));
ReadCategory(Settings::Category::Debugging);
ReadCategory(Settings::Category::DebuggingGraphics);
} else {
ReadSettingGeneric(&Settings::values.program_args);
ReadSettingGeneric(&Settings::values.debug_knobs);
}
EndGroup();
}
void Config::ReadServiceValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
ReadCategory(Settings::Category::Services);
EndGroup();
}
void Config::ReadDisabledAddOnValues() {
// Custom config section
BeginGroup(std::string("DisabledAddOns"));
const int size = BeginArray(std::string(""));
for (int i = 0; i < size; ++i) {
SetArrayIndex(i);
const auto title_id = ReadUnsignedIntegerSetting(std::string("title_id"), 0);
std::vector<std::string> out;
const int d_size = BeginArray("disabled");
for (int j = 0; j < d_size; ++j) {
SetArrayIndex(j);
out.push_back(ReadStringSetting(std::string("d"), std::string("")));
}
EndArray(); // d
Settings::values.disabled_addons.insert_or_assign(title_id, out);
}
EndArray(); // Base disabled addons array - Has no base key
EndGroup();
}
void Config::ReadMiscellaneousValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous));
ReadCategory(Settings::Category::Miscellaneous);
EndGroup();
}
void Config::ReadCpuValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Cpu));
ReadCategory(Settings::Category::Cpu);
ReadCategory(Settings::Category::CpuDebug);
ReadCategory(Settings::Category::CpuUnsafe);
EndGroup();
}
void Config::ReadRendererValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Renderer));
ReadCategory(Settings::Category::Renderer);
ReadCategory(Settings::Category::RendererAdvanced);
ReadCategory(Settings::Category::RendererHacks);
ReadCategory(Settings::Category::RendererExtensions);
ReadCategory(Settings::Category::RendererDebug);
EndGroup();
}
void Config::ReadScreenshotValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
ReadCategory(Settings::Category::Screenshots);
FS::SetEdenPath(FS::EdenPath::ScreenshotsDir,
ReadStringSetting(std::string("screenshot_path")));
EndGroup();
}
void Config::ReadSystemValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::System));
ReadCategory(Settings::Category::System);
ReadCategory(Settings::Category::SystemAudio);
EndGroup();
}
void Config::ReadWebServiceValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::WebService));
ReadCategory(Settings::Category::WebService);
EndGroup();
}
void Config::ReadNetworkValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
ReadCategory(Settings::Category::Network);
EndGroup();
}
void Config::ReadLibraryAppletValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::LibraryApplet));
ReadCategory(Settings::Category::LibraryApplet);
EndGroup();
}
void Config::ReadValues() {
if (global) {
ReadDataStorageValues();
ReadDisabledAddOnValues();
ReadServiceValues();
ReadWebServiceValues();
ReadMiscellaneousValues();
}
ReadDebuggingValues();
ReadLibraryAppletValues();
ReadNetworkValues();
ReadControlValues();
ReadCoreValues();
ReadCpuValues();
ReadRendererValues();
ReadAudioValues();
ReadSystemValues();
}
void Config::SavePlayerValues(const std::size_t player_index) {
std::string player_prefix;
if (type != ConfigType::InputProfile) {
player_prefix = std::string("player_").append(ToString(player_index)).append("_");
}
const auto& player = Settings::values.players.GetValue()[player_index];
if (IsCustomConfig()) {
if (player.profile_name.empty()) {
// No custom profile selected
return;
}
WriteStringSetting(std::string(player_prefix).append("profile_name"), player.profile_name,
std::make_optional(std::string("")));
}
WriteIntegerSetting(std::string(player_prefix).append("type"), u8(player.controller_type),
std::make_optional(u8(Settings::ControllerType::ProController)));
if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) {
if (global) {
const auto& player_global = Settings::values.players.GetValue(true)[player_index];
WriteStringSetting(std::string(player_prefix).append("profile_name"),
player_global.profile_name, std::make_optional(std::string("")));
}
WriteBooleanSetting(std::string(player_prefix).append("connected"), player.connected,
std::make_optional(player_index == 0));
WriteIntegerSetting(std::string(player_prefix).append("vibration_enabled"),
player.vibration_enabled, std::make_optional(true));
WriteIntegerSetting(std::string(player_prefix).append("vibration_strength"),
player.vibration_strength, std::make_optional(100));
WriteIntegerSetting(std::string(player_prefix).append("body_color_left"),
player.body_color_left,
std::make_optional(Settings::JOYCON_BODY_NEON_BLUE));
WriteIntegerSetting(std::string(player_prefix).append("body_color_right"),
player.body_color_right,
std::make_optional(Settings::JOYCON_BODY_NEON_RED));
WriteIntegerSetting(std::string(player_prefix).append("button_color_left"),
player.button_color_left,
std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE));
WriteIntegerSetting(std::string(player_prefix).append("button_color_right"),
player.button_color_right,
std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED));
}
}
void Config::SaveTouchscreenValues() {
const auto& touchscreen = Settings::values.touchscreen;
WriteBooleanSetting(std::string("touchscreen_enabled"), touchscreen.enabled,
std::make_optional(true));
WriteIntegerSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle,
std::make_optional(u32(0)));
WriteIntegerSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x,
std::make_optional(u32(90)));
WriteIntegerSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y,
std::make_optional(u32(90)));
}
void Config::SaveMotionTouchValues() {
BeginArray(std::string("touch_from_button_maps"));
for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
SetArrayIndex(int(p));
WriteStringSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name, std::make_optional(std::string("default")));
BeginArray(std::string("entries"));
for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
++q) {
SetArrayIndex(int(q));
WriteStringSetting(std::string("bind"), Settings::values.touch_from_button_maps[p].buttons[q]);
}
EndArray(); // entries
}
EndArray(); // touch_from_button_maps
}
void Config::SaveValues() {
if (global) {
LOG_DEBUG(Config, "Saving global generic configuration values");
SaveDataStorageValues();
SaveDisabledAddOnValues();
SaveWebServiceValues();
SaveMiscellaneousValues();
} else {
LOG_DEBUG(Config, "Saving only generic configuration values");
}
SaveDebuggingValues();
SaveLibraryAppletValues();
SaveNetworkValues();
SaveControlValues();
SaveCoreValues();
SaveCpuValues();
SaveRendererValues();
SaveAudioValues();
SaveSystemValues();
WriteToIni();
}
void Config::SaveAudioValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Audio));
WriteCategory(Settings::Category::Audio);
WriteCategory(Settings::Category::UiAudio);
EndGroup();
}
void Config::SaveControlValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
WriteCategory(Settings::Category::Controls);
Settings::values.players.SetGlobal(!IsCustomConfig());
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
SavePlayerValues(p);
}
if (IsCustomConfig()) {
EndGroup();
return;
}
SaveTouchscreenValues();
SaveMotionTouchValues();
EndGroup();
}
void Config::SaveCoreValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Core));
WriteCategory(Settings::Category::Core);
EndGroup();
}
void Config::SaveDataStorageValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
using namespace Common::FS;
const auto writePath = [this](const char* setting, const EdenPath& path) {
WriteStringSetting(std::string(setting), FS::GetEdenPathString(path),
std::make_optional(FS::GetEdenPathString(path)));
};
writePath("nand_directory", EdenPath::NANDDir);
writePath("sdmc_directory", EdenPath::SDMCDir);
writePath("load_directory", EdenPath::LoadDir);
writePath("dump_directory", EdenPath::DumpDir);
writePath("tas_directory", EdenPath::TASDir);
const auto save_path = FS::GetEdenPathString(EdenPath::SaveDir);
const auto nand_path = FS::GetEdenPathString(EdenPath::NANDDir);
if (save_path == nand_path) {
WriteStringSetting(std::string("save_directory"), std::string(""),
std::make_optional(std::string("")));
} else {
WriteStringSetting(std::string("save_directory"), save_path,
std::make_optional(std::string("")));
}
WriteCategory(Settings::Category::DataStorage);
EndGroup();
}
void Config::SaveDebuggingValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
if (global) {
// Intentionally not using the QT default setting as this is intended to be changed in the ini
WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times);
WriteCategory(Settings::Category::Debugging);
WriteCategory(Settings::Category::DebuggingGraphics);
} else {
WriteSettingGeneric(&Settings::values.program_args);
WriteSettingGeneric(&Settings::values.debug_knobs);
}
EndGroup();
}
void Config::SaveNetworkValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
WriteCategory(Settings::Category::Network);
EndGroup();
}
void Config::SaveDisabledAddOnValues() {
// Custom config section
BeginGroup(std::string("DisabledAddOns"));
int i = 0;
BeginArray(std::string(""));
for (const auto& elem : Settings::values.disabled_addons) {
SetArrayIndex(i);
WriteIntegerSetting(std::string("title_id"), elem.first, std::make_optional(u64(0)));
BeginArray(std::string("disabled"));
for (std::size_t j = 0; j < elem.second.size(); ++j) {
SetArrayIndex(int(j));
WriteStringSetting(std::string("d"), elem.second[j], std::make_optional(std::string("")));
}
EndArray(); // disabled
++i;
}
EndArray(); // Base disabled addons array - Has no base key
EndGroup();
}
void Config::SaveMiscellaneousValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous));
WriteCategory(Settings::Category::Miscellaneous);
EndGroup();
}
void Config::SaveCpuValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Cpu));
WriteCategory(Settings::Category::Cpu);
WriteCategory(Settings::Category::CpuDebug);
WriteCategory(Settings::Category::CpuUnsafe);
EndGroup();
}
void Config::SaveRendererValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Renderer));
WriteCategory(Settings::Category::Renderer);
WriteCategory(Settings::Category::RendererAdvanced);
WriteCategory(Settings::Category::RendererHacks);
WriteCategory(Settings::Category::RendererExtensions);
WriteCategory(Settings::Category::RendererDebug);
EndGroup();
}
void Config::SaveScreenshotValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
WriteStringSetting(std::string("screenshot_path"),
FS::GetEdenPathString(FS::EdenPath::ScreenshotsDir));
WriteCategory(Settings::Category::Screenshots);
EndGroup();
}
void Config::SaveSystemValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::System));
WriteCategory(Settings::Category::System);
WriteCategory(Settings::Category::SystemAudio);
EndGroup();
}
void Config::SaveWebServiceValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::WebService));
WriteCategory(Settings::Category::WebService);
EndGroup();
}
void Config::SaveLibraryAppletValues() {
BeginGroup(Settings::TranslateCategory(Settings::Category::LibraryApplet));
WriteCategory(Settings::Category::LibraryApplet);
EndGroup();
}
bool Config::ReadBooleanSetting(const std::string& key, const std::optional<bool> default_value) {
std::string full_key = GetFullKey(key, false);
if (!default_value.has_value()) {
return config->GetBoolValue(GetSection().c_str(), full_key.c_str(), false);
}
if (config->GetBoolValue(GetSection().c_str(),
std::string(full_key).append("\\default").c_str(), false)) {
return bool(default_value.value());
} else {
return config->GetBoolValue(GetSection().c_str(), full_key.c_str(),
bool(default_value.value()));
}
}
s64 Config::ReadIntegerSetting(const std::string& key, const std::optional<s64> default_value) {
std::string full_key = GetFullKey(key, false);
if (!default_value.has_value()) {
try {
return std::stoll(std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), "0")));
} catch (...) {
return 0;
}
}
s64 result = 0;
if (config->GetBoolValue(GetSection().c_str(), std::string(full_key).append("\\default").c_str(), true)) {
result = default_value.value();
} else {
try {
result = std::stoll(std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), ToString(default_value.value()).c_str())));
} catch (...) {
result = default_value.value();
}
}
return result;
}
u64 Config::ReadUnsignedIntegerSetting(const std::string& key,
const std::optional<u64> default_value) {
std::string full_key = GetFullKey(key, false);
if (!default_value.has_value()) {
try {
return std::stoull(
std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), "0")));
} catch (...) {
return 0;
}
}
u64 result = 0;
if (config->GetBoolValue(GetSection().c_str(),
std::string(full_key).append("\\default").c_str(), true)) {
result = default_value.value();
} else {
try {
result = std::stoull(std::string(config->GetValue(
GetSection().c_str(), full_key.c_str(), ToString(default_value.value()).c_str())));
} catch (...) {
result = default_value.value();
}
}
return result;
}
double Config::ReadDoubleSetting(const std::string& key,
const std::optional<double> default_value) {
std::string full_key = GetFullKey(key, false);
if (!default_value.has_value()) {
return config->GetDoubleValue(GetSection().c_str(), full_key.c_str(), 0);
}
double result;
if (config->GetBoolValue(GetSection().c_str(),
std::string(full_key).append("\\default").c_str(), true)) {
result = default_value.value();
} else {
result =
config->GetDoubleValue(GetSection().c_str(), full_key.c_str(), default_value.value());
}
return result;
}
std::string Config::ReadStringSetting(const std::string& key,
const std::optional<std::string> default_value) {
std::string result;
std::string full_key = GetFullKey(key, false);
if (!default_value.has_value()) {
result = config->GetValue(GetSection().c_str(), full_key.c_str(), "");
boost::replace_all(result, "\"", "");
return result;
}
if (config->GetBoolValue(GetSection().c_str(),
std::string(full_key).append("\\default").c_str(), true)) {
result = default_value.value();
} else {
result =
config->GetValue(GetSection().c_str(), full_key.c_str(), default_value.value().c_str());
}
boost::replace_all(result, "\"", "");
boost::replace_all(result, "//", "/");
return result;
}
bool Config::Exists(const std::string& section, const std::string& key) const {
const std::string value = config->GetValue(section.c_str(), key.c_str(), "");
return !value.empty();
}
void Config::WriteBooleanSetting(const std::string& key, const bool& value,
const std::optional<bool>& default_value,
const std::optional<bool>& use_global) {
std::optional<std::string> string_default = std::nullopt;
if (default_value.has_value()) {
string_default = std::make_optional(ToString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
}
void Config::WriteDoubleSetting(const std::string& key, const double& value,
const std::optional<double>& default_value,
const std::optional<bool>& use_global) {
std::optional<std::string> string_default = std::nullopt;
if (default_value.has_value()) {
string_default = std::make_optional(ToString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
}
void Config::WriteStringSetting(const std::string& key, const std::string& value,
const std::optional<std::string>& default_value,
const std::optional<bool>& use_global) {
std::optional string_default = default_value;
if (default_value.has_value()) {
string_default.value().append(AdjustOutputString(default_value.value()));
}
WritePreparedSetting(key, AdjustOutputString(value), string_default, use_global);
}
void Config::WritePreparedSetting(const std::string& key, const std::string& adjusted_value,
const std::optional<std::string>& adjusted_default_value,
const std::optional<bool>& use_global) {
std::string full_key = GetFullKey(key, false);
if (adjusted_default_value.has_value() && use_global.has_value()) {
if (!global) {
WriteString(std::string(full_key).append("\\global"), ToString(use_global.value()));
}
if (global || use_global.value() == false) {
WriteString(std::string(full_key).append("\\default"),
ToString(adjusted_default_value == adjusted_value));
WriteString(full_key, adjusted_value);
}
} else if (adjusted_default_value.has_value() && !use_global.has_value()) {
WriteString(std::string(full_key).append("\\default"),
ToString(adjusted_default_value == adjusted_value));
WriteString(full_key, adjusted_value);
} else {
WriteString(full_key, adjusted_value);
}
}
void Config::WriteString(const std::string& key, const std::string& value) {
config->SetValue(GetSection().c_str(), key.c_str(), value.c_str());
}
void Config::Reload() {
ReadValues();
// To apply default value changes
SaveValues();
}
void Config::ClearControlPlayerValues() const {
// Removes the entire [Controls] section
const char* section = Settings::TranslateCategory(Settings::Category::Controls);
config->Delete(section, nullptr, true);
}
const std::string& Config::GetConfigFilePath() const {
return config_loc;
}
void Config::ReadCategory(const Settings::Category category) {
const auto& settings = FindRelevantList(category);
std::ranges::for_each(settings, [&](const auto& setting) { ReadSettingGeneric(setting); });
}
void Config::WriteCategory(const Settings::Category category) {
const auto& settings = FindRelevantList(category);
std::ranges::for_each(settings, [&](const auto& setting) { WriteSettingGeneric(setting); });
}
void Config::ReadSettingGeneric(Settings::BasicSetting* const setting) {
if (!setting->Save() || (!setting->Switchable() && !global)) {
return;
}
const std::string key = AdjustKey(setting->GetLabel());
const std::string default_value(setting->DefaultToString());
bool use_global = true;
if (setting->Switchable() && !global) {
use_global = ReadBooleanSetting(std::string(key).append("\\use_global"), std::make_optional(true));
setting->SetGlobal(use_global);
}
if (global || !use_global) {
const bool is_default = ReadBooleanSetting(std::string(key).append("\\default"), std::make_optional(true));
if (!is_default) {
setting->LoadString(ReadStringSetting(key, default_value));
} else {
// Empty string resets the Setting to default
setting->LoadString("");
}
}
}
void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) {
if (!setting->Save()) {
return;
}
std::string key = AdjustKey(setting->GetLabel());
if (setting->Switchable()) {
if (!global) {
WriteBooleanSetting(std::string(key).append("\\use_global"), setting->UsingGlobal());
}
if (global || !setting->UsingGlobal()) {
auto value = global ? setting->ToStringGlobal() : setting->ToString();
WriteBooleanSetting(std::string(key).append("\\default"),
value == setting->DefaultToString());
WriteStringSetting(key, value);
}
} else if (global) {
WriteBooleanSetting(std::string(key).append("\\default"),
setting->ToString() == setting->DefaultToString());
WriteStringSetting(key, setting->ToString());
}
}
void Config::BeginGroup(const std::string& group) {
// You can't begin a group while reading/writing from a config array
ASSERT(array_stack.empty());
key_stack.push_back(AdjustKey(group));
}
void Config::EndGroup() {
// You can't end a group if you haven't started one yet
ASSERT(!key_stack.empty());
// You can't end a group when reading/writing from a config array
ASSERT(array_stack.empty());
key_stack.pop_back();
}
std::string Config::GetSection() {
if (key_stack.empty()) {
return std::string{""};
}
return key_stack.front();
}
std::string Config::GetGroup() const {
if (key_stack.size() <= 1) {
return std::string{""};
}
std::string key;
for (size_t i = 1; i < key_stack.size(); ++i) {
key.append(key_stack[i]).append("\\");
}
return key;
}
std::string Config::AdjustKey(const std::string& key) {
std::string adjusted_key(key);
boost::replace_all(adjusted_key, "/", "\\");
boost::replace_all(adjusted_key, " ", "%20");
return adjusted_key;
}
std::string Config::AdjustOutputString(const std::string& string) {
std::string adjusted_string(string);
boost::replace_all(adjusted_string, "\\", "/");
// Windows requires that two forward slashes are used at the start of a path for unmapped
// network drives so we have to watch for that here
#ifndef __ANDROID__
if (string.substr(0, 2) == "//") {
boost::replace_all(adjusted_string, "//", "/");
adjusted_string.insert(0, "/");
} else {
boost::replace_all(adjusted_string, "//", "/");
}
#endif
// Needed for backwards compatibility with QSettings deserialization
for (const auto& special_character : special_characters) {
if (adjusted_string.find(special_character) != std::string::npos) {
adjusted_string.insert(0, "\"");
adjusted_string.append("\"");
break;
}
}
return adjusted_string;
}
std::string Config::GetFullKey(const std::string& key, bool skipArrayIndex) {
if (array_stack.empty()) {
return std::string(GetGroup()).append(AdjustKey(key));
}
std::string array_key;
for (size_t i = 0; i < array_stack.size(); ++i) {
if (!array_stack[i].name.empty()) {
array_key.append(array_stack[i].name).append("\\");
}
if (!skipArrayIndex || (array_stack.size() - 1 != i && array_stack.size() > 1)) {
array_key.append(ToString(array_stack[i].index)).append("\\");
}
}
std::string final_key = std::string(GetGroup()).append(array_key).append(AdjustKey(key));
return final_key;
}
int Config::BeginArray(const std::string& array) {
array_stack.push_back(ConfigArray{AdjustKey(array), 0, 0});
const int size = config->GetLongValue(GetSection().c_str(), GetFullKey(std::string("size"), true).c_str(), 0);
array_stack.back().size = (std::max)(0, size);
return array_stack.back().size;
}
void Config::EndArray() {
// You can't end a config array before starting one
ASSERT(!array_stack.empty());
// Set the array size to 0 if the array is ended without changing the index
int size = 0;
if (array_stack.back().index != 0) {
size = array_stack.back().size;
}
// Write out the size to config
if (key_stack.size() == 1 && array_stack.back().name.empty()) {
// Edge-case where the first array created doesn't have a name
config->SetValue(GetSection().c_str(), std::string("size").c_str(), ToString(size).c_str());
} else {
auto const key = GetFullKey(std::string("size"), true);
config->SetValue(GetSection().c_str(), key.c_str(), ToString(size).c_str());
}
array_stack.pop_back();
}
void Config::SetArrayIndex(const int index) {
// You can't set the array index if you haven't started one yet
ASSERT(!array_stack.empty());
const int array_index = index + 1;
// You can't exceed the known max size of the array by more than 1
ASSERT(array_stack.front().size + 1 >= array_index);
// Change the config array size to the current index since you may want
// to reduce the number of elements that you read back from the config
// in the future.
array_stack.back().size = array_index;
array_stack.back().index = array_index;
}