1 // Copyright 2014 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <algorithm>
6 #include <array>
7 #include <unordered_map>
8 #include <QKeySequence>
9 #include <QSettings>
10 #include "citra_qt/configuration/config.h"
11 #include "citra_qt/uisettings.h"
12 #include "common/file_util.h"
13 #include "core/frontend/mic.h"
14 #include "core/hle/service/service.h"
15 #include "input_common/main.h"
16 #include "input_common/udp/client.h"
17 #include "network/network.h"
18 
Config()19 Config::Config() {
20     // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
21     qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini";
22     FileUtil::CreateFullPath(qt_config_loc);
23     qt_config =
24         std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
25     Reload();
26 }
27 
~Config()28 Config::~Config() {
29     Save();
30 }
31 
32 const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
33     Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G,
34     Qt::Key_F, Qt::Key_H, Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N,
35     Qt::Key_O, Qt::Key_P, Qt::Key_1, Qt::Key_2, Qt::Key_B,
36 };
37 
38 const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
39     {
40         Qt::Key_Up,
41         Qt::Key_Down,
42         Qt::Key_Left,
43         Qt::Key_Right,
44         Qt::Key_D,
45     },
46     {
47         Qt::Key_I,
48         Qt::Key_K,
49         Qt::Key_J,
50         Qt::Key_L,
51         Qt::Key_D,
52     },
53 }};
54 
55 // This shouldn't have anything except static initializers (no functions). So
56 // QKeySequence(...).toString() is NOT ALLOWED HERE.
57 // This must be in alphabetical order according to action name as it must have the same order as
58 // UISetting::values.shortcuts, which is alphabetically ordered.
59 // clang-format off
60 const std::array<UISettings::Shortcut, 23> default_hotkeys{
61     {{QStringLiteral("Advance Frame"),            QStringLiteral("Main Window"), {QStringLiteral("\\"), Qt::ApplicationShortcut}},
62      {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}},
63      {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
64      {QStringLiteral("Decrease Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
65      {QStringLiteral("Exit Citra"),               QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
66      {QStringLiteral("Exit Fullscreen"),          QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}},
67      {QStringLiteral("Fullscreen"),               QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
68      {QStringLiteral("Increase Speed Limit"),     QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
69      {QStringLiteral("Load Amiibo"),              QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}},
70      {QStringLiteral("Load File"),                QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}},
71      {QStringLiteral("Load from Newest Slot"),    QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
72      {QStringLiteral("Remove Amiibo"),            QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
73      {QStringLiteral("Restart Emulation"),        QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
74      {QStringLiteral("Rotate Screens Upright"),   QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
75      {QStringLiteral("Save to Oldest Slot"),      QStringLiteral("Main Window"), {QStringLiteral("Ctrl+C"), Qt::WindowShortcut}},
76      {QStringLiteral("Stop Emulation"),           QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
77      {QStringLiteral("Swap Screens"),             QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::WindowShortcut}},
78      {QStringLiteral("Toggle Alternate Speed"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
79      {QStringLiteral("Toggle Filter Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
80      {QStringLiteral("Toggle Frame Advancing"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), Qt::ApplicationShortcut}},
81      {QStringLiteral("Toggle Screen Layout"),     QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::WindowShortcut}},
82      {QStringLiteral("Toggle Status Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
83      {QStringLiteral("Toggle Texture Dumping"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+D"), Qt::ApplicationShortcut}}}};
84 // clang-format on
85 
ReadValues()86 void Config::ReadValues() {
87     ReadControlValues();
88     ReadCoreValues();
89     ReadRendererValues();
90     ReadLayoutValues();
91     ReadAudioValues();
92     ReadCameraValues();
93     ReadDataStorageValues();
94     ReadSystemValues();
95     ReadMiscellaneousValues();
96     ReadDebuggingValues();
97     ReadWebServiceValues();
98     ReadVideoDumpingValues();
99     ReadUIValues();
100     ReadUtilityValues();
101 }
102 
ReadAudioValues()103 void Config::ReadAudioValues() {
104     qt_config->beginGroup(QStringLiteral("Audio"));
105 
106     Settings::values.enable_dsp_lle = ReadSetting(QStringLiteral("enable_dsp_lle"), false).toBool();
107     Settings::values.enable_dsp_lle_multithread =
108         ReadSetting(QStringLiteral("enable_dsp_lle_multithread"), false).toBool();
109     Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto"))
110                                    .toString()
111                                    .toStdString();
112     Settings::values.enable_audio_stretching =
113         ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool();
114     Settings::values.audio_device_id =
115         ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto"))
116             .toString()
117             .toStdString();
118     Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat();
119     Settings::values.mic_input_type = static_cast<Settings::MicInputType>(
120         ReadSetting(QStringLiteral("mic_input_type"), 0).toInt());
121     Settings::values.mic_input_device =
122         ReadSetting(QStringLiteral("mic_input_device"),
123                     QString::fromUtf8(Frontend::Mic::default_device_name))
124             .toString()
125             .toStdString();
126 
127     qt_config->endGroup();
128 }
129 
ReadCameraValues()130 void Config::ReadCameraValues() {
131     using namespace Service::CAM;
132     qt_config->beginGroup(QStringLiteral("Camera"));
133 
134     Settings::values.camera_name[OuterRightCamera] =
135         ReadSetting(QStringLiteral("camera_outer_right_name"), QStringLiteral("blank"))
136             .toString()
137             .toStdString();
138     Settings::values.camera_config[OuterRightCamera] =
139         ReadSetting(QStringLiteral("camera_outer_right_config"), QString{})
140             .toString()
141             .toStdString();
142     Settings::values.camera_flip[OuterRightCamera] =
143         ReadSetting(QStringLiteral("camera_outer_right_flip"), 0).toInt();
144     Settings::values.camera_name[InnerCamera] =
145         ReadSetting(QStringLiteral("camera_inner_name"), QStringLiteral("blank"))
146             .toString()
147             .toStdString();
148     Settings::values.camera_config[InnerCamera] =
149         ReadSetting(QStringLiteral("camera_inner_config"), QString{}).toString().toStdString();
150     Settings::values.camera_flip[InnerCamera] =
151         ReadSetting(QStringLiteral("camera_inner_flip"), 0).toInt();
152     Settings::values.camera_name[OuterLeftCamera] =
153         ReadSetting(QStringLiteral("camera_outer_left_name"), QStringLiteral("blank"))
154             .toString()
155             .toStdString();
156     Settings::values.camera_config[OuterLeftCamera] =
157         ReadSetting(QStringLiteral("camera_outer_left_config"), QString{}).toString().toStdString();
158     Settings::values.camera_flip[OuterLeftCamera] =
159         ReadSetting(QStringLiteral("camera_outer_left_flip"), 0).toInt();
160 
161     qt_config->endGroup();
162 }
163 
ReadControlValues()164 void Config::ReadControlValues() {
165     qt_config->beginGroup(QStringLiteral("Controls"));
166 
167     int num_touch_from_button_maps =
168         qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
169 
170     if (num_touch_from_button_maps > 0) {
171         const auto append_touch_from_button_map = [this] {
172             Settings::TouchFromButtonMap map;
173             map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default"))
174                            .toString()
175                            .toStdString();
176             const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries"));
177             map.buttons.reserve(num_touch_maps);
178             for (int i = 0; i < num_touch_maps; i++) {
179                 qt_config->setArrayIndex(i);
180                 std::string touch_mapping =
181                     ReadSetting(QStringLiteral("bind")).toString().toStdString();
182                 map.buttons.emplace_back(std::move(touch_mapping));
183             }
184             qt_config->endArray(); // entries
185             Settings::values.touch_from_button_maps.emplace_back(std::move(map));
186         };
187 
188         for (int i = 0; i < num_touch_from_button_maps; ++i) {
189             qt_config->setArrayIndex(i);
190             append_touch_from_button_map();
191         }
192     } else {
193         Settings::values.touch_from_button_maps.emplace_back(
194             Settings::TouchFromButtonMap{"default", {}});
195         num_touch_from_button_maps = 1;
196     }
197     qt_config->endArray();
198 
199     Settings::values.current_input_profile_index =
200         ReadSetting(QStringLiteral("profile"), 0).toInt();
201 
202     const auto append_profile = [this, num_touch_from_button_maps] {
203         Settings::InputProfile profile;
204         profile.name =
205             ReadSetting(QStringLiteral("name"), QStringLiteral("default")).toString().toStdString();
206         for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
207             std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
208             profile.buttons[i] = ReadSetting(QString::fromUtf8(Settings::NativeButton::mapping[i]),
209                                              QString::fromStdString(default_param))
210                                      .toString()
211                                      .toStdString();
212             if (profile.buttons[i].empty())
213                 profile.buttons[i] = default_param;
214         }
215         for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
216             std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
217                 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
218                 default_analogs[i][3], default_analogs[i][4], 0.5f);
219             profile.analogs[i] = ReadSetting(QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
220                                              QString::fromStdString(default_param))
221                                      .toString()
222                                      .toStdString();
223             if (profile.analogs[i].empty())
224                 profile.analogs[i] = default_param;
225         }
226         profile.motion_device =
227             ReadSetting(QStringLiteral("motion_device"),
228                         QStringLiteral(
229                             "engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0"))
230                 .toString()
231                 .toStdString();
232         profile.touch_device =
233             ReadSetting(QStringLiteral("touch_device"), QStringLiteral("engine:emu_window"))
234                 .toString()
235                 .toStdString();
236         profile.use_touch_from_button =
237             ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool();
238         profile.touch_from_button_map_index =
239             ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt();
240         profile.touch_from_button_map_index =
241             std::clamp(profile.touch_from_button_map_index, 0, num_touch_from_button_maps - 1);
242         profile.udp_input_address =
243             ReadSetting(QStringLiteral("udp_input_address"),
244                         QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
245                 .toString()
246                 .toStdString();
247         profile.udp_input_port = static_cast<u16>(
248             ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT)
249                 .toInt());
250         profile.udp_pad_index =
251             static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
252         Settings::values.input_profiles.emplace_back(std::move(profile));
253     };
254 
255     int num_input_profiles = qt_config->beginReadArray(QStringLiteral("profiles"));
256 
257     for (int i = 0; i < num_input_profiles; ++i) {
258         qt_config->setArrayIndex(i);
259         append_profile();
260     }
261 
262     qt_config->endArray();
263 
264     // create a input profile if no input profiles exist, with the default or old settings
265     if (num_input_profiles == 0) {
266         append_profile();
267         num_input_profiles = 1;
268     }
269 
270     // ensure that the current input profile index is valid.
271     Settings::values.current_input_profile_index =
272         std::clamp(Settings::values.current_input_profile_index, 0, num_input_profiles - 1);
273 
274     Settings::LoadProfile(Settings::values.current_input_profile_index);
275 
276     qt_config->endGroup();
277 }
278 
ReadUtilityValues()279 void Config::ReadUtilityValues() {
280     qt_config->beginGroup(QStringLiteral("Utility"));
281 
282     Settings::values.dump_textures = ReadSetting(QStringLiteral("dump_textures"), false).toBool();
283     Settings::values.custom_textures =
284         ReadSetting(QStringLiteral("custom_textures"), false).toBool();
285     Settings::values.preload_textures =
286         ReadSetting(QStringLiteral("preload_textures"), false).toBool();
287 
288     qt_config->endGroup();
289 }
290 
ReadCoreValues()291 void Config::ReadCoreValues() {
292     qt_config->beginGroup(QStringLiteral("Core"));
293 
294     Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool();
295     Settings::values.cpu_clock_percentage =
296         ReadSetting(QStringLiteral("cpu_clock_percentage"), 100).toInt();
297 
298     qt_config->endGroup();
299 }
300 
ReadDataStorageValues()301 void Config::ReadDataStorageValues() {
302     qt_config->beginGroup(QStringLiteral("Data Storage"));
303 
304     Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
305     std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
306     Settings::values.nand_dir =
307         ReadSetting(QStringLiteral("nand_directory"), QString::fromStdString(nand_dir))
308             .toString()
309             .toStdString();
310     std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
311     Settings::values.sdmc_dir =
312         ReadSetting(QStringLiteral("sdmc_directory"), QString::fromStdString(sdmc_dir))
313             .toString()
314             .toStdString();
315 
316     qt_config->endGroup();
317 }
318 
ReadDebuggingValues()319 void Config::ReadDebuggingValues() {
320     qt_config->beginGroup(QStringLiteral("Debugging"));
321 
322     // Intentionally not using the QT default setting as this is intended to be changed in the ini
323     Settings::values.record_frame_times =
324         qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
325     Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
326     Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
327 
328     qt_config->beginGroup(QStringLiteral("LLE"));
329     for (const auto& service_module : Service::service_module_map) {
330         bool use_lle = ReadSetting(QString::fromStdString(service_module.name), false).toBool();
331         Settings::values.lle_modules.emplace(service_module.name, use_lle);
332     }
333     qt_config->endGroup();
334 
335     qt_config->endGroup();
336 }
337 
ReadLayoutValues()338 void Config::ReadLayoutValues() {
339     qt_config->beginGroup(QStringLiteral("Layout"));
340 
341     Settings::values.render_3d = static_cast<Settings::StereoRenderOption>(
342         ReadSetting(QStringLiteral("render_3d"), 0).toInt());
343     Settings::values.factor_3d = ReadSetting(QStringLiteral("factor_3d"), 0).toInt();
344     Settings::values.pp_shader_name =
345         ReadSetting(QStringLiteral("pp_shader_name"),
346                     (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
347                         ? QStringLiteral("dubois (builtin)")
348                         : QStringLiteral("none (builtin)"))
349             .toString()
350             .toStdString();
351     Settings::values.filter_mode = ReadSetting(QStringLiteral("filter_mode"), true).toBool();
352     Settings::values.layout_option =
353         static_cast<Settings::LayoutOption>(ReadSetting(QStringLiteral("layout_option")).toInt());
354     Settings::values.swap_screen = ReadSetting(QStringLiteral("swap_screen"), false).toBool();
355     Settings::values.upright_screen = ReadSetting(QStringLiteral("upright_screen"), false).toBool();
356     Settings::values.custom_layout = ReadSetting(QStringLiteral("custom_layout"), false).toBool();
357     Settings::values.custom_top_left = ReadSetting(QStringLiteral("custom_top_left"), 0).toInt();
358     Settings::values.custom_top_top = ReadSetting(QStringLiteral("custom_top_top"), 0).toInt();
359     Settings::values.custom_top_right =
360         ReadSetting(QStringLiteral("custom_top_right"), 400).toInt();
361     Settings::values.custom_top_bottom =
362         ReadSetting(QStringLiteral("custom_top_bottom"), 240).toInt();
363     Settings::values.custom_bottom_left =
364         ReadSetting(QStringLiteral("custom_bottom_left"), 40).toInt();
365     Settings::values.custom_bottom_top =
366         ReadSetting(QStringLiteral("custom_bottom_top"), 240).toInt();
367     Settings::values.custom_bottom_right =
368         ReadSetting(QStringLiteral("custom_bottom_right"), 360).toInt();
369     Settings::values.custom_bottom_bottom =
370         ReadSetting(QStringLiteral("custom_bottom_bottom"), 480).toInt();
371 
372     qt_config->endGroup();
373 }
374 
ReadMiscellaneousValues()375 void Config::ReadMiscellaneousValues() {
376     qt_config->beginGroup(QStringLiteral("Miscellaneous"));
377 
378     Settings::values.log_filter =
379         ReadSetting(QStringLiteral("log_filter"), QStringLiteral("*:Info"))
380             .toString()
381             .toStdString();
382 
383     qt_config->endGroup();
384 }
385 
ReadMultiplayerValues()386 void Config::ReadMultiplayerValues() {
387     qt_config->beginGroup(QStringLiteral("Multiplayer"));
388 
389     UISettings::values.nickname = ReadSetting(QStringLiteral("nickname"), QString{}).toString();
390     UISettings::values.ip = ReadSetting(QStringLiteral("ip"), QString{}).toString();
391     UISettings::values.port =
392         ReadSetting(QStringLiteral("port"), Network::DefaultRoomPort).toString();
393     UISettings::values.room_nickname =
394         ReadSetting(QStringLiteral("room_nickname"), QString{}).toString();
395     UISettings::values.room_name = ReadSetting(QStringLiteral("room_name"), QString{}).toString();
396     UISettings::values.room_port =
397         ReadSetting(QStringLiteral("room_port"), QStringLiteral("24872")).toString();
398     bool ok;
399     UISettings::values.host_type = ReadSetting(QStringLiteral("host_type"), 0).toUInt(&ok);
400     if (!ok) {
401         UISettings::values.host_type = 0;
402     }
403     UISettings::values.max_player = ReadSetting(QStringLiteral("max_player"), 8).toUInt();
404     UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong();
405     UISettings::values.room_description =
406         ReadSetting(QStringLiteral("room_description"), QString{}).toString();
407     // Read ban list back
408     int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
409     UISettings::values.ban_list.first.resize(size);
410     for (int i = 0; i < size; ++i) {
411         qt_config->setArrayIndex(i);
412         UISettings::values.ban_list.first[i] =
413             ReadSetting(QStringLiteral("username")).toString().toStdString();
414     }
415     qt_config->endArray();
416     size = qt_config->beginReadArray(QStringLiteral("ip_ban_list"));
417     UISettings::values.ban_list.second.resize(size);
418     for (int i = 0; i < size; ++i) {
419         qt_config->setArrayIndex(i);
420         UISettings::values.ban_list.second[i] =
421             ReadSetting(QStringLiteral("ip")).toString().toStdString();
422     }
423     qt_config->endArray();
424 
425     qt_config->endGroup();
426 }
427 
ReadPathValues()428 void Config::ReadPathValues() {
429     qt_config->beginGroup(QStringLiteral("Paths"));
430 
431     UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
432     UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
433     UISettings::values.movie_record_path =
434         ReadSetting(QStringLiteral("movieRecordPath")).toString();
435     UISettings::values.movie_playback_path =
436         ReadSetting(QStringLiteral("moviePlaybackPath")).toString();
437     UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();
438     UISettings::values.video_dumping_path =
439         ReadSetting(QStringLiteral("videoDumpingPath")).toString();
440     UISettings::values.game_dir_deprecated =
441         ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
442     UISettings::values.game_dir_deprecated_deepscan =
443         ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
444     int size = qt_config->beginReadArray(QStringLiteral("gamedirs"));
445     for (int i = 0; i < size; ++i) {
446         qt_config->setArrayIndex(i);
447         UISettings::GameDir game_dir;
448         game_dir.path = ReadSetting(QStringLiteral("path")).toString();
449         game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool();
450         game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool();
451         UISettings::values.game_dirs.append(game_dir);
452     }
453     qt_config->endArray();
454     // create NAND and SD card directories if empty, these are not removable through the UI,
455     // also carries over old game list settings if present
456     if (UISettings::values.game_dirs.isEmpty()) {
457         UISettings::GameDir game_dir;
458         game_dir.path = QStringLiteral("INSTALLED");
459         game_dir.expanded = true;
460         UISettings::values.game_dirs.append(game_dir);
461         game_dir.path = QStringLiteral("SYSTEM");
462         UISettings::values.game_dirs.append(game_dir);
463         if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) {
464             game_dir.path = UISettings::values.game_dir_deprecated;
465             game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
466             UISettings::values.game_dirs.append(game_dir);
467         }
468     }
469     UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
470     UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
471 
472     qt_config->endGroup();
473 }
474 
ReadRendererValues()475 void Config::ReadRendererValues() {
476     qt_config->beginGroup(QStringLiteral("Renderer"));
477 
478     Settings::values.use_hw_renderer =
479         ReadSetting(QStringLiteral("use_hw_renderer"), true).toBool();
480     Settings::values.use_hw_shader = ReadSetting(QStringLiteral("use_hw_shader"), true).toBool();
481 #ifdef __APPLE__
482     // Hardware shader is broken on macos with Intel GPUs thanks to poor drivers.
483     // We still want to provide this option for test/development purposes, but disable it by
484     // default.
485     Settings::values.separable_shader =
486         ReadSetting(QStringLiteral("separable_shader"), false).toBool();
487 #endif
488     Settings::values.shaders_accurate_mul =
489         ReadSetting(QStringLiteral("shaders_accurate_mul"), true).toBool();
490     Settings::values.use_shader_jit = ReadSetting(QStringLiteral("use_shader_jit"), true).toBool();
491     Settings::values.use_disk_shader_cache =
492         ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool();
493     Settings::values.use_vsync_new = ReadSetting(QStringLiteral("use_vsync_new"), true).toBool();
494     Settings::values.resolution_factor =
495         static_cast<u16>(ReadSetting(QStringLiteral("resolution_factor"), 1).toInt());
496     Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt();
497     Settings::values.use_frame_limit_alternate =
498         ReadSetting(QStringLiteral("use_frame_limit_alternate"), false).toBool();
499     Settings::values.frame_limit_alternate =
500         ReadSetting(QStringLiteral("frame_limit_alternate"), 200).toInt();
501 
502     Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat();
503     Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat();
504     Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat();
505 
506     Settings::values.texture_filter_name =
507         ReadSetting(QStringLiteral("texture_filter_name"), QStringLiteral("none"))
508             .toString()
509             .toStdString();
510 
511     qt_config->endGroup();
512 }
513 
ReadShortcutValues()514 void Config::ReadShortcutValues() {
515     qt_config->beginGroup(QStringLiteral("Shortcuts"));
516 
517     for (auto [name, group, shortcut] : default_hotkeys) {
518         auto [keyseq, context] = shortcut;
519         qt_config->beginGroup(group);
520         qt_config->beginGroup(name);
521         UISettings::values.shortcuts.push_back(
522             {name,
523              group,
524              {ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(),
525               ReadSetting(QStringLiteral("Context"), context).toInt()}});
526         qt_config->endGroup();
527         qt_config->endGroup();
528     }
529 
530     qt_config->endGroup();
531 }
532 
ReadSystemValues()533 void Config::ReadSystemValues() {
534     qt_config->beginGroup(QStringLiteral("System"));
535 
536     Settings::values.is_new_3ds = ReadSetting(QStringLiteral("is_new_3ds"), true).toBool();
537     Settings::values.region_value =
538         ReadSetting(QStringLiteral("region_value"), Settings::REGION_VALUE_AUTO_SELECT).toInt();
539     Settings::values.init_clock = static_cast<Settings::InitClock>(
540         ReadSetting(QStringLiteral("init_clock"), static_cast<u32>(Settings::InitClock::SystemTime))
541             .toInt());
542     Settings::values.init_time =
543         ReadSetting(QStringLiteral("init_time"), 946681277ULL).toULongLong();
544 
545     qt_config->endGroup();
546 }
547 
548 // Options for variable bit rate live streaming taken from here:
549 // https://developers.google.com/media/vp9/live-encoding
550 const QString DEFAULT_VIDEO_ENCODER_OPTIONS =
551     QStringLiteral("quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1");
552 const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QString{};
553 
ReadVideoDumpingValues()554 void Config::ReadVideoDumpingValues() {
555     qt_config->beginGroup(QStringLiteral("VideoDumping"));
556 
557     Settings::values.output_format =
558         ReadSetting(QStringLiteral("output_format"), QStringLiteral("webm"))
559             .toString()
560             .toStdString();
561     Settings::values.format_options =
562         ReadSetting(QStringLiteral("format_options")).toString().toStdString();
563 
564     Settings::values.video_encoder =
565         ReadSetting(QStringLiteral("video_encoder"), QStringLiteral("libvpx-vp9"))
566             .toString()
567             .toStdString();
568 
569     Settings::values.video_encoder_options =
570         ReadSetting(QStringLiteral("video_encoder_options"), DEFAULT_VIDEO_ENCODER_OPTIONS)
571             .toString()
572             .toStdString();
573 
574     Settings::values.video_bitrate =
575         ReadSetting(QStringLiteral("video_bitrate"), 2500000).toULongLong();
576 
577     Settings::values.audio_encoder =
578         ReadSetting(QStringLiteral("audio_encoder"), QStringLiteral("libvorbis"))
579             .toString()
580             .toStdString();
581     Settings::values.audio_encoder_options =
582         ReadSetting(QStringLiteral("audio_encoder_options"), DEFAULT_AUDIO_ENCODER_OPTIONS)
583             .toString()
584             .toStdString();
585     Settings::values.audio_bitrate =
586         ReadSetting(QStringLiteral("audio_bitrate"), 64000).toULongLong();
587 
588     qt_config->endGroup();
589 }
590 
ReadUIValues()591 void Config::ReadUIValues() {
592     qt_config->beginGroup(QStringLiteral("UI"));
593 
594     UISettings::values.theme =
595         ReadSetting(QStringLiteral("theme"), QString::fromUtf8(UISettings::themes[0].second))
596             .toString();
597     UISettings::values.enable_discord_presence =
598         ReadSetting(QStringLiteral("enable_discord_presence"), true).toBool();
599     UISettings::values.screenshot_resolution_factor =
600         static_cast<u16>(ReadSetting(QStringLiteral("screenshot_resolution_factor"), 0).toUInt());
601 
602     ReadUpdaterValues();
603     ReadUILayoutValues();
604     ReadUIGameListValues();
605     ReadPathValues();
606     ReadShortcutValues();
607     ReadMultiplayerValues();
608 
609     UISettings::values.single_window_mode =
610         ReadSetting(QStringLiteral("singleWindowMode"), true).toBool();
611     UISettings::values.fullscreen = ReadSetting(QStringLiteral("fullscreen"), false).toBool();
612     UISettings::values.display_titlebar =
613         ReadSetting(QStringLiteral("displayTitleBars"), true).toBool();
614     UISettings::values.show_filter_bar =
615         ReadSetting(QStringLiteral("showFilterBar"), true).toBool();
616     UISettings::values.show_status_bar =
617         ReadSetting(QStringLiteral("showStatusBar"), true).toBool();
618     UISettings::values.confirm_before_closing =
619         ReadSetting(QStringLiteral("confirmClose"), true).toBool();
620     UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool();
621     UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt();
622     UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool();
623     UISettings::values.pause_when_in_background =
624         ReadSetting(QStringLiteral("pauseWhenInBackground"), false).toBool();
625     UISettings::values.hide_mouse =
626         ReadSetting(QStringLiteral("hideInactiveMouse"), false).toBool();
627 
628     qt_config->endGroup();
629 }
630 
ReadUIGameListValues()631 void Config::ReadUIGameListValues() {
632     qt_config->beginGroup(QStringLiteral("GameList"));
633 
634     auto icon_size = UISettings::GameListIconSize{
635         ReadSetting(QStringLiteral("iconSize"),
636                     static_cast<int>(UISettings::GameListIconSize::LargeIcon))
637             .toInt()};
638     if (icon_size < UISettings::GameListIconSize::NoIcon ||
639         icon_size > UISettings::GameListIconSize::LargeIcon) {
640         icon_size = UISettings::GameListIconSize::LargeIcon;
641     }
642     UISettings::values.game_list_icon_size = icon_size;
643 
644     UISettings::GameListText row_1 = UISettings::GameListText{
645         ReadSetting(QStringLiteral("row1"), static_cast<int>(UISettings::GameListText::TitleName))
646             .toInt()};
647     if (row_1 <= UISettings::GameListText::NoText || row_1 >= UISettings::GameListText::ListEnd) {
648         row_1 = UISettings::GameListText::TitleName;
649     }
650     UISettings::values.game_list_row_1 = row_1;
651 
652     UISettings::GameListText row_2 = UISettings::GameListText{
653         ReadSetting(QStringLiteral("row2"), static_cast<int>(UISettings::GameListText::FileName))
654             .toInt()};
655     if (row_2 < UISettings::GameListText::NoText || row_2 >= UISettings::GameListText::ListEnd) {
656         row_2 = UISettings::GameListText::FileName;
657     }
658     UISettings::values.game_list_row_2 = row_2;
659 
660     UISettings::values.game_list_hide_no_icon =
661         ReadSetting(QStringLiteral("hideNoIcon"), false).toBool();
662     UISettings::values.game_list_single_line_mode =
663         ReadSetting(QStringLiteral("singleLineMode"), false).toBool();
664 
665     qt_config->endGroup();
666 }
667 
ReadUILayoutValues()668 void Config::ReadUILayoutValues() {
669     qt_config->beginGroup(QStringLiteral("UILayout"));
670 
671     UISettings::values.geometry = ReadSetting(QStringLiteral("geometry")).toByteArray();
672     UISettings::values.state = ReadSetting(QStringLiteral("state")).toByteArray();
673     UISettings::values.renderwindow_geometry =
674         ReadSetting(QStringLiteral("geometryRenderWindow")).toByteArray();
675     UISettings::values.gamelist_header_state =
676         ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();
677     UISettings::values.microprofile_geometry =
678         ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();
679     UISettings::values.microprofile_visible =
680         ReadSetting(QStringLiteral("microProfileDialogVisible"), false).toBool();
681 
682     qt_config->endGroup();
683 }
684 
ReadUpdaterValues()685 void Config::ReadUpdaterValues() {
686     qt_config->beginGroup(QStringLiteral("Updater"));
687 
688     UISettings::values.check_for_update_on_start =
689         ReadSetting(QStringLiteral("check_for_update_on_start"), true).toBool();
690     UISettings::values.update_on_close =
691         ReadSetting(QStringLiteral("update_on_close"), false).toBool();
692 
693     qt_config->endGroup();
694 }
695 
ReadWebServiceValues()696 void Config::ReadWebServiceValues() {
697     qt_config->beginGroup(QStringLiteral("WebService"));
698 
699     Settings::values.enable_telemetry =
700         ReadSetting(QStringLiteral("enable_telemetry"), true).toBool();
701     Settings::values.web_api_url =
702         ReadSetting(QStringLiteral("web_api_url"), QStringLiteral("https://api.citra-emu.org"))
703             .toString()
704             .toStdString();
705     Settings::values.citra_username =
706         ReadSetting(QStringLiteral("citra_username")).toString().toStdString();
707     Settings::values.citra_token =
708         ReadSetting(QStringLiteral("citra_token")).toString().toStdString();
709 
710     qt_config->endGroup();
711 }
712 
SaveValues()713 void Config::SaveValues() {
714     SaveControlValues();
715     SaveCoreValues();
716     SaveRendererValues();
717     SaveLayoutValues();
718     SaveAudioValues();
719     SaveCameraValues();
720     SaveDataStorageValues();
721     SaveSystemValues();
722     SaveMiscellaneousValues();
723     SaveDebuggingValues();
724     SaveWebServiceValues();
725     SaveVideoDumpingValues();
726     SaveUIValues();
727     SaveUtilityValues();
728 }
729 
SaveAudioValues()730 void Config::SaveAudioValues() {
731     qt_config->beginGroup(QStringLiteral("Audio"));
732 
733     WriteSetting(QStringLiteral("enable_dsp_lle"), Settings::values.enable_dsp_lle, false);
734     WriteSetting(QStringLiteral("enable_dsp_lle_multithread"),
735                  Settings::values.enable_dsp_lle_multithread, false);
736     WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id),
737                  QStringLiteral("auto"));
738     WriteSetting(QStringLiteral("enable_audio_stretching"),
739                  Settings::values.enable_audio_stretching, true);
740     WriteSetting(QStringLiteral("output_device"),
741                  QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto"));
742     WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f);
743     WriteSetting(QStringLiteral("mic_input_device"),
744                  QString::fromStdString(Settings::values.mic_input_device),
745                  QString::fromUtf8(Frontend::Mic::default_device_name));
746     WriteSetting(QStringLiteral("mic_input_type"),
747                  static_cast<int>(Settings::values.mic_input_type), 0);
748 
749     qt_config->endGroup();
750 }
751 
SaveCameraValues()752 void Config::SaveCameraValues() {
753     using namespace Service::CAM;
754     qt_config->beginGroup(QStringLiteral("Camera"));
755 
756     WriteSetting(QStringLiteral("camera_outer_right_name"),
757                  QString::fromStdString(Settings::values.camera_name[OuterRightCamera]),
758                  QStringLiteral("blank"));
759     WriteSetting(QStringLiteral("camera_outer_right_config"),
760                  QString::fromStdString(Settings::values.camera_config[OuterRightCamera]),
761                  QString{});
762     WriteSetting(QStringLiteral("camera_outer_right_flip"),
763                  Settings::values.camera_flip[OuterRightCamera], 0);
764     WriteSetting(QStringLiteral("camera_inner_name"),
765                  QString::fromStdString(Settings::values.camera_name[InnerCamera]),
766                  QStringLiteral("blank"));
767     WriteSetting(QStringLiteral("camera_inner_config"),
768                  QString::fromStdString(Settings::values.camera_config[InnerCamera]), QString{});
769     WriteSetting(QStringLiteral("camera_inner_flip"), Settings::values.camera_flip[InnerCamera], 0);
770     WriteSetting(QStringLiteral("camera_outer_left_name"),
771                  QString::fromStdString(Settings::values.camera_name[OuterLeftCamera]),
772                  QStringLiteral("blank"));
773     WriteSetting(QStringLiteral("camera_outer_left_config"),
774                  QString::fromStdString(Settings::values.camera_config[OuterLeftCamera]),
775                  QString{});
776     WriteSetting(QStringLiteral("camera_outer_left_flip"),
777                  Settings::values.camera_flip[OuterLeftCamera], 0);
778 
779     qt_config->endGroup();
780 }
781 
SaveControlValues()782 void Config::SaveControlValues() {
783     qt_config->beginGroup(QStringLiteral("Controls"));
784 
785     WriteSetting(QStringLiteral("profile"), Settings::values.current_input_profile_index, 0);
786     qt_config->beginWriteArray(QStringLiteral("profiles"));
787     for (std::size_t p = 0; p < Settings::values.input_profiles.size(); ++p) {
788         qt_config->setArrayIndex(static_cast<int>(p));
789         const auto& profile = Settings::values.input_profiles[p];
790         WriteSetting(QStringLiteral("name"), QString::fromStdString(profile.name),
791                      QStringLiteral("default"));
792         for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
793             std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
794             WriteSetting(QString::fromStdString(Settings::NativeButton::mapping[i]),
795                          QString::fromStdString(profile.buttons[i]),
796                          QString::fromStdString(default_param));
797         }
798         for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
799             std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
800                 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
801                 default_analogs[i][3], default_analogs[i][4], 0.5f);
802             WriteSetting(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
803                          QString::fromStdString(profile.analogs[i]),
804                          QString::fromStdString(default_param));
805         }
806         WriteSetting(
807             QStringLiteral("motion_device"), QString::fromStdString(profile.motion_device),
808             QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0"));
809         WriteSetting(QStringLiteral("touch_device"), QString::fromStdString(profile.touch_device),
810                      QStringLiteral("engine:emu_window"));
811         WriteSetting(QStringLiteral("use_touch_from_button"), profile.use_touch_from_button, false);
812         WriteSetting(QStringLiteral("touch_from_button_map"), profile.touch_from_button_map_index,
813                      0);
814         WriteSetting(QStringLiteral("udp_input_address"),
815                      QString::fromStdString(profile.udp_input_address),
816                      QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
817         WriteSetting(QStringLiteral("udp_input_port"), profile.udp_input_port,
818                      InputCommon::CemuhookUDP::DEFAULT_PORT);
819         WriteSetting(QStringLiteral("udp_pad_index"), profile.udp_pad_index, 0);
820     }
821     qt_config->endArray();
822 
823     qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
824     for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
825         qt_config->setArrayIndex(static_cast<int>(p));
826         const auto& map = Settings::values.touch_from_button_maps[p];
827         WriteSetting(QStringLiteral("name"), QString::fromStdString(map.name),
828                      QStringLiteral("default"));
829         qt_config->beginWriteArray(QStringLiteral("entries"));
830         for (std::size_t q = 0; q < map.buttons.size(); ++q) {
831             qt_config->setArrayIndex(static_cast<int>(q));
832             WriteSetting(QStringLiteral("bind"), QString::fromStdString(map.buttons[q]));
833         }
834         qt_config->endArray();
835     }
836     qt_config->endArray();
837 
838     qt_config->endGroup();
839 }
840 
SaveUtilityValues()841 void Config::SaveUtilityValues() {
842     qt_config->beginGroup(QStringLiteral("Utility"));
843 
844     WriteSetting(QStringLiteral("dump_textures"), Settings::values.dump_textures, false);
845     WriteSetting(QStringLiteral("custom_textures"), Settings::values.custom_textures, false);
846     WriteSetting(QStringLiteral("preload_textures"), Settings::values.preload_textures, false);
847 
848     qt_config->endGroup();
849 }
850 
SaveCoreValues()851 void Config::SaveCoreValues() {
852     qt_config->beginGroup(QStringLiteral("Core"));
853 
854     WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true);
855     WriteSetting(QStringLiteral("cpu_clock_percentage"), Settings::values.cpu_clock_percentage,
856                  100);
857 
858     qt_config->endGroup();
859 }
860 
SaveDataStorageValues()861 void Config::SaveDataStorageValues() {
862     qt_config->beginGroup(QStringLiteral("Data Storage"));
863 
864     WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
865     WriteSetting(QStringLiteral("nand_directory"),
866                  QString::fromStdString(Settings::values.nand_dir),
867                  QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
868     WriteSetting(QStringLiteral("sdmc_directory"),
869                  QString::fromStdString(Settings::values.sdmc_dir),
870                  QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
871 
872     qt_config->endGroup();
873 }
874 
SaveDebuggingValues()875 void Config::SaveDebuggingValues() {
876     qt_config->beginGroup(QStringLiteral("Debugging"));
877 
878     // Intentionally not using the QT default setting as this is intended to be changed in the ini
879     qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
880     WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
881     WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
882 
883     qt_config->beginGroup(QStringLiteral("LLE"));
884     for (const auto& service_module : Settings::values.lle_modules) {
885         WriteSetting(QString::fromStdString(service_module.first), service_module.second, false);
886     }
887     qt_config->endGroup();
888 
889     qt_config->endGroup();
890 }
891 
SaveLayoutValues()892 void Config::SaveLayoutValues() {
893     qt_config->beginGroup(QStringLiteral("Layout"));
894 
895     WriteSetting(QStringLiteral("render_3d"), static_cast<int>(Settings::values.render_3d), 0);
896     WriteSetting(QStringLiteral("factor_3d"), Settings::values.factor_3d.load(), 0);
897     WriteSetting(QStringLiteral("pp_shader_name"),
898                  QString::fromStdString(Settings::values.pp_shader_name),
899                  (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
900                      ? QStringLiteral("dubois (builtin)")
901                      : QStringLiteral("none (builtin)"));
902     WriteSetting(QStringLiteral("filter_mode"), Settings::values.filter_mode, true);
903     WriteSetting(QStringLiteral("layout_option"), static_cast<int>(Settings::values.layout_option));
904     WriteSetting(QStringLiteral("swap_screen"), Settings::values.swap_screen, false);
905     WriteSetting(QStringLiteral("upright_screen"), Settings::values.upright_screen, false);
906     WriteSetting(QStringLiteral("custom_layout"), Settings::values.custom_layout, false);
907     WriteSetting(QStringLiteral("custom_top_left"), Settings::values.custom_top_left, 0);
908     WriteSetting(QStringLiteral("custom_top_top"), Settings::values.custom_top_top, 0);
909     WriteSetting(QStringLiteral("custom_top_right"), Settings::values.custom_top_right, 400);
910     WriteSetting(QStringLiteral("custom_top_bottom"), Settings::values.custom_top_bottom, 240);
911     WriteSetting(QStringLiteral("custom_bottom_left"), Settings::values.custom_bottom_left, 40);
912     WriteSetting(QStringLiteral("custom_bottom_top"), Settings::values.custom_bottom_top, 240);
913     WriteSetting(QStringLiteral("custom_bottom_right"), Settings::values.custom_bottom_right, 360);
914     WriteSetting(QStringLiteral("custom_bottom_bottom"), Settings::values.custom_bottom_bottom,
915                  480);
916 
917     qt_config->endGroup();
918 }
919 
SaveMiscellaneousValues()920 void Config::SaveMiscellaneousValues() {
921     qt_config->beginGroup(QStringLiteral("Miscellaneous"));
922 
923     WriteSetting(QStringLiteral("log_filter"), QString::fromStdString(Settings::values.log_filter),
924                  QStringLiteral("*:Info"));
925 
926     qt_config->endGroup();
927 }
928 
SaveMultiplayerValues()929 void Config::SaveMultiplayerValues() {
930     qt_config->beginGroup(QStringLiteral("Multiplayer"));
931 
932     WriteSetting(QStringLiteral("nickname"), UISettings::values.nickname, QString{});
933     WriteSetting(QStringLiteral("ip"), UISettings::values.ip, QString{});
934     WriteSetting(QStringLiteral("port"), UISettings::values.port, Network::DefaultRoomPort);
935     WriteSetting(QStringLiteral("room_nickname"), UISettings::values.room_nickname, QString{});
936     WriteSetting(QStringLiteral("room_name"), UISettings::values.room_name, QString{});
937     WriteSetting(QStringLiteral("room_port"), UISettings::values.room_port,
938                  QStringLiteral("24872"));
939     WriteSetting(QStringLiteral("host_type"), UISettings::values.host_type, 0);
940     WriteSetting(QStringLiteral("max_player"), UISettings::values.max_player, 8);
941     WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0);
942     WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description,
943                  QString{});
944     // Write ban list
945     qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
946     for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {
947         qt_config->setArrayIndex(i);
948         WriteSetting(QStringLiteral("username"),
949                      QString::fromStdString(UISettings::values.ban_list.first[i]));
950     }
951     qt_config->endArray();
952     qt_config->beginWriteArray(QStringLiteral("ip_ban_list"));
953     for (std::size_t i = 0; i < UISettings::values.ban_list.second.size(); ++i) {
954         qt_config->setArrayIndex(i);
955         WriteSetting(QStringLiteral("ip"),
956                      QString::fromStdString(UISettings::values.ban_list.second[i]));
957     }
958     qt_config->endArray();
959 
960     qt_config->endGroup();
961 }
962 
SavePathValues()963 void Config::SavePathValues() {
964     qt_config->beginGroup(QStringLiteral("Paths"));
965 
966     WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
967     WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
968     WriteSetting(QStringLiteral("movieRecordPath"), UISettings::values.movie_record_path);
969     WriteSetting(QStringLiteral("moviePlaybackPath"), UISettings::values.movie_playback_path);
970     WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
971     WriteSetting(QStringLiteral("videoDumpingPath"), UISettings::values.video_dumping_path);
972     qt_config->beginWriteArray(QStringLiteral("gamedirs"));
973     for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
974         qt_config->setArrayIndex(i);
975         const auto& game_dir = UISettings::values.game_dirs[i];
976         WriteSetting(QStringLiteral("path"), game_dir.path);
977         WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false);
978         WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true);
979     }
980     qt_config->endArray();
981     WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
982     WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
983 
984     qt_config->endGroup();
985 }
986 
SaveRendererValues()987 void Config::SaveRendererValues() {
988     qt_config->beginGroup(QStringLiteral("Renderer"));
989 
990     WriteSetting(QStringLiteral("use_hw_renderer"), Settings::values.use_hw_renderer, true);
991     WriteSetting(QStringLiteral("use_hw_shader"), Settings::values.use_hw_shader, true);
992 #ifdef __APPLE__
993     // Hardware shader is broken on macos thanks to poor drivers.
994     // TODO: enable this for none Intel GPUs
995     WriteSetting(QStringLiteral("use_separable_shader"), Settings::values.separable_shader, false);
996 #endif
997     WriteSetting(QStringLiteral("shaders_accurate_mul"), Settings::values.shaders_accurate_mul,
998                  true);
999     WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit, true);
1000     WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache,
1001                  true);
1002     WriteSetting(QStringLiteral("use_vsync_new"), Settings::values.use_vsync_new, true);
1003     WriteSetting(QStringLiteral("resolution_factor"), Settings::values.resolution_factor, 1);
1004     WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100);
1005     WriteSetting(QStringLiteral("use_frame_limit_alternate"),
1006                  Settings::values.use_frame_limit_alternate, false);
1007     WriteSetting(QStringLiteral("frame_limit_alternate"), Settings::values.frame_limit_alternate,
1008                  200);
1009 
1010     // Cast to double because Qt's written float values are not human-readable
1011     WriteSetting(QStringLiteral("bg_red"), (double)Settings::values.bg_red, 0.0);
1012     WriteSetting(QStringLiteral("bg_green"), (double)Settings::values.bg_green, 0.0);
1013     WriteSetting(QStringLiteral("bg_blue"), (double)Settings::values.bg_blue, 0.0);
1014 
1015     WriteSetting(QStringLiteral("texture_filter_name"),
1016                  QString::fromStdString(Settings::values.texture_filter_name),
1017                  QStringLiteral("none"));
1018 
1019     qt_config->endGroup();
1020 }
1021 
SaveShortcutValues()1022 void Config::SaveShortcutValues() {
1023     qt_config->beginGroup(QStringLiteral("Shortcuts"));
1024 
1025     // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
1026     // However, their ordering must also be the same.
1027     for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
1028         auto [name, group, shortcut] = UISettings::values.shortcuts[i];
1029         qt_config->beginGroup(group);
1030         qt_config->beginGroup(name);
1031         WriteSetting(QStringLiteral("KeySeq"), shortcut.first, default_hotkeys[i].shortcut.first);
1032         WriteSetting(QStringLiteral("Context"), shortcut.second,
1033                      default_hotkeys[i].shortcut.second);
1034         qt_config->endGroup();
1035         qt_config->endGroup();
1036     }
1037 
1038     qt_config->endGroup();
1039 }
1040 
SaveSystemValues()1041 void Config::SaveSystemValues() {
1042     qt_config->beginGroup(QStringLiteral("System"));
1043 
1044     WriteSetting(QStringLiteral("is_new_3ds"), Settings::values.is_new_3ds, true);
1045     WriteSetting(QStringLiteral("region_value"), Settings::values.region_value,
1046                  Settings::REGION_VALUE_AUTO_SELECT);
1047     WriteSetting(QStringLiteral("init_clock"), static_cast<u32>(Settings::values.init_clock),
1048                  static_cast<u32>(Settings::InitClock::SystemTime));
1049     WriteSetting(QStringLiteral("init_time"),
1050                  static_cast<unsigned long long>(Settings::values.init_time), 946681277ULL);
1051 
1052     qt_config->endGroup();
1053 }
1054 
SaveVideoDumpingValues()1055 void Config::SaveVideoDumpingValues() {
1056     qt_config->beginGroup(QStringLiteral("VideoDumping"));
1057 
1058     WriteSetting(QStringLiteral("output_format"),
1059                  QString::fromStdString(Settings::values.output_format), QStringLiteral("webm"));
1060     WriteSetting(QStringLiteral("format_options"),
1061                  QString::fromStdString(Settings::values.format_options));
1062     WriteSetting(QStringLiteral("video_encoder"),
1063                  QString::fromStdString(Settings::values.video_encoder),
1064                  QStringLiteral("libvpx-vp9"));
1065     WriteSetting(QStringLiteral("video_encoder_options"),
1066                  QString::fromStdString(Settings::values.video_encoder_options),
1067                  DEFAULT_VIDEO_ENCODER_OPTIONS);
1068     WriteSetting(QStringLiteral("video_bitrate"),
1069                  static_cast<unsigned long long>(Settings::values.video_bitrate), 2500000);
1070     WriteSetting(QStringLiteral("audio_encoder"),
1071                  QString::fromStdString(Settings::values.audio_encoder),
1072                  QStringLiteral("libvorbis"));
1073     WriteSetting(QStringLiteral("audio_encoder_options"),
1074                  QString::fromStdString(Settings::values.audio_encoder_options),
1075                  DEFAULT_AUDIO_ENCODER_OPTIONS);
1076     WriteSetting(QStringLiteral("audio_bitrate"),
1077                  static_cast<unsigned long long>(Settings::values.audio_bitrate), 64000);
1078 
1079     qt_config->endGroup();
1080 }
1081 
SaveUIValues()1082 void Config::SaveUIValues() {
1083     qt_config->beginGroup(QStringLiteral("UI"));
1084 
1085     WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
1086                  QString::fromUtf8(UISettings::themes[0].second));
1087     WriteSetting(QStringLiteral("enable_discord_presence"),
1088                  UISettings::values.enable_discord_presence, true);
1089     WriteSetting(QStringLiteral("screenshot_resolution_factor"),
1090                  UISettings::values.screenshot_resolution_factor, 0);
1091 
1092     SaveUpdaterValues();
1093     SaveUILayoutValues();
1094     SaveUIGameListValues();
1095     SavePathValues();
1096     SaveShortcutValues();
1097     SaveMultiplayerValues();
1098 
1099     WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true);
1100     WriteSetting(QStringLiteral("fullscreen"), UISettings::values.fullscreen, false);
1101     WriteSetting(QStringLiteral("displayTitleBars"), UISettings::values.display_titlebar, true);
1102     WriteSetting(QStringLiteral("showFilterBar"), UISettings::values.show_filter_bar, true);
1103     WriteSetting(QStringLiteral("showStatusBar"), UISettings::values.show_status_bar, true);
1104     WriteSetting(QStringLiteral("confirmClose"), UISettings::values.confirm_before_closing, true);
1105     WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true);
1106     WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0);
1107     WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false);
1108     WriteSetting(QStringLiteral("pauseWhenInBackground"),
1109                  UISettings::values.pause_when_in_background, false);
1110     WriteSetting(QStringLiteral("hideInactiveMouse"), UISettings::values.hide_mouse, false);
1111 
1112     qt_config->endGroup();
1113 }
1114 
SaveUIGameListValues()1115 void Config::SaveUIGameListValues() {
1116     qt_config->beginGroup(QStringLiteral("GameList"));
1117 
1118     WriteSetting(QStringLiteral("iconSize"),
1119                  static_cast<int>(UISettings::values.game_list_icon_size), 2);
1120     WriteSetting(QStringLiteral("row1"), static_cast<int>(UISettings::values.game_list_row_1), 2);
1121     WriteSetting(QStringLiteral("row2"), static_cast<int>(UISettings::values.game_list_row_2), 0);
1122     WriteSetting(QStringLiteral("hideNoIcon"), UISettings::values.game_list_hide_no_icon, false);
1123     WriteSetting(QStringLiteral("singleLineMode"), UISettings::values.game_list_single_line_mode,
1124                  false);
1125 
1126     qt_config->endGroup();
1127 }
1128 
SaveUILayoutValues()1129 void Config::SaveUILayoutValues() {
1130     qt_config->beginGroup(QStringLiteral("UILayout"));
1131 
1132     WriteSetting(QStringLiteral("geometry"), UISettings::values.geometry);
1133     WriteSetting(QStringLiteral("state"), UISettings::values.state);
1134     WriteSetting(QStringLiteral("geometryRenderWindow"), UISettings::values.renderwindow_geometry);
1135     WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state);
1136     WriteSetting(QStringLiteral("microProfileDialogGeometry"),
1137                  UISettings::values.microprofile_geometry);
1138     WriteSetting(QStringLiteral("microProfileDialogVisible"),
1139                  UISettings::values.microprofile_visible, false);
1140 
1141     qt_config->endGroup();
1142 }
1143 
SaveUpdaterValues()1144 void Config::SaveUpdaterValues() {
1145     qt_config->beginGroup(QStringLiteral("Updater"));
1146 
1147     WriteSetting(QStringLiteral("check_for_update_on_start"),
1148                  UISettings::values.check_for_update_on_start, true);
1149     WriteSetting(QStringLiteral("update_on_close"), UISettings::values.update_on_close, false);
1150 
1151     qt_config->endGroup();
1152 }
1153 
SaveWebServiceValues()1154 void Config::SaveWebServiceValues() {
1155     qt_config->beginGroup(QStringLiteral("WebService"));
1156 
1157     WriteSetting(QStringLiteral("enable_telemetry"), Settings::values.enable_telemetry, true);
1158     WriteSetting(QStringLiteral("web_api_url"),
1159                  QString::fromStdString(Settings::values.web_api_url),
1160                  QStringLiteral("https://api.citra-emu.org"));
1161     WriteSetting(QStringLiteral("citra_username"),
1162                  QString::fromStdString(Settings::values.citra_username));
1163     WriteSetting(QStringLiteral("citra_token"),
1164                  QString::fromStdString(Settings::values.citra_token));
1165 
1166     qt_config->endGroup();
1167 }
1168 
ReadSetting(const QString & name) const1169 QVariant Config::ReadSetting(const QString& name) const {
1170     return qt_config->value(name);
1171 }
1172 
ReadSetting(const QString & name,const QVariant & default_value) const1173 QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {
1174     QVariant result;
1175     if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
1176         result = default_value;
1177     } else {
1178         result = qt_config->value(name, default_value);
1179     }
1180     return result;
1181 }
1182 
WriteSetting(const QString & name,const QVariant & value)1183 void Config::WriteSetting(const QString& name, const QVariant& value) {
1184     qt_config->setValue(name, value);
1185 }
1186 
WriteSetting(const QString & name,const QVariant & value,const QVariant & default_value)1187 void Config::WriteSetting(const QString& name, const QVariant& value,
1188                           const QVariant& default_value) {
1189     qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
1190     qt_config->setValue(name, value);
1191 }
1192 
Reload()1193 void Config::Reload() {
1194     ReadValues();
1195     // To apply default value changes
1196     SaveValues();
1197     Settings::Apply();
1198 }
1199 
Save()1200 void Config::Save() {
1201     SaveValues();
1202 }
1203