summaryrefslogtreecommitdiff
path: root/src/frontend_common/config.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend_common/config.cpp')
-rw-r--r--src/frontend_common/config.cpp931
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
19namespace FS = Common::FS;
20
21Config::Config(const ConfigType config_type)
22 : type(config_type), global{config_type == ConfigType::GlobalConfig} {}
23
24void 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
49void 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
58void 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
64void 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
71bool Config::IsCustomConfig() const {
72 return type == ConfigType::PerGameConfig;
73}
74
75void 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
130void 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
141void 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
150void 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
177void 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
208void Config::ReadCoreValues() {
209 BeginGroup(Settings::TranslateCategory(Settings::Category::Core));
210
211 ReadCategory(Settings::Category::Core);
212
213 EndGroup();
214}
215
216void 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
230void 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
243void Config::ReadServiceValues() {
244 BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
245
246 ReadCategory(Settings::Category::Services);
247
248 EndGroup();
249}
250
251void 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
273void Config::ReadMiscellaneousValues() {
274 BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous));
275
276 ReadCategory(Settings::Category::Miscellaneous);
277
278 EndGroup();
279}
280
281void 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
291void 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
301void 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
312void 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
321void Config::ReadWebServiceValues() {
322 BeginGroup(Settings::TranslateCategory(Settings::Category::WebService));
323
324 ReadCategory(Settings::Category::WebService);
325
326 EndGroup();
327}
328
329void Config::ReadNetworkValues() {
330 BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
331
332 ReadCategory(Settings::Category::Network);
333
334 EndGroup();
335}
336
337void 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
355void 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
394void 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
407void 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
426void 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
445void 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
454void 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
473void Config::SaveCoreValues() {
474 BeginGroup(Settings::TranslateCategory(Settings::Category::Core));
475
476 WriteCategory(Settings::Category::Core);
477
478 EndGroup();
479}
480
481void 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
500void 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
512void Config::SaveNetworkValues() {
513 BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
514
515 WriteCategory(Settings::Category::Network);
516
517 EndGroup();
518}
519
520void 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
542void Config::SaveMiscellaneousValues() {
543 BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous));
544
545 WriteCategory(Settings::Category::Miscellaneous);
546
547 EndGroup();
548}
549
550void 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
560void 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
570void 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
580void 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
589void Config::SaveWebServiceValues() {
590 BeginGroup(Settings::TranslateCategory(Settings::Category::WebService));
591
592 WriteCategory(Settings::Category::WebService);
593
594 EndGroup();
595}
596
597bool 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
612s64 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
638double 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
656std::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
678bool 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
683template <typename Type>
684void 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
722void Config::WriteSettingInternal(const std::string& key, const std::string& value) {
723 config->SetValue(GetSection().c_str(), key.c_str(), value.c_str());
724}
725
726void Config::Reload() {
727 ReadValues();
728 // To apply default value changes
729 SaveValues();
730}
731
732void Config::Save() {
733 SaveValues();
734}
735
736void 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
748const std::string& Config::GetConfigFilePath() const {
749 return config_loc;
750}
751
752void 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
757void 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
762void 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
790void 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
812void 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
819void 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
829std::string Config::GetSection() {
830 if (key_stack.empty()) {
831 return std::string{""};
832 }
833
834 return key_stack.front();
835}
836
837std::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
849std::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
856std::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
872std::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
891int 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
899void 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
917void 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}