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