summaryrefslogtreecommitdiff
path: root/src/frontend_common/config.cpp
diff options
context:
space:
mode:
authorGravatar t8952023-11-12 02:03:01 -0500
committerGravatar t8952023-11-21 01:58:13 -0500
commitda14c7b8e47fcd5456d88a033a1fb154a0dcfa39 (patch)
tree4f9da84775b2b25aaa7eb519bccb1d5e620608a0 /src/frontend_common/config.cpp
parentMerge pull request #12011 from Macj0rdan/controller-applet (diff)
downloadyuzu-da14c7b8e47fcd5456d88a033a1fb154a0dcfa39.tar.gz
yuzu-da14c7b8e47fcd5456d88a033a1fb154a0dcfa39.tar.xz
yuzu-da14c7b8e47fcd5456d88a033a1fb154a0dcfa39.zip
config: Unify config handling under frontend_common
Replaces every way of handling config for each frontend with SimpleIni. frontend_common's Config class is at the center where it saves and loads all of the cross-platform settings and provides a set of pure virtual functions for platform specific settings. As a result of making config handling platform specific, several parts had to be moved to each platform's own config class or to other parts. Default keys were put in platform specific config classes and translatable strings for Qt were moved to shared_translation. Default hotkeys, default_theme, window geometry, and qt metatypes were moved to uisettings. Additionally, to reduce dependence on Qt, QStrings were converted to std::strings where applicable.
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}