diff options
Diffstat (limited to 'src/frontend_common/config.cpp')
| -rw-r--r-- | src/frontend_common/config.cpp | 931 |
1 files changed, 931 insertions, 0 deletions
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp new file mode 100644 index 000000000..d317d81fd --- /dev/null +++ b/src/frontend_common/config.cpp | |||
| @@ -0,0 +1,931 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <array> | ||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/settings.h" | ||
| 9 | #include "common/settings_common.h" | ||
| 10 | #include "common/settings_enums.h" | ||
| 11 | #include "config.h" | ||
| 12 | #include "core/core.h" | ||
| 13 | #include "core/hle/service/acc/profile_manager.h" | ||
| 14 | #include "core/hle/service/hid/controllers/npad.h" | ||
| 15 | #include "network/network.h" | ||
| 16 | |||
| 17 | #include <boost/algorithm/string/replace.hpp> | ||
| 18 | |||
| 19 | namespace FS = Common::FS; | ||
| 20 | |||
| 21 | Config::Config(const ConfigType config_type) | ||
| 22 | : type(config_type), global{config_type == ConfigType::GlobalConfig} {} | ||
| 23 | |||
| 24 | void Config::Initialize(const std::string& config_name) { | ||
| 25 | const std::filesystem::path fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir); | ||
| 26 | const auto config_file = fmt::format("{}.ini", config_name); | ||
| 27 | |||
| 28 | switch (type) { | ||
| 29 | case ConfigType::GlobalConfig: | ||
| 30 | config_loc = FS::PathToUTF8String(fs_config_loc / config_file); | ||
| 31 | void(FS::CreateParentDir(config_loc)); | ||
| 32 | SetUpIni(); | ||
| 33 | Reload(); | ||
| 34 | break; | ||
| 35 | case ConfigType::PerGameConfig: | ||
| 36 | config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file)); | ||
| 37 | void(FS::CreateParentDir(config_loc)); | ||
| 38 | SetUpIni(); | ||
| 39 | Reload(); | ||
| 40 | break; | ||
| 41 | case ConfigType::InputProfile: | ||
| 42 | config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file); | ||
| 43 | void(FS::CreateParentDir(config_loc)); | ||
| 44 | SetUpIni(); | ||
| 45 | break; | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | void Config::Initialize(const std::optional<std::string> config_path) { | ||
| 50 | const std::filesystem::path default_sdl_config_path = | ||
| 51 | FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini"; | ||
| 52 | config_loc = config_path.value_or(FS::PathToUTF8String(default_sdl_config_path)); | ||
| 53 | void(FS::CreateParentDir(config_loc)); | ||
| 54 | SetUpIni(); | ||
| 55 | Reload(); | ||
| 56 | } | ||
| 57 | |||
| 58 | void Config::WriteToIni() const { | ||
| 59 | if (const SI_Error rc = config->SaveFile(config_loc.c_str()); rc < 0) { | ||
| 60 | LOG_ERROR(Frontend, "Config file could not be saved!"); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | void Config::SetUpIni() { | ||
| 65 | config = std::make_unique<CSimpleIniA>(); | ||
| 66 | config->SetUnicode(true); | ||
| 67 | config->SetSpaces(false); | ||
| 68 | config->LoadFile(config_loc.c_str()); | ||
| 69 | } | ||
| 70 | |||
| 71 | bool Config::IsCustomConfig() const { | ||
| 72 | return type == ConfigType::PerGameConfig; | ||
| 73 | } | ||
| 74 | |||
| 75 | void Config::ReadPlayerValues(const std::size_t player_index) { | ||
| 76 | std::string player_prefix; | ||
| 77 | if (type != ConfigType::InputProfile) { | ||
| 78 | player_prefix.append("player_").append(ToString(player_index)).append("_"); | ||
| 79 | } | ||
| 80 | |||
| 81 | auto& player = Settings::values.players.GetValue()[player_index]; | ||
| 82 | if (IsCustomConfig()) { | ||
| 83 | const auto profile_name = | ||
| 84 | ReadStringSetting(std::string(player_prefix).append("profile_name")); | ||
| 85 | if (profile_name.empty()) { | ||
| 86 | // Use the global input config | ||
| 87 | player = Settings::values.players.GetValue(true)[player_index]; | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | player.profile_name = profile_name; | ||
| 91 | } | ||
| 92 | |||
| 93 | if (player_prefix.empty() && Settings::IsConfiguringGlobal()) { | ||
| 94 | const auto controller = static_cast<Settings::ControllerType>( | ||
| 95 | ReadIntegerSetting(std::string(player_prefix).append("type"), | ||
| 96 | static_cast<u8>(Settings::ControllerType::ProController))); | ||
| 97 | |||
| 98 | if (controller == Settings::ControllerType::LeftJoycon || | ||
| 99 | controller == Settings::ControllerType::RightJoycon) { | ||
| 100 | player.controller_type = controller; | ||
| 101 | } | ||
| 102 | } else { | ||
| 103 | std::string connected_key = player_prefix; | ||
| 104 | player.connected = ReadBooleanSetting(connected_key.append("connected"), | ||
| 105 | std::make_optional(player_index == 0)); | ||
| 106 | |||
| 107 | player.controller_type = static_cast<Settings::ControllerType>( | ||
| 108 | ReadIntegerSetting(std::string(player_prefix).append("type"), | ||
| 109 | static_cast<u8>(Settings::ControllerType::ProController))); | ||
| 110 | |||
| 111 | player.vibration_enabled = ReadBooleanSetting( | ||
| 112 | std::string(player_prefix).append("vibration_enabled"), std::make_optional(true)); | ||
| 113 | |||
| 114 | player.vibration_strength = static_cast<int>( | ||
| 115 | ReadIntegerSetting(std::string(player_prefix).append("vibration_strength"), 100)); | ||
| 116 | |||
| 117 | player.body_color_left = static_cast<u32>(ReadIntegerSetting( | ||
| 118 | std::string(player_prefix).append("body_color_left"), Settings::JOYCON_BODY_NEON_BLUE)); | ||
| 119 | player.body_color_right = static_cast<u32>(ReadIntegerSetting( | ||
| 120 | std::string(player_prefix).append("body_color_right"), Settings::JOYCON_BODY_NEON_RED)); | ||
| 121 | player.button_color_left = static_cast<u32>( | ||
| 122 | ReadIntegerSetting(std::string(player_prefix).append("button_color_left"), | ||
| 123 | Settings::JOYCON_BUTTONS_NEON_BLUE)); | ||
| 124 | player.button_color_right = static_cast<u32>( | ||
| 125 | ReadIntegerSetting(std::string(player_prefix).append("button_color_right"), | ||
| 126 | Settings::JOYCON_BUTTONS_NEON_RED)); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | void Config::ReadTouchscreenValues() { | ||
| 131 | Settings::values.touchscreen.enabled = | ||
| 132 | ReadBooleanSetting(std::string("touchscreen_enabled"), std::make_optional(true)); | ||
| 133 | Settings::values.touchscreen.rotation_angle = | ||
| 134 | static_cast<u32>(ReadIntegerSetting(std::string("touchscreen_angle"), 0)); | ||
| 135 | Settings::values.touchscreen.diameter_x = | ||
| 136 | static_cast<u32>(ReadIntegerSetting(std::string("touchscreen_diameter_x"), 15)); | ||
| 137 | Settings::values.touchscreen.diameter_y = | ||
| 138 | static_cast<u32>(ReadIntegerSetting(std::string("touchscreen_diameter_y"), 15)); | ||
| 139 | } | ||
| 140 | |||
| 141 | void Config::ReadAudioValues() { | ||
| 142 | BeginGroup(Settings::TranslateCategory(Settings::Category::Audio)); | ||
| 143 | |||
| 144 | ReadCategory(Settings::Category::Audio); | ||
| 145 | ReadCategory(Settings::Category::UiAudio); | ||
| 146 | |||
| 147 | EndGroup(); | ||
| 148 | } | ||
| 149 | |||
| 150 | void Config::ReadControlValues() { | ||
| 151 | BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); | ||
| 152 | |||
| 153 | ReadCategory(Settings::Category::Controls); | ||
| 154 | |||
| 155 | Settings::values.players.SetGlobal(!IsCustomConfig()); | ||
| 156 | for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { | ||
| 157 | ReadPlayerValues(p); | ||
| 158 | } | ||
| 159 | |||
| 160 | // Disable docked mode if handheld is selected | ||
| 161 | const auto controller_type = Settings::values.players.GetValue()[0].controller_type; | ||
| 162 | if (controller_type == Settings::ControllerType::Handheld) { | ||
| 163 | Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig()); | ||
| 164 | Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld); | ||
| 165 | } | ||
| 166 | |||
| 167 | if (IsCustomConfig()) { | ||
| 168 | EndGroup(); | ||
| 169 | return; | ||
| 170 | } | ||
| 171 | ReadTouchscreenValues(); | ||
| 172 | ReadMotionTouchValues(); | ||
| 173 | |||
| 174 | EndGroup(); | ||
| 175 | } | ||
| 176 | |||
| 177 | void Config::ReadMotionTouchValues() { | ||
| 178 | int num_touch_from_button_maps = BeginArray(std::string("touch_from_button_maps")); | ||
| 179 | |||
| 180 | if (num_touch_from_button_maps > 0) { | ||
| 181 | for (int i = 0; i < num_touch_from_button_maps; ++i) { | ||
| 182 | SetArrayIndex(i); | ||
| 183 | |||
| 184 | Settings::TouchFromButtonMap map; | ||
| 185 | map.name = ReadStringSetting(std::string("name"), std::string("default")); | ||
| 186 | |||
| 187 | const int num_touch_maps = BeginArray(std::string("entries")); | ||
| 188 | map.buttons.reserve(num_touch_maps); | ||
| 189 | for (int j = 0; j < num_touch_maps; j++) { | ||
| 190 | SetArrayIndex(j); | ||
| 191 | std::string touch_mapping = ReadStringSetting(std::string("bind")); | ||
| 192 | map.buttons.emplace_back(std::move(touch_mapping)); | ||
| 193 | } | ||
| 194 | EndArray(); // entries | ||
| 195 | Settings::values.touch_from_button_maps.emplace_back(std::move(map)); | ||
| 196 | } | ||
| 197 | } else { | ||
| 198 | Settings::values.touch_from_button_maps.emplace_back( | ||
| 199 | Settings::TouchFromButtonMap{"default", {}}); | ||
| 200 | num_touch_from_button_maps = 1; | ||
| 201 | } | ||
| 202 | EndArray(); // touch_from_button_maps | ||
| 203 | |||
| 204 | Settings::values.touch_from_button_map_index = std::clamp( | ||
| 205 | Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1); | ||
| 206 | } | ||
| 207 | |||
| 208 | void Config::ReadCoreValues() { | ||
| 209 | BeginGroup(Settings::TranslateCategory(Settings::Category::Core)); | ||
| 210 | |||
| 211 | ReadCategory(Settings::Category::Core); | ||
| 212 | |||
| 213 | EndGroup(); | ||
| 214 | } | ||
| 215 | |||
| 216 | void Config::ReadDataStorageValues() { | ||
| 217 | BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage)); | ||
| 218 | |||
| 219 | FS::SetYuzuPath(FS::YuzuPath::NANDDir, ReadStringSetting(std::string("nand_directory"))); | ||
| 220 | FS::SetYuzuPath(FS::YuzuPath::SDMCDir, ReadStringSetting(std::string("sdmc_directory"))); | ||
| 221 | FS::SetYuzuPath(FS::YuzuPath::LoadDir, ReadStringSetting(std::string("load_directory"))); | ||
| 222 | FS::SetYuzuPath(FS::YuzuPath::DumpDir, ReadStringSetting(std::string("dump_directory"))); | ||
| 223 | FS::SetYuzuPath(FS::YuzuPath::TASDir, ReadStringSetting(std::string("tas_directory"))); | ||
| 224 | |||
| 225 | ReadCategory(Settings::Category::DataStorage); | ||
| 226 | |||
| 227 | EndGroup(); | ||
| 228 | } | ||
| 229 | |||
| 230 | void Config::ReadDebuggingValues() { | ||
| 231 | BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging)); | ||
| 232 | |||
| 233 | // Intentionally not using the QT default setting as this is intended to be changed in the ini | ||
| 234 | Settings::values.record_frame_times = | ||
| 235 | ReadBooleanSetting(std::string("record_frame_times"), std::make_optional(false)); | ||
| 236 | |||
| 237 | ReadCategory(Settings::Category::Debugging); | ||
| 238 | ReadCategory(Settings::Category::DebuggingGraphics); | ||
| 239 | |||
| 240 | EndGroup(); | ||
| 241 | } | ||
| 242 | |||
| 243 | void Config::ReadServiceValues() { | ||
| 244 | BeginGroup(Settings::TranslateCategory(Settings::Category::Services)); | ||
| 245 | |||
| 246 | ReadCategory(Settings::Category::Services); | ||
| 247 | |||
| 248 | EndGroup(); | ||
| 249 | } | ||
| 250 | |||
| 251 | void Config::ReadDisabledAddOnValues() { | ||
| 252 | // Custom config section | ||
| 253 | BeginGroup(std::string("DisabledAddOns")); | ||
| 254 | |||
| 255 | const int size = BeginArray(std::string("")); | ||
| 256 | for (int i = 0; i < size; ++i) { | ||
| 257 | SetArrayIndex(i); | ||
| 258 | const auto title_id = ReadIntegerSetting(std::string("title_id"), 0); | ||
| 259 | std::vector<std::string> out; | ||
| 260 | const int d_size = BeginArray("disabled"); | ||
| 261 | for (int j = 0; j < d_size; ++j) { | ||
| 262 | SetArrayIndex(j); | ||
| 263 | out.push_back(ReadStringSetting(std::string("d"), std::string(""))); | ||
| 264 | } | ||
| 265 | EndArray(); // d | ||
| 266 | Settings::values.disabled_addons.insert_or_assign(title_id, out); | ||
| 267 | } | ||
| 268 | EndArray(); // Base disabled addons array - Has no base key | ||
| 269 | |||
| 270 | EndGroup(); | ||
| 271 | } | ||
| 272 | |||
| 273 | void Config::ReadMiscellaneousValues() { | ||
| 274 | BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous)); | ||
| 275 | |||
| 276 | ReadCategory(Settings::Category::Miscellaneous); | ||
| 277 | |||
| 278 | EndGroup(); | ||
| 279 | } | ||
| 280 | |||
| 281 | void Config::ReadCpuValues() { | ||
| 282 | BeginGroup(Settings::TranslateCategory(Settings::Category::Cpu)); | ||
| 283 | |||
| 284 | ReadCategory(Settings::Category::Cpu); | ||
| 285 | ReadCategory(Settings::Category::CpuDebug); | ||
| 286 | ReadCategory(Settings::Category::CpuUnsafe); | ||
| 287 | |||
| 288 | EndGroup(); | ||
| 289 | } | ||
| 290 | |||
| 291 | void Config::ReadRendererValues() { | ||
| 292 | BeginGroup(Settings::TranslateCategory(Settings::Category::Renderer)); | ||
| 293 | |||
| 294 | ReadCategory(Settings::Category::Renderer); | ||
| 295 | ReadCategory(Settings::Category::RendererAdvanced); | ||
| 296 | ReadCategory(Settings::Category::RendererDebug); | ||
| 297 | |||
| 298 | EndGroup(); | ||
| 299 | } | ||
| 300 | |||
| 301 | void Config::ReadScreenshotValues() { | ||
| 302 | BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots)); | ||
| 303 | |||
| 304 | ReadCategory(Settings::Category::Screenshots); | ||
| 305 | FS::SetYuzuPath(FS::YuzuPath::ScreenshotsDir, | ||
| 306 | ReadStringSetting(std::string("screenshot_path"), | ||
| 307 | FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir))); | ||
| 308 | |||
| 309 | EndGroup(); | ||
| 310 | } | ||
| 311 | |||
| 312 | void Config::ReadSystemValues() { | ||
| 313 | BeginGroup(Settings::TranslateCategory(Settings::Category::System)); | ||
| 314 | |||
| 315 | ReadCategory(Settings::Category::System); | ||
| 316 | ReadCategory(Settings::Category::SystemAudio); | ||
| 317 | |||
| 318 | EndGroup(); | ||
| 319 | } | ||
| 320 | |||
| 321 | void Config::ReadWebServiceValues() { | ||
| 322 | BeginGroup(Settings::TranslateCategory(Settings::Category::WebService)); | ||
| 323 | |||
| 324 | ReadCategory(Settings::Category::WebService); | ||
| 325 | |||
| 326 | EndGroup(); | ||
| 327 | } | ||
| 328 | |||
| 329 | void Config::ReadNetworkValues() { | ||
| 330 | BeginGroup(Settings::TranslateCategory(Settings::Category::Services)); | ||
| 331 | |||
| 332 | ReadCategory(Settings::Category::Network); | ||
| 333 | |||
| 334 | EndGroup(); | ||
| 335 | } | ||
| 336 | |||
| 337 | void Config::ReadValues() { | ||
| 338 | if (global) { | ||
| 339 | ReadDataStorageValues(); | ||
| 340 | ReadDebuggingValues(); | ||
| 341 | ReadDisabledAddOnValues(); | ||
| 342 | ReadNetworkValues(); | ||
| 343 | ReadServiceValues(); | ||
| 344 | ReadWebServiceValues(); | ||
| 345 | ReadMiscellaneousValues(); | ||
| 346 | } | ||
| 347 | ReadControlValues(); | ||
| 348 | ReadCoreValues(); | ||
| 349 | ReadCpuValues(); | ||
| 350 | ReadRendererValues(); | ||
| 351 | ReadAudioValues(); | ||
| 352 | ReadSystemValues(); | ||
| 353 | } | ||
| 354 | |||
| 355 | void Config::SavePlayerValues(const std::size_t player_index) { | ||
| 356 | std::string player_prefix; | ||
| 357 | if (type != ConfigType::InputProfile) { | ||
| 358 | player_prefix = std::string("player_").append(ToString(player_index)).append("_"); | ||
| 359 | } | ||
| 360 | |||
| 361 | const auto& player = Settings::values.players.GetValue()[player_index]; | ||
| 362 | if (IsCustomConfig()) { | ||
| 363 | if (player.profile_name.empty()) { | ||
| 364 | // No custom profile selected | ||
| 365 | return; | ||
| 366 | } | ||
| 367 | WriteSetting(std::string(player_prefix).append("profile_name"), player.profile_name, | ||
| 368 | std::make_optional(std::string(""))); | ||
| 369 | } | ||
| 370 | |||
| 371 | WriteSetting(std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type), | ||
| 372 | std::make_optional(static_cast<u8>(Settings::ControllerType::ProController))); | ||
| 373 | |||
| 374 | if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) { | ||
| 375 | WriteSetting(std::string(player_prefix).append("connected"), player.connected, | ||
| 376 | std::make_optional(player_index == 0)); | ||
| 377 | WriteSetting(std::string(player_prefix).append("vibration_enabled"), | ||
| 378 | player.vibration_enabled, std::make_optional(true)); | ||
| 379 | WriteSetting(std::string(player_prefix).append("vibration_strength"), | ||
| 380 | player.vibration_strength, std::make_optional(100)); | ||
| 381 | WriteSetting(std::string(player_prefix).append("body_color_left"), player.body_color_left, | ||
| 382 | std::make_optional(Settings::JOYCON_BODY_NEON_BLUE)); | ||
| 383 | WriteSetting(std::string(player_prefix).append("body_color_right"), player.body_color_right, | ||
| 384 | std::make_optional(Settings::JOYCON_BODY_NEON_RED)); | ||
| 385 | WriteSetting(std::string(player_prefix).append("button_color_left"), | ||
| 386 | player.button_color_left, | ||
| 387 | std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE)); | ||
| 388 | WriteSetting(std::string(player_prefix).append("button_color_right"), | ||
| 389 | player.button_color_right, | ||
| 390 | std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED)); | ||
| 391 | } | ||
| 392 | } | ||
| 393 | |||
| 394 | void Config::SaveTouchscreenValues() { | ||
| 395 | const auto& touchscreen = Settings::values.touchscreen; | ||
| 396 | |||
| 397 | WriteSetting(std::string("touchscreen_enabled"), touchscreen.enabled, std::make_optional(true)); | ||
| 398 | |||
| 399 | WriteSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle, | ||
| 400 | std::make_optional(static_cast<u32>(0))); | ||
| 401 | WriteSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x, | ||
| 402 | std::make_optional(static_cast<u32>(15))); | ||
| 403 | WriteSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y, | ||
| 404 | std::make_optional(static_cast<u32>(15))); | ||
| 405 | } | ||
| 406 | |||
| 407 | void Config::SaveMotionTouchValues() { | ||
| 408 | BeginArray(std::string("touch_from_button_maps")); | ||
| 409 | for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) { | ||
| 410 | SetArrayIndex(static_cast<int>(p)); | ||
| 411 | WriteSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name, | ||
| 412 | std::make_optional(std::string("default"))); | ||
| 413 | |||
| 414 | BeginArray(std::string("entries")); | ||
| 415 | for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size(); | ||
| 416 | ++q) { | ||
| 417 | SetArrayIndex(static_cast<int>(q)); | ||
| 418 | WriteSetting(std::string("bind"), | ||
| 419 | Settings::values.touch_from_button_maps[p].buttons[q]); | ||
| 420 | } | ||
| 421 | EndArray(); // entries | ||
| 422 | } | ||
| 423 | EndArray(); // touch_from_button_maps | ||
| 424 | } | ||
| 425 | |||
| 426 | void Config::SaveValues() { | ||
| 427 | if (global) { | ||
| 428 | SaveDataStorageValues(); | ||
| 429 | SaveDebuggingValues(); | ||
| 430 | SaveDisabledAddOnValues(); | ||
| 431 | SaveNetworkValues(); | ||
| 432 | SaveWebServiceValues(); | ||
| 433 | SaveMiscellaneousValues(); | ||
| 434 | } | ||
| 435 | SaveControlValues(); | ||
| 436 | SaveCoreValues(); | ||
| 437 | SaveCpuValues(); | ||
| 438 | SaveRendererValues(); | ||
| 439 | SaveAudioValues(); | ||
| 440 | SaveSystemValues(); | ||
| 441 | |||
| 442 | WriteToIni(); | ||
| 443 | } | ||
| 444 | |||
| 445 | void Config::SaveAudioValues() { | ||
| 446 | BeginGroup(Settings::TranslateCategory(Settings::Category::Audio)); | ||
| 447 | |||
| 448 | WriteCategory(Settings::Category::Audio); | ||
| 449 | WriteCategory(Settings::Category::UiAudio); | ||
| 450 | |||
| 451 | EndGroup(); | ||
| 452 | } | ||
| 453 | |||
| 454 | void Config::SaveControlValues() { | ||
| 455 | BeginGroup(Settings::TranslateCategory(Settings::Category::Controls)); | ||
| 456 | |||
| 457 | WriteCategory(Settings::Category::Controls); | ||
| 458 | |||
| 459 | Settings::values.players.SetGlobal(!IsCustomConfig()); | ||
| 460 | for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) { | ||
| 461 | SavePlayerValues(p); | ||
| 462 | } | ||
| 463 | if (IsCustomConfig()) { | ||
| 464 | EndGroup(); | ||
| 465 | return; | ||
| 466 | } | ||
| 467 | SaveTouchscreenValues(); | ||
| 468 | SaveMotionTouchValues(); | ||
| 469 | |||
| 470 | EndGroup(); | ||
| 471 | } | ||
| 472 | |||
| 473 | void Config::SaveCoreValues() { | ||
| 474 | BeginGroup(Settings::TranslateCategory(Settings::Category::Core)); | ||
| 475 | |||
| 476 | WriteCategory(Settings::Category::Core); | ||
| 477 | |||
| 478 | EndGroup(); | ||
| 479 | } | ||
| 480 | |||
| 481 | void Config::SaveDataStorageValues() { | ||
| 482 | BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage)); | ||
| 483 | |||
| 484 | WriteSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir), | ||
| 485 | std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir))); | ||
| 486 | WriteSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir), | ||
| 487 | std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir))); | ||
| 488 | WriteSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir), | ||
| 489 | std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir))); | ||
| 490 | WriteSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir), | ||
| 491 | std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir))); | ||
| 492 | WriteSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir), | ||
| 493 | std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir))); | ||
| 494 | |||
| 495 | WriteCategory(Settings::Category::DataStorage); | ||
| 496 | |||
| 497 | EndGroup(); | ||
| 498 | } | ||
| 499 | |||
| 500 | void Config::SaveDebuggingValues() { | ||
| 501 | BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging)); | ||
| 502 | |||
| 503 | // Intentionally not using the QT default setting as this is intended to be changed in the ini | ||
| 504 | WriteSetting(std::string("record_frame_times"), Settings::values.record_frame_times); | ||
| 505 | |||
| 506 | WriteCategory(Settings::Category::Debugging); | ||
| 507 | WriteCategory(Settings::Category::DebuggingGraphics); | ||
| 508 | |||
| 509 | EndGroup(); | ||
| 510 | } | ||
| 511 | |||
| 512 | void Config::SaveNetworkValues() { | ||
| 513 | BeginGroup(Settings::TranslateCategory(Settings::Category::Services)); | ||
| 514 | |||
| 515 | WriteCategory(Settings::Category::Network); | ||
| 516 | |||
| 517 | EndGroup(); | ||
| 518 | } | ||
| 519 | |||
| 520 | void Config::SaveDisabledAddOnValues() { | ||
| 521 | // Custom config section | ||
| 522 | BeginGroup(std::string("DisabledAddOns")); | ||
| 523 | |||
| 524 | int i = 0; | ||
| 525 | BeginArray(std::string("")); | ||
| 526 | for (const auto& elem : Settings::values.disabled_addons) { | ||
| 527 | SetArrayIndex(i); | ||
| 528 | WriteSetting(std::string("title_id"), elem.first, std::make_optional(static_cast<u64>(0))); | ||
| 529 | BeginArray(std::string("disabled")); | ||
| 530 | for (std::size_t j = 0; j < elem.second.size(); ++j) { | ||
| 531 | SetArrayIndex(static_cast<int>(j)); | ||
| 532 | WriteSetting(std::string("d"), elem.second[j], std::make_optional(std::string(""))); | ||
| 533 | } | ||
| 534 | EndArray(); // disabled | ||
| 535 | ++i; | ||
| 536 | } | ||
| 537 | EndArray(); // Base disabled addons array - Has no base key | ||
| 538 | |||
| 539 | EndGroup(); | ||
| 540 | } | ||
| 541 | |||
| 542 | void Config::SaveMiscellaneousValues() { | ||
| 543 | BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous)); | ||
| 544 | |||
| 545 | WriteCategory(Settings::Category::Miscellaneous); | ||
| 546 | |||
| 547 | EndGroup(); | ||
| 548 | } | ||
| 549 | |||
| 550 | void Config::SaveCpuValues() { | ||
| 551 | BeginGroup(Settings::TranslateCategory(Settings::Category::Cpu)); | ||
| 552 | |||
| 553 | WriteCategory(Settings::Category::Cpu); | ||
| 554 | WriteCategory(Settings::Category::CpuDebug); | ||
| 555 | WriteCategory(Settings::Category::CpuUnsafe); | ||
| 556 | |||
| 557 | EndGroup(); | ||
| 558 | } | ||
| 559 | |||
| 560 | void Config::SaveRendererValues() { | ||
| 561 | BeginGroup(Settings::TranslateCategory(Settings::Category::Renderer)); | ||
| 562 | |||
| 563 | WriteCategory(Settings::Category::Renderer); | ||
| 564 | WriteCategory(Settings::Category::RendererAdvanced); | ||
| 565 | WriteCategory(Settings::Category::RendererDebug); | ||
| 566 | |||
| 567 | EndGroup(); | ||
| 568 | } | ||
| 569 | |||
| 570 | void Config::SaveScreenshotValues() { | ||
| 571 | BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots)); | ||
| 572 | |||
| 573 | WriteSetting(std::string("screenshot_path"), | ||
| 574 | FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)); | ||
| 575 | WriteCategory(Settings::Category::Screenshots); | ||
| 576 | |||
| 577 | EndGroup(); | ||
| 578 | } | ||
| 579 | |||
| 580 | void Config::SaveSystemValues() { | ||
| 581 | BeginGroup(Settings::TranslateCategory(Settings::Category::System)); | ||
| 582 | |||
| 583 | WriteCategory(Settings::Category::System); | ||
| 584 | WriteCategory(Settings::Category::SystemAudio); | ||
| 585 | |||
| 586 | EndGroup(); | ||
| 587 | } | ||
| 588 | |||
| 589 | void Config::SaveWebServiceValues() { | ||
| 590 | BeginGroup(Settings::TranslateCategory(Settings::Category::WebService)); | ||
| 591 | |||
| 592 | WriteCategory(Settings::Category::WebService); | ||
| 593 | |||
| 594 | EndGroup(); | ||
| 595 | } | ||
| 596 | |||
| 597 | bool Config::ReadBooleanSetting(const std::string& key, const std::optional<bool> default_value) { | ||
| 598 | std::string full_key = GetFullKey(key, false); | ||
| 599 | if (!default_value.has_value()) { | ||
| 600 | return config->GetBoolValue(GetSection().c_str(), full_key.c_str(), false); | ||
| 601 | } | ||
| 602 | |||
| 603 | if (config->GetBoolValue(GetSection().c_str(), | ||
| 604 | std::string(full_key).append("\\default").c_str(), false)) { | ||
| 605 | return static_cast<bool>(default_value.value()); | ||
| 606 | } else { | ||
| 607 | return config->GetBoolValue(GetSection().c_str(), full_key.c_str(), | ||
| 608 | static_cast<bool>(default_value.value())); | ||
| 609 | } | ||
| 610 | } | ||
| 611 | |||
| 612 | s64 Config::ReadIntegerSetting(const std::string& key, const std::optional<s64> default_value) { | ||
| 613 | std::string full_key = GetFullKey(key, false); | ||
| 614 | if (!default_value.has_value()) { | ||
| 615 | try { | ||
| 616 | return std::stoll( | ||
| 617 | std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), "0"))); | ||
| 618 | } catch (...) { | ||
| 619 | return 0; | ||
| 620 | } | ||
| 621 | } | ||
| 622 | |||
| 623 | s64 result = 0; | ||
| 624 | if (config->GetBoolValue(GetSection().c_str(), | ||
| 625 | std::string(full_key).append("\\default").c_str(), true)) { | ||
| 626 | result = default_value.value(); | ||
| 627 | } else { | ||
| 628 | try { | ||
| 629 | result = std::stoll(std::string(config->GetValue( | ||
| 630 | GetSection().c_str(), full_key.c_str(), ToString(default_value.value()).c_str()))); | ||
| 631 | } catch (...) { | ||
| 632 | result = default_value.value(); | ||
| 633 | } | ||
| 634 | } | ||
| 635 | return result; | ||
| 636 | } | ||
| 637 | |||
| 638 | double Config::ReadDoubleSetting(const std::string& key, | ||
| 639 | const std::optional<double> default_value) { | ||
| 640 | std::string full_key = GetFullKey(key, false); | ||
| 641 | if (!default_value.has_value()) { | ||
| 642 | return config->GetDoubleValue(GetSection().c_str(), full_key.c_str(), 0); | ||
| 643 | } | ||
| 644 | |||
| 645 | double result; | ||
| 646 | if (config->GetBoolValue(GetSection().c_str(), | ||
| 647 | std::string(full_key).append("\\default").c_str(), true)) { | ||
| 648 | result = default_value.value(); | ||
| 649 | } else { | ||
| 650 | result = | ||
| 651 | config->GetDoubleValue(GetSection().c_str(), full_key.c_str(), default_value.value()); | ||
| 652 | } | ||
| 653 | return result; | ||
| 654 | } | ||
| 655 | |||
| 656 | std::string Config::ReadStringSetting(const std::string& key, | ||
| 657 | const std::optional<std::string> default_value) { | ||
| 658 | std::string result; | ||
| 659 | std::string full_key = GetFullKey(key, false); | ||
| 660 | if (!default_value.has_value()) { | ||
| 661 | result = config->GetValue(GetSection().c_str(), full_key.c_str(), ""); | ||
| 662 | boost::replace_all(result, "\"", ""); | ||
| 663 | return result; | ||
| 664 | } | ||
| 665 | |||
| 666 | if (config->GetBoolValue(GetSection().c_str(), | ||
| 667 | std::string(full_key).append("\\default").c_str(), true)) { | ||
| 668 | result = default_value.value(); | ||
| 669 | } else { | ||
| 670 | result = | ||
| 671 | config->GetValue(GetSection().c_str(), full_key.c_str(), default_value.value().c_str()); | ||
| 672 | } | ||
| 673 | boost::replace_all(result, "\"", ""); | ||
| 674 | boost::replace_all(result, "//", "/"); | ||
| 675 | return result; | ||
| 676 | } | ||
| 677 | |||
| 678 | bool Config::Exists(const std::string& section, const std::string& key) const { | ||
| 679 | const std::string value = config->GetValue(section.c_str(), key.c_str(), ""); | ||
| 680 | return !value.empty(); | ||
| 681 | } | ||
| 682 | |||
| 683 | template <typename Type> | ||
| 684 | void Config::WriteSetting(const std::string& key, const Type& value, | ||
| 685 | const std::optional<Type>& default_value, | ||
| 686 | const std::optional<bool>& use_global) { | ||
| 687 | std::string full_key = GetFullKey(key, false); | ||
| 688 | |||
| 689 | std::string saved_value; | ||
| 690 | std::string string_default; | ||
| 691 | if constexpr (std::is_same_v<Type, std::string>) { | ||
| 692 | saved_value.append(AdjustOutputString(value)); | ||
| 693 | if (default_value.has_value()) { | ||
| 694 | string_default.append(AdjustOutputString(default_value.value())); | ||
| 695 | } | ||
| 696 | } else { | ||
| 697 | saved_value.append(AdjustOutputString(ToString(value))); | ||
| 698 | if (default_value.has_value()) { | ||
| 699 | string_default.append(ToString(default_value.value())); | ||
| 700 | } | ||
| 701 | } | ||
| 702 | |||
| 703 | if (default_value.has_value() && use_global.has_value()) { | ||
| 704 | if (!global) { | ||
| 705 | WriteSettingInternal(std::string(full_key).append("\\global"), | ||
| 706 | ToString(use_global.value())); | ||
| 707 | } | ||
| 708 | if (global || use_global.value() == false) { | ||
| 709 | WriteSettingInternal(std::string(full_key).append("\\default"), | ||
| 710 | ToString(string_default == saved_value)); | ||
| 711 | WriteSettingInternal(full_key, saved_value); | ||
| 712 | } | ||
| 713 | } else if (default_value.has_value() && !use_global.has_value()) { | ||
| 714 | WriteSettingInternal(std::string(full_key).append("\\default"), | ||
| 715 | ToString(string_default == saved_value)); | ||
| 716 | WriteSettingInternal(full_key, saved_value); | ||
| 717 | } else { | ||
| 718 | WriteSettingInternal(full_key, saved_value); | ||
| 719 | } | ||
| 720 | } | ||
| 721 | |||
| 722 | void Config::WriteSettingInternal(const std::string& key, const std::string& value) { | ||
| 723 | config->SetValue(GetSection().c_str(), key.c_str(), value.c_str()); | ||
| 724 | } | ||
| 725 | |||
| 726 | void Config::Reload() { | ||
| 727 | ReadValues(); | ||
| 728 | // To apply default value changes | ||
| 729 | SaveValues(); | ||
| 730 | } | ||
| 731 | |||
| 732 | void Config::Save() { | ||
| 733 | SaveValues(); | ||
| 734 | } | ||
| 735 | |||
| 736 | void Config::ClearControlPlayerValues() const { | ||
| 737 | // If key is an empty string, all keys in the current group() are removed. | ||
| 738 | const char* section = Settings::TranslateCategory(Settings::Category::Controls); | ||
| 739 | CSimpleIniA::TNamesDepend keys; | ||
| 740 | config->GetAllKeys(section, keys); | ||
| 741 | for (const auto& key : keys) { | ||
| 742 | if (std::string(config->GetValue(section, key.pItem)).empty()) { | ||
| 743 | config->Delete(section, key.pItem); | ||
| 744 | } | ||
| 745 | } | ||
| 746 | } | ||
| 747 | |||
| 748 | const std::string& Config::GetConfigFilePath() const { | ||
| 749 | return config_loc; | ||
| 750 | } | ||
| 751 | |||
| 752 | void Config::ReadCategory(const Settings::Category category) { | ||
| 753 | const auto& settings = FindRelevantList(category); | ||
| 754 | std::ranges::for_each(settings, [&](const auto& setting) { ReadSettingGeneric(setting); }); | ||
| 755 | } | ||
| 756 | |||
| 757 | void Config::WriteCategory(const Settings::Category category) { | ||
| 758 | const auto& settings = FindRelevantList(category); | ||
| 759 | std::ranges::for_each(settings, [&](const auto& setting) { WriteSettingGeneric(setting); }); | ||
| 760 | } | ||
| 761 | |||
| 762 | void Config::ReadSettingGeneric(Settings::BasicSetting* const setting) { | ||
| 763 | if (!setting->Save() || (!setting->Switchable() && !global)) { | ||
| 764 | return; | ||
| 765 | } | ||
| 766 | |||
| 767 | const std::string key = AdjustKey(setting->GetLabel()); | ||
| 768 | const std::string default_value(setting->DefaultToString()); | ||
| 769 | |||
| 770 | bool use_global = true; | ||
| 771 | if (setting->Switchable() && !global) { | ||
| 772 | use_global = | ||
| 773 | ReadBooleanSetting(std::string(key).append("\\use_global"), std::make_optional(true)); | ||
| 774 | setting->SetGlobal(use_global); | ||
| 775 | } | ||
| 776 | |||
| 777 | if (global || !use_global) { | ||
| 778 | const bool is_default = | ||
| 779 | ReadBooleanSetting(std::string(key).append("\\default"), std::make_optional(true)); | ||
| 780 | if (!is_default) { | ||
| 781 | const std::string setting_string = ReadStringSetting(key, default_value); | ||
| 782 | setting->LoadString(setting_string); | ||
| 783 | } else { | ||
| 784 | // Empty string resets the Setting to default | ||
| 785 | setting->LoadString(""); | ||
| 786 | } | ||
| 787 | } | ||
| 788 | } | ||
| 789 | |||
| 790 | void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) { | ||
| 791 | if (!setting->Save()) { | ||
| 792 | return; | ||
| 793 | } | ||
| 794 | |||
| 795 | std::string key = AdjustKey(setting->GetLabel()); | ||
| 796 | if (setting->Switchable()) { | ||
| 797 | if (!global) { | ||
| 798 | WriteSetting(std::string(key).append("\\use_global"), setting->UsingGlobal()); | ||
| 799 | } | ||
| 800 | if (global || !setting->UsingGlobal()) { | ||
| 801 | WriteSetting(std::string(key).append("\\default"), | ||
| 802 | setting->ToString() == setting->DefaultToString()); | ||
| 803 | WriteSetting(key, setting->ToString()); | ||
| 804 | } | ||
| 805 | } else if (global) { | ||
| 806 | WriteSetting(std::string(key).append("\\default"), | ||
| 807 | setting->ToString() == setting->DefaultToString()); | ||
| 808 | WriteSetting(key, setting->ToString()); | ||
| 809 | } | ||
| 810 | } | ||
| 811 | |||
| 812 | void Config::BeginGroup(const std::string& group) { | ||
| 813 | // You can't begin a group while reading/writing from a config array | ||
| 814 | ASSERT(array_stack.empty()); | ||
| 815 | |||
| 816 | key_stack.push_back(AdjustKey(group)); | ||
| 817 | } | ||
| 818 | |||
| 819 | void Config::EndGroup() { | ||
| 820 | // You can't end a group if you haven't started one yet | ||
| 821 | ASSERT(!key_stack.empty()); | ||
| 822 | |||
| 823 | // You can't end a group when reading/writing from a config array | ||
| 824 | ASSERT(array_stack.empty()); | ||
| 825 | |||
| 826 | key_stack.pop_back(); | ||
| 827 | } | ||
| 828 | |||
| 829 | std::string Config::GetSection() { | ||
| 830 | if (key_stack.empty()) { | ||
| 831 | return std::string{""}; | ||
| 832 | } | ||
| 833 | |||
| 834 | return key_stack.front(); | ||
| 835 | } | ||
| 836 | |||
| 837 | std::string Config::GetGroup() const { | ||
| 838 | if (key_stack.size() <= 1) { | ||
| 839 | return std::string{""}; | ||
| 840 | } | ||
| 841 | |||
| 842 | std::string key; | ||
| 843 | for (size_t i = 1; i < key_stack.size(); ++i) { | ||
| 844 | key.append(key_stack[i]).append("\\"); | ||
| 845 | } | ||
| 846 | return key; | ||
| 847 | } | ||
| 848 | |||
| 849 | std::string Config::AdjustKey(const std::string& key) { | ||
| 850 | std::string adjusted_key(key); | ||
| 851 | boost::replace_all(adjusted_key, "/", "\\"); | ||
| 852 | boost::replace_all(adjusted_key, " ", "%20"); | ||
| 853 | return adjusted_key; | ||
| 854 | } | ||
| 855 | |||
| 856 | std::string Config::AdjustOutputString(const std::string& string) { | ||
| 857 | std::string adjusted_string(string); | ||
| 858 | boost::replace_all(adjusted_string, "\\", "/"); | ||
| 859 | boost::replace_all(adjusted_string, "//", "/"); | ||
| 860 | |||
| 861 | // Needed for backwards compatibility with QSettings deserialization | ||
| 862 | for (const auto& special_character : special_characters) { | ||
| 863 | if (adjusted_string.find(special_character) != std::string::npos) { | ||
| 864 | adjusted_string.insert(0, "\""); | ||
| 865 | adjusted_string.append("\""); | ||
| 866 | break; | ||
| 867 | } | ||
| 868 | } | ||
| 869 | return adjusted_string; | ||
| 870 | } | ||
| 871 | |||
| 872 | std::string Config::GetFullKey(const std::string& key, bool skipArrayIndex) { | ||
| 873 | if (array_stack.empty()) { | ||
| 874 | return std::string(GetGroup()).append(AdjustKey(key)); | ||
| 875 | } | ||
| 876 | |||
| 877 | std::string array_key; | ||
| 878 | for (size_t i = 0; i < array_stack.size(); ++i) { | ||
| 879 | if (!array_stack[i].name.empty()) { | ||
| 880 | array_key.append(array_stack[i].name).append("\\"); | ||
| 881 | } | ||
| 882 | |||
| 883 | if (!skipArrayIndex || (array_stack.size() - 1 != i && array_stack.size() > 1)) { | ||
| 884 | array_key.append(ToString(array_stack[i].index)).append("\\"); | ||
| 885 | } | ||
| 886 | } | ||
| 887 | std::string final_key = std::string(GetGroup()).append(array_key).append(AdjustKey(key)); | ||
| 888 | return final_key; | ||
| 889 | } | ||
| 890 | |||
| 891 | int Config::BeginArray(const std::string& array) { | ||
| 892 | array_stack.push_back(ConfigArray{AdjustKey(array), 0, 0}); | ||
| 893 | const int size = config->GetLongValue(GetSection().c_str(), | ||
| 894 | GetFullKey(std::string("size"), true).c_str(), 0); | ||
| 895 | array_stack.back().size = size; | ||
| 896 | return size; | ||
| 897 | } | ||
| 898 | |||
| 899 | void Config::EndArray() { | ||
| 900 | // You can't end a config array before starting one | ||
| 901 | ASSERT(!array_stack.empty()); | ||
| 902 | |||
| 903 | // Write out the size to config | ||
| 904 | if (key_stack.size() == 1 && array_stack.back().name.empty()) { | ||
| 905 | // Edge-case where the first array created doesn't have a name | ||
| 906 | config->SetValue(GetSection().c_str(), std::string("size").c_str(), | ||
| 907 | ToString(array_stack.back().size).c_str()); | ||
| 908 | } else { | ||
| 909 | const auto key = GetFullKey(std::string("size"), true); | ||
| 910 | config->SetValue(GetSection().c_str(), key.c_str(), | ||
| 911 | ToString(array_stack.back().size).c_str()); | ||
| 912 | } | ||
| 913 | |||
| 914 | array_stack.pop_back(); | ||
| 915 | } | ||
| 916 | |||
| 917 | void Config::SetArrayIndex(const int index) { | ||
| 918 | // You can't set the array index if you haven't started one yet | ||
| 919 | ASSERT(!array_stack.empty()); | ||
| 920 | |||
| 921 | const int array_index = index + 1; | ||
| 922 | |||
| 923 | // You can't exceed the known max size of the array by more than 1 | ||
| 924 | ASSERT(array_stack.front().size + 1 >= array_index); | ||
| 925 | |||
| 926 | // Change the config array size to the current index since you may want | ||
| 927 | // to reduce the number of elements that you read back from the config | ||
| 928 | // in the future. | ||
| 929 | array_stack.back().size = array_index; | ||
| 930 | array_stack.back().index = array_index; | ||
| 931 | } | ||