summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar lat9nq2020-07-09 22:42:09 -0400
committerGravatar GitHub2020-07-09 22:42:09 -0400
commit63d23835ef4445b3a010cc0e8b1e73ebe012ac08 (patch)
treeb243c1167280dbf2e3f5af4597b6638fb707f091 /src
parentMerge pull request #4281 from RealJohnGalt/linkfix2 (diff)
downloadyuzu-63d23835ef4445b3a010cc0e8b1e73ebe012ac08.tar.gz
yuzu-63d23835ef4445b3a010cc0e8b1e73ebe012ac08.tar.xz
yuzu-63d23835ef4445b3a010cc0e8b1e73ebe012ac08.zip
configuration: implement per-game configurations (#4098)
* Switch game settings to use a pointer In order to add full per-game settings, we need to be able to tell yuzu to switch to using either the global or game configuration. Using a pointer makes it easier to switch. * configuration: add new UI without changing existing funcitonality The new UI also adds General, System, Graphics, Advanced Graphics, and Audio tabs, but as yet they do nothing. This commit keeps yuzu to the same functionality as originally branched. * configuration: Rename files These weren't included in the last commit. Now they are. * configuration: setup global configuration checkbox Global config checkbox now enables/disables the appropriate tabs in the game properties dialog. The use global configuration setting is now saved to the config, defaulting to true. This also addresses some changes requested in the PR. * configuration: swap to per-game config memory for properties dialog Does not set memory going in-game. Swaps to game values when opening the properties dialog, then swaps back when closing it. Uses a `memcpy` to swap. Also implements saving config files, limited to certain groups of configurations so as to not risk setting unsafe configurations. * configuration: change config interfaces to use config-specific pointers When a game is booted, we need to be able to open the configuration dialogs without changing the settings pointer in the game's emualtion. A new pointer specific to just the configuration dialogs can be used to separate changes to just those config dialogs without affecting the emulation. * configuration: boot a game using per-game settings Swaps values where needed to boot a game. * configuration: user correct config during emulation Creates a new pointer specifically for modifying the configuration while emulation is in progress. Both the regular configuration dialog and the game properties dialog now use the pointer Settings::config_values to focus edits to the correct struct. * settings: split Settings::values into two different structs By splitting the settings into two mutually exclusive structs, it becomes easier, as a developer, to determine how to use the Settings structs after per-game configurations is merged. Other benefits include only duplicating the required settings in memory. * settings: move use_docked_mode to Controls group `use_docked_mode` is set in the input settings and cannot be accessed from the system settings. Grouping it with system settings causes it to be saved with per-game settings, which may make transferring configs more difficult later on, especially since docked mode cannot be set from within the game properties dialog. * configuration: Fix the other yuzu executables and a regression In main.cpp, we have to get the title ID before the ROM is loaded, else the renderer will reflect only the global settings and now the user's game specific settings. * settings: use a template to duplicate memory for each setting Replaces the type of each variable in the Settings::Values struct with a new class that allows basic data reading and writing. The new struct Settings::Setting duplicates the data in memory and can manage global overrides per each setting. * configuration: correct add-ons config and swap settings when apropriate Any add-ons interaction happens directly through the global values struct. Swapping bewteen structs now also includes copying the necessary global configs that cannot be changed nor saved in per-game settings. General and System config menus now update based on whether it is viewing the global or per-game settings. * settings: restore old values struct No longer needed with the Settings::Setting class template. * configuration: implement hierarchical game properties dialog This sets the apropriate global or local data in each setting. * clang format * clang format take 2 can the docker container save this? * address comments and style issues * config: read and write settings with global awareness Adds new functions to read and write settings while keeping the global state in focus. Files now generated per-game are much smaller since often they only need address the global state. * settings: restore global state when necessary Upon closing a game or the game properties dialog, we need to restore all global settings to the original global state so that we can properly open the configuration dialog or boot a different game. * configuration: guard setting values incorrectly This disables setting values while a game is running if the setting is overwritten by a per game setting. * config: don't write local settings in the global config Simple guards to prevent writing the wrong settings in the wrong files. * configuration: add comments, assume less, and clang format No longer assumes that a disabled UI element means the global state is turned off, instead opting to directly answer that question. Still however assumes a game is running if it is in that state. * configuration: fix a logic error Should not be negated * restore settings' global state regardless of accept/cancel Fixes loading a properties dialog and causing the global config dialog to show local settings. * fix more logic errors Fixed the frame limit would set the global setting from the game properties dialog. Also strengthened the Settings::Setting member variables and simplified the logic in config reading (ReadSettingGlobal). * fix another logic error In my efforts to guard RestoreGlobalState, I accidentally negated the IsPowered condition. * configure_audio: set toggle_stretched_audio to tristate * fixed custom rtc and rng seed overwriting the global value * clang format * rebased * clang format take 4 * address my own review Basically revert unintended changes * settings: literal instead of casting "No need to cast, use 1U instead" Thanks, Morph! Co-authored-by: Morph <39850852+Morph1984@users.noreply.github.com> * Revert "settings: literal instead of casting " This reverts commit 95e992a87c898f3e882ffdb415bb0ef9f80f613f. * main: fix status buttons reporting wrong settings after stop emulation * settings: Log UseDockedMode in the Controls group This should have happened when use_docked_mode was moved over to the controls group internally. This just reflects this in the log. * main: load settings if the file has a title id In other words, don't exit if the loader has trouble getting a title id. * use a zero * settings: initalize resolution factor with constructor instead of casting * Revert "settings: initalize resolution factor with constructor instead of casting" This reverts commit 54c35ecb46a29953842614620f9b7de1aa9d5dc8. * configure_graphics: guard device selector when Vulkan is global Prevents the user from editing the device selector if Vulkan is the global renderer backend. Also resets the vulkan_device variable when the users switches back-and-forth between global and Vulkan. * address reviewer concerns Changes function variables to const wherever they don't need to be changed. Sets Settings::Setting to final as it should not be inherited from. Sets ConfigurationShared::use_global_text to static. Co-Authored-By: VolcaEM <volcaem@users.noreply.github.com> * main: load per-game settings after LoadROM This prevents `Restart Emulation` from restoring the global settings *after* the per-game settings were applied. Thanks to BSoDGamingYT for finding this bug. * Revert "main: load per-game settings after LoadROM" This reverts commit 9d0d48c52d2dcf3bfb1806cc8fa7d5a271a8a804. * main: only restore global settings when necessary Loading the per-game settings cannot happen after the ROM is loaded, so we have to specify when to restore the global state. Again thanks to BSoD for finding the bug. * configuration_shared: address reviewer concerns except operator overrides Dropping operator override usage in next commit. Co-Authored-By: LC <lioncash@users.noreply.github.com> * settings: Drop operator overrides from Setting template Requires using GetValue and SetValue explicitly. Also reverts a change that broke title ID formatting in the game properties dialog. * complete rebase * configuration_shared: translate "Use global configuration" Uses ConfigurePerGame to do so, since its usage, at least as of now, corresponds with ConfigurationShared. * configure_per_game: address reviewer concern As far as I understand, it prevents the program from unnecessarily copying strings. Co-Authored-By: LC <lioncash@users.noreply.github.com> Co-authored-by: Morph <39850852+Morph1984@users.noreply.github.com> Co-authored-by: VolcaEM <volcaem@users.noreply.github.com> Co-authored-by: LC <lioncash@users.noreply.github.com>
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/cubeb_sink.cpp2
-rw-r--r--src/audio_core/stream.cpp7
-rw-r--r--src/core/core.cpp6
-rw-r--r--src/core/frontend/framebuffer_layout.cpp2
-rw-r--r--src/core/hle/kernel/process.cpp2
-rw-r--r--src/core/hle/service/am/am.cpp8
-rw-r--r--src/core/hle/service/ns/ns.cpp3
-rw-r--r--src/core/hle/service/set/set.cpp10
-rw-r--r--src/core/hle/service/spl/module.cpp2
-rw-r--r--src/core/hle/service/vi/vi.cpp20
-rw-r--r--src/core/perf_stats.cpp5
-rw-r--r--src/core/settings.cpp84
-rw-r--r--src/core/settings.h124
-rw-r--r--src/core/telemetry_session.cpp27
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h6
-rw-r--r--src/video_core/gpu.cpp2
-rw-r--r--src/video_core/query_cache.h2
-rw-r--r--src/video_core/renderer_base.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp8
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp8
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp2
-rw-r--r--src/video_core/texture_cache/texture_cache.h2
-rw-r--r--src/video_core/textures/texture.cpp2
-rw-r--r--src/video_core/video_core.cpp8
-rw-r--r--src/yuzu/CMakeLists.txt11
-rw-r--r--src/yuzu/bootmanager.cpp8
-rw-r--r--src/yuzu/configuration/config.cpp336
-rw-r--r--src/yuzu/configuration/config.h27
-rw-r--r--src/yuzu/configuration/configuration_shared.cpp76
-rw-r--r--src/yuzu/configuration/configuration_shared.h36
-rw-r--r--src/yuzu/configuration/configure_audio.cpp82
-rw-r--r--src/yuzu/configuration/configure_audio.h2
-rw-r--r--src/yuzu/configuration/configure_audio.ui50
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp2
-rw-r--r--src/yuzu/configuration/configure_general.cpp78
-rw-r--r--src/yuzu/configuration/configure_general.h2
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp150
-rw-r--r--src/yuzu/configuration/configure_graphics.h2
-rw-r--r--src/yuzu/configuration/configure_graphics.ui36
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp106
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h2
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp140
-rw-r--r--src/yuzu/configuration/configure_per_game.h51
-rw-r--r--src/yuzu/configuration/configure_per_game.ui350
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.cpp (renamed from src/yuzu/configuration/configure_per_general.cpp)95
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.h (renamed from src/yuzu/configuration/configure_per_general.h)16
-rw-r--r--src/yuzu/configuration/configure_per_game_addons.ui38
-rw-r--r--src/yuzu/configuration/configure_per_general.ui276
-rw-r--r--src/yuzu/configuration/configure_system.cpp190
-rw-r--r--src/yuzu/configuration/configure_system.h2
-rw-r--r--src/yuzu/main.cpp116
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu_cmd/config.cpp83
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp2
-rw-r--r--src/yuzu_tester/config.cpp63
57 files changed, 1942 insertions, 835 deletions
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index c4e0e30fe..41bf5cd4d 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -193,7 +193,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
193 const std::size_t samples_to_write = num_channels * num_frames; 193 const std::size_t samples_to_write = num_channels * num_frames;
194 std::size_t samples_written; 194 std::size_t samples_written;
195 195
196 if (Settings::values.enable_audio_stretching) { 196 if (Settings::values.enable_audio_stretching.GetValue()) {
197 const std::vector<s16> in{impl->queue.Pop()}; 197 const std::vector<s16> in{impl->queue.Pop()};
198 const std::size_t num_in{in.size() / num_channels}; 198 const std::size_t num_in{in.size() / num_channels};
199 s16* const out{reinterpret_cast<s16*>(buffer)}; 199 s16* const out{reinterpret_cast<s16*>(buffer)};
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
index 22e8df373..aab3e979a 100644
--- a/src/audio_core/stream.cpp
+++ b/src/audio_core/stream.cpp
@@ -105,9 +105,10 @@ void Stream::PlayNextBuffer(s64 cycles_late) {
105 105
106 sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); 106 sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
107 107
108 core_timing.ScheduleEvent(GetBufferReleaseNS(*active_buffer) - 108 core_timing.ScheduleEvent(
109 (Settings::values.enable_audio_stretching ? 0 : cycles_late), 109 GetBufferReleaseNS(*active_buffer) -
110 release_event, {}); 110 (Settings::values.enable_audio_stretching.GetValue() ? 0 : cycles_late),
111 release_event, {});
111} 112}
112 113
113void Stream::ReleaseActiveBuffer(s64 cycles_late) { 114void Stream::ReleaseActiveBuffer(s64 cycles_late) {
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 1a243c515..69a1aa0a5 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -147,8 +147,8 @@ struct System::Impl {
147 147
148 device_memory = std::make_unique<Core::DeviceMemory>(system); 148 device_memory = std::make_unique<Core::DeviceMemory>(system);
149 149
150 is_multicore = Settings::values.use_multi_core; 150 is_multicore = Settings::values.use_multi_core.GetValue();
151 is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation; 151 is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation.GetValue();
152 152
153 kernel.SetMulticore(is_multicore); 153 kernel.SetMulticore(is_multicore);
154 cpu_manager.SetMulticore(is_multicore); 154 cpu_manager.SetMulticore(is_multicore);
@@ -162,7 +162,7 @@ struct System::Impl {
162 const auto current_time = std::chrono::duration_cast<std::chrono::seconds>( 162 const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
163 std::chrono::system_clock::now().time_since_epoch()); 163 std::chrono::system_clock::now().time_since_epoch());
164 Settings::values.custom_rtc_differential = 164 Settings::values.custom_rtc_differential =
165 Settings::values.custom_rtc.value_or(current_time) - current_time; 165 Settings::values.custom_rtc.GetValue().value_or(current_time) - current_time;
166 166
167 // Create a default fs if one doesn't already exist. 167 // Create a default fs if one doesn't already exist.
168 if (virtual_filesystem == nullptr) 168 if (virtual_filesystem == nullptr)
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index d0c43447c..c1fbc235b 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -29,7 +29,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
29 29
30 const float window_aspect_ratio = static_cast<float>(height) / width; 30 const float window_aspect_ratio = static_cast<float>(height) / width;
31 const float emulation_aspect_ratio = EmulationAspectRatio( 31 const float emulation_aspect_ratio = EmulationAspectRatio(
32 static_cast<AspectRatio>(Settings::values.aspect_ratio), window_aspect_ratio); 32 static_cast<AspectRatio>(Settings::values.aspect_ratio.GetValue()), window_aspect_ratio);
33 33
34 const Common::Rectangle<u32> screen_window_area{0, 0, width, height}; 34 const Common::Rectangle<u32> screen_window_area{0, 0, width, height};
35 Common::Rectangle<u32> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio); 35 Common::Rectangle<u32> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio);
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index f9d7c024d..c6fcb56ad 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -123,7 +123,7 @@ std::shared_ptr<Process> Process::Create(Core::System& system, std::string name,
123 : kernel.CreateNewUserProcessID(); 123 : kernel.CreateNewUserProcessID();
124 process->capabilities.InitializeForMetadatalessProcess(); 124 process->capabilities.InitializeForMetadatalessProcess();
125 125
126 std::mt19937 rng(Settings::values.rng_seed.value_or(0)); 126 std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(0));
127 std::uniform_int_distribution<u64> distribution; 127 std::uniform_int_distribution<u64> distribution;
128 std::generate(process->random_entropy.begin(), process->random_entropy.end(), 128 std::generate(process->random_entropy.begin(), process->random_entropy.end(),
129 [&] { return distribution(rng); }); 129 [&] { return distribution(rng); });
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index c688d6d98..256449aa7 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -749,14 +749,14 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext&
749 749
750 if (Settings::values.use_docked_mode) { 750 if (Settings::values.use_docked_mode) {
751 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * 751 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
752 static_cast<u32>(Settings::values.resolution_factor)); 752 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
753 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * 753 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *
754 static_cast<u32>(Settings::values.resolution_factor)); 754 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
755 } else { 755 } else {
756 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) * 756 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) *
757 static_cast<u32>(Settings::values.resolution_factor)); 757 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
758 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) * 758 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) *
759 static_cast<u32>(Settings::values.resolution_factor)); 759 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
760 } 760 }
761} 761}
762 762
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 7e5ceccdb..6cfa9666d 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -366,7 +366,8 @@ ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage(
366 LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages); 366 LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages);
367 367
368 // Get language code from settings 368 // Get language code from settings
369 const auto language_code = Set::GetLanguageCodeFromIndex(Settings::values.language_index); 369 const auto language_code =
370 Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue());
370 371
371 // Convert to application language, get priority list 372 // Convert to application language, get priority list
372 const auto application_language = ConvertToApplicationLanguage(language_code); 373 const auto application_language = ConvertToApplicationLanguage(language_code);
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index e5cfd2101..34fe2fd82 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -91,7 +91,7 @@ void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t m
91} 91}
92 92
93void GetKeyCodeMapImpl(Kernel::HLERequestContext& ctx) { 93void GetKeyCodeMapImpl(Kernel::HLERequestContext& ctx) {
94 const auto language_code = available_language_codes[Settings::values.language_index]; 94 const auto language_code = available_language_codes[Settings::values.language_index.GetValue()];
95 const auto key_code = 95 const auto key_code =
96 std::find_if(language_to_layout.cbegin(), language_to_layout.cend(), 96 std::find_if(language_to_layout.cbegin(), language_to_layout.cend(),
97 [=](const auto& element) { return element.first == language_code; }); 97 [=](const auto& element) { return element.first == language_code; });
@@ -99,7 +99,7 @@ void GetKeyCodeMapImpl(Kernel::HLERequestContext& ctx) {
99 if (key_code == language_to_layout.cend()) { 99 if (key_code == language_to_layout.cend()) {
100 LOG_ERROR(Service_SET, 100 LOG_ERROR(Service_SET,
101 "Could not find keyboard layout for language index {}, defaulting to English us", 101 "Could not find keyboard layout for language index {}, defaulting to English us",
102 Settings::values.language_index); 102 Settings::values.language_index.GetValue());
103 } else { 103 } else {
104 layout = key_code->second; 104 layout = key_code->second;
105 } 105 }
@@ -163,11 +163,11 @@ void SET::GetQuestFlag(Kernel::HLERequestContext& ctx) {
163} 163}
164 164
165void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) { 165void SET::GetLanguageCode(Kernel::HLERequestContext& ctx) {
166 LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index); 166 LOG_DEBUG(Service_SET, "called {}", Settings::values.language_index.GetValue());
167 167
168 IPC::ResponseBuilder rb{ctx, 4}; 168 IPC::ResponseBuilder rb{ctx, 4};
169 rb.Push(RESULT_SUCCESS); 169 rb.Push(RESULT_SUCCESS);
170 rb.PushEnum(available_language_codes[Settings::values.language_index]); 170 rb.PushEnum(available_language_codes[Settings::values.language_index.GetValue()]);
171} 171}
172 172
173void SET::GetRegionCode(Kernel::HLERequestContext& ctx) { 173void SET::GetRegionCode(Kernel::HLERequestContext& ctx) {
@@ -175,7 +175,7 @@ void SET::GetRegionCode(Kernel::HLERequestContext& ctx) {
175 175
176 IPC::ResponseBuilder rb{ctx, 3}; 176 IPC::ResponseBuilder rb{ctx, 3};
177 rb.Push(RESULT_SUCCESS); 177 rb.Push(RESULT_SUCCESS);
178 rb.Push(Settings::values.region_index); 178 rb.Push(Settings::values.region_index.GetValue());
179} 179}
180 180
181void SET::GetKeyCodeMap(Kernel::HLERequestContext& ctx) { 181void SET::GetKeyCodeMap(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp
index e724d4ab8..865ed3b91 100644
--- a/src/core/hle/service/spl/module.cpp
+++ b/src/core/hle/service/spl/module.cpp
@@ -19,7 +19,7 @@ namespace Service::SPL {
19 19
20Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) 20Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
21 : ServiceFramework(name), module(std::move(module)), 21 : ServiceFramework(name), module(std::move(module)),
22 rng(Settings::values.rng_seed.value_or(std::time(nullptr))) {} 22 rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {}
23 23
24Module::Interface::~Interface() = default; 24Module::Interface::~Interface() = default;
25 25
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 157092074..552a5e4ef 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -519,9 +519,9 @@ private:
519 IGBPConnectRequestParcel request{ctx.ReadBuffer()}; 519 IGBPConnectRequestParcel request{ctx.ReadBuffer()};
520 IGBPConnectResponseParcel response{ 520 IGBPConnectResponseParcel response{
521 static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedWidth) * 521 static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedWidth) *
522 Settings::values.resolution_factor), 522 Settings::values.resolution_factor.GetValue()),
523 static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) * 523 static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) *
524 Settings::values.resolution_factor)}; 524 Settings::values.resolution_factor.GetValue())};
525 ctx.WriteBuffer(response.Serialize()); 525 ctx.WriteBuffer(response.Serialize());
526 break; 526 break;
527 } 527 }
@@ -748,14 +748,14 @@ private:
748 748
749 if (Settings::values.use_docked_mode) { 749 if (Settings::values.use_docked_mode) {
750 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) * 750 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
751 static_cast<u32>(Settings::values.resolution_factor)); 751 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
752 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) * 752 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *
753 static_cast<u32>(Settings::values.resolution_factor)); 753 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
754 } else { 754 } else {
755 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) * 755 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth) *
756 static_cast<u32>(Settings::values.resolution_factor)); 756 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
757 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) * 757 rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight) *
758 static_cast<u32>(Settings::values.resolution_factor)); 758 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
759 } 759 }
760 760
761 rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games. 761 rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games.
@@ -1029,9 +1029,9 @@ private:
1029 // between docked and undocked dimensions. We take the liberty of applying 1029 // between docked and undocked dimensions. We take the liberty of applying
1030 // the resolution scaling factor here. 1030 // the resolution scaling factor here.
1031 rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth) * 1031 rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth) *
1032 static_cast<u32>(Settings::values.resolution_factor)); 1032 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
1033 rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight) * 1033 rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight) *
1034 static_cast<u32>(Settings::values.resolution_factor)); 1034 static_cast<u32>(Settings::values.resolution_factor.GetValue()));
1035 } 1035 }
1036 1036
1037 void SetLayerScalingMode(Kernel::HLERequestContext& ctx) { 1037 void SetLayerScalingMode(Kernel::HLERequestContext& ctx) {
@@ -1064,8 +1064,8 @@ private:
1064 LOG_WARNING(Service_VI, "(STUBBED) called"); 1064 LOG_WARNING(Service_VI, "(STUBBED) called");
1065 1065
1066 DisplayInfo display_info; 1066 DisplayInfo display_info;
1067 display_info.width *= static_cast<u64>(Settings::values.resolution_factor); 1067 display_info.width *= static_cast<u64>(Settings::values.resolution_factor.GetValue());
1068 display_info.height *= static_cast<u64>(Settings::values.resolution_factor); 1068 display_info.height *= static_cast<u64>(Settings::values.resolution_factor.GetValue());
1069 ctx.WriteBuffer(&display_info, sizeof(DisplayInfo)); 1069 ctx.WriteBuffer(&display_info, sizeof(DisplayInfo));
1070 IPC::ResponseBuilder rb{ctx, 4}; 1070 IPC::ResponseBuilder rb{ctx, 4};
1071 rb.Push(RESULT_SUCCESS); 1071 rb.Push(RESULT_SUCCESS);
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 9f3a6b811..29339ead7 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -119,13 +119,14 @@ double PerfStats::GetLastFrameTimeScale() {
119} 119}
120 120
121void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) { 121void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
122 if (!Settings::values.use_frame_limit || Settings::values.use_multi_core) { 122 if (!Settings::values.use_frame_limit.GetValue() ||
123 Settings::values.use_multi_core.GetValue()) {
123 return; 124 return;
124 } 125 }
125 126
126 auto now = Clock::now(); 127 auto now = Clock::now();
127 128
128 const double sleep_scale = Settings::values.frame_limit / 100.0; 129 const double sleep_scale = Settings::values.frame_limit.GetValue() / 100.0;
129 130
130 // Max lag caused by slow frames. Shouldn't be more than the length of a frame at the current 131 // Max lag caused by slow frames. Shouldn't be more than the length of a frame at the current
131 // speed percent or it will clamp too much and prevent this from properly limiting to that 132 // speed percent or it will clamp too much and prevent this from properly limiting to that
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 56df5e925..d3886c4ec 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -62,6 +62,7 @@ const std::array<const char*, NumMouseButtons> mapping = {{
62} 62}
63 63
64Values values = {}; 64Values values = {};
65bool configuring_global = true;
65 66
66std::string GetTimeZoneString() { 67std::string GetTimeZoneString() {
67 static constexpr std::array<const char*, 46> timezones{{ 68 static constexpr std::array<const char*, 46> timezones{{
@@ -73,9 +74,9 @@ std::string GetTimeZoneString() {
73 "UCT", "Universal", "UTC", "W-SU", "WET", "Zulu", 74 "UCT", "Universal", "UTC", "W-SU", "WET", "Zulu",
74 }}; 75 }};
75 76
76 ASSERT(Settings::values.time_zone_index < timezones.size()); 77 ASSERT(Settings::values.time_zone_index.GetValue() < timezones.size());
77 78
78 return timezones[Settings::values.time_zone_index]; 79 return timezones[Settings::values.time_zone_index.GetValue()];
79} 80}
80 81
81void Apply() { 82void Apply() {
@@ -97,25 +98,25 @@ void LogSetting(const std::string& name, const T& value) {
97 98
98void LogSettings() { 99void LogSettings() {
99 LOG_INFO(Config, "yuzu Configuration:"); 100 LOG_INFO(Config, "yuzu Configuration:");
100 LogSetting("System_UseDockedMode", Settings::values.use_docked_mode); 101 LogSetting("Controls_UseDockedMode", Settings::values.use_docked_mode);
101 LogSetting("System_RngSeed", Settings::values.rng_seed.value_or(0)); 102 LogSetting("System_RngSeed", Settings::values.rng_seed.GetValue().value_or(0));
102 LogSetting("System_CurrentUser", Settings::values.current_user); 103 LogSetting("System_CurrentUser", Settings::values.current_user);
103 LogSetting("System_LanguageIndex", Settings::values.language_index); 104 LogSetting("System_LanguageIndex", Settings::values.language_index.GetValue());
104 LogSetting("System_RegionIndex", Settings::values.region_index); 105 LogSetting("System_RegionIndex", Settings::values.region_index.GetValue());
105 LogSetting("System_TimeZoneIndex", Settings::values.time_zone_index); 106 LogSetting("System_TimeZoneIndex", Settings::values.time_zone_index.GetValue());
106 LogSetting("Core_UseMultiCore", Settings::values.use_multi_core); 107 LogSetting("Core_UseMultiCore", Settings::values.use_multi_core.GetValue());
107 LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor); 108 LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor.GetValue());
108 LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit); 109 LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit.GetValue());
109 LogSetting("Renderer_FrameLimit", Settings::values.frame_limit); 110 LogSetting("Renderer_FrameLimit", Settings::values.frame_limit.GetValue());
110 LogSetting("Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache); 111 LogSetting("Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache.GetValue());
111 LogSetting("Renderer_GPUAccuracyLevel", Settings::values.gpu_accuracy); 112 LogSetting("Renderer_GPUAccuracyLevel", Settings::values.gpu_accuracy.GetValue());
112 LogSetting("Renderer_UseAsynchronousGpuEmulation", 113 LogSetting("Renderer_UseAsynchronousGpuEmulation",
113 Settings::values.use_asynchronous_gpu_emulation); 114 Settings::values.use_asynchronous_gpu_emulation.GetValue());
114 LogSetting("Renderer_UseVsync", Settings::values.use_vsync); 115 LogSetting("Renderer_UseVsync", Settings::values.use_vsync.GetValue());
115 LogSetting("Renderer_UseAssemblyShaders", Settings::values.use_assembly_shaders); 116 LogSetting("Renderer_UseAssemblyShaders", Settings::values.use_assembly_shaders.GetValue());
116 LogSetting("Renderer_AnisotropicFilteringLevel", Settings::values.max_anisotropy); 117 LogSetting("Renderer_AnisotropicFilteringLevel", Settings::values.max_anisotropy.GetValue());
117 LogSetting("Audio_OutputEngine", Settings::values.sink_id); 118 LogSetting("Audio_OutputEngine", Settings::values.sink_id);
118 LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); 119 LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching.GetValue());
119 LogSetting("Audio_OutputDevice", Settings::values.audio_device_id); 120 LogSetting("Audio_OutputDevice", Settings::values.audio_device_id);
120 LogSetting("DataStorage_UseVirtualSd", Settings::values.use_virtual_sd); 121 LogSetting("DataStorage_UseVirtualSd", Settings::values.use_virtual_sd);
121 LogSetting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)); 122 LogSetting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
@@ -131,15 +132,56 @@ float Volume() {
131 if (values.audio_muted) { 132 if (values.audio_muted) {
132 return 0.0f; 133 return 0.0f;
133 } 134 }
134 return values.volume; 135 return values.volume.GetValue();
135} 136}
136 137
137bool IsGPULevelExtreme() { 138bool IsGPULevelExtreme() {
138 return values.gpu_accuracy == GPUAccuracy::Extreme; 139 return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme;
139} 140}
140 141
141bool IsGPULevelHigh() { 142bool IsGPULevelHigh() {
142 return values.gpu_accuracy == GPUAccuracy::Extreme || values.gpu_accuracy == GPUAccuracy::High; 143 return values.gpu_accuracy.GetValue() == GPUAccuracy::Extreme ||
144 values.gpu_accuracy.GetValue() == GPUAccuracy::High;
145}
146
147void RestoreGlobalState() {
148 // If a game is running, DO NOT restore the global settings state
149 if (Core::System::GetInstance().IsPoweredOn()) {
150 return;
151 }
152
153 // Audio
154 values.enable_audio_stretching.SetGlobal(true);
155 values.volume.SetGlobal(true);
156
157 // Core
158 values.use_multi_core.SetGlobal(true);
159
160 // Renderer
161 values.renderer_backend.SetGlobal(true);
162 values.vulkan_device.SetGlobal(true);
163 values.aspect_ratio.SetGlobal(true);
164 values.max_anisotropy.SetGlobal(true);
165 values.use_frame_limit.SetGlobal(true);
166 values.frame_limit.SetGlobal(true);
167 values.use_disk_shader_cache.SetGlobal(true);
168 values.gpu_accuracy.SetGlobal(true);
169 values.use_asynchronous_gpu_emulation.SetGlobal(true);
170 values.use_vsync.SetGlobal(true);
171 values.use_assembly_shaders.SetGlobal(true);
172 values.use_fast_gpu_time.SetGlobal(true);
173 values.force_30fps_mode.SetGlobal(true);
174 values.bg_red.SetGlobal(true);
175 values.bg_green.SetGlobal(true);
176 values.bg_blue.SetGlobal(true);
177
178 // System
179 values.language_index.SetGlobal(true);
180 values.region_index.SetGlobal(true);
181 values.time_zone_index.SetGlobal(true);
182 values.rng_seed.SetGlobal(true);
183 values.custom_rtc.SetGlobal(true);
184 values.sound_index.SetGlobal(true);
143} 185}
144 186
145} // namespace Settings 187} // namespace Settings
diff --git a/src/core/settings.h b/src/core/settings.h
index a598ccbc1..850ca4072 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -382,20 +382,85 @@ enum class GPUAccuracy : u32 {
382 Extreme = 2, 382 Extreme = 2,
383}; 383};
384 384
385extern bool configuring_global;
386
387template <typename Type>
388class Setting final {
389public:
390 Setting() = default;
391 explicit Setting(Type val) : global{val} {}
392 ~Setting() = default;
393 void SetGlobal(bool to_global) {
394 use_global = to_global;
395 }
396 bool UsingGlobal() const {
397 return use_global;
398 }
399 Type GetValue(bool need_global = false) const {
400 if (use_global || need_global) {
401 return global;
402 }
403 return local;
404 }
405 void SetValue(const Type& value) {
406 if (use_global) {
407 global = value;
408 } else {
409 local = value;
410 }
411 }
412
413private:
414 bool use_global = true;
415 Type global{};
416 Type local{};
417};
418
385struct Values { 419struct Values {
420 // Audio
421 std::string audio_device_id;
422 std::string sink_id;
423 bool audio_muted;
424 Setting<bool> enable_audio_stretching;
425 Setting<float> volume;
426
427 // Core
428 Setting<bool> use_multi_core;
429
430 // Renderer
431 Setting<RendererBackend> renderer_backend;
432 bool renderer_debug;
433 Setting<int> vulkan_device;
434
435 Setting<u16> resolution_factor = Setting(static_cast<u16>(1));
436 Setting<int> aspect_ratio;
437 Setting<int> max_anisotropy;
438 Setting<bool> use_frame_limit;
439 Setting<u16> frame_limit;
440 Setting<bool> use_disk_shader_cache;
441 Setting<GPUAccuracy> gpu_accuracy;
442 Setting<bool> use_asynchronous_gpu_emulation;
443 Setting<bool> use_vsync;
444 Setting<bool> use_assembly_shaders;
445 Setting<bool> force_30fps_mode;
446 Setting<bool> use_fast_gpu_time;
447
448 Setting<float> bg_red;
449 Setting<float> bg_green;
450 Setting<float> bg_blue;
451
386 // System 452 // System
387 bool use_docked_mode; 453 Setting<std::optional<u32>> rng_seed;
388 std::optional<u32> rng_seed;
389 // Measured in seconds since epoch 454 // Measured in seconds since epoch
390 std::optional<std::chrono::seconds> custom_rtc; 455 Setting<std::optional<std::chrono::seconds>> custom_rtc;
391 // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc` 456 // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
392 std::chrono::seconds custom_rtc_differential; 457 std::chrono::seconds custom_rtc_differential;
393 458
394 s32 current_user; 459 s32 current_user;
395 s32 language_index; 460 Setting<s32> language_index;
396 s32 region_index; 461 Setting<s32> region_index;
397 s32 time_zone_index; 462 Setting<s32> time_zone_index;
398 s32 sound_index; 463 Setting<s32> sound_index;
399 464
400 // Controls 465 // Controls
401 std::array<PlayerInput, 10> players; 466 std::array<PlayerInput, 10> players;
@@ -419,8 +484,7 @@ struct Values {
419 u16 udp_input_port; 484 u16 udp_input_port;
420 u8 udp_pad_index; 485 u8 udp_pad_index;
421 486
422 // Core 487 bool use_docked_mode;
423 bool use_multi_core;
424 488
425 // Data Storage 489 // Data Storage
426 bool use_virtual_sd; 490 bool use_virtual_sd;
@@ -432,39 +496,6 @@ struct Values {
432 NANDUserSize nand_user_size; 496 NANDUserSize nand_user_size;
433 SDMCSize sdmc_size; 497 SDMCSize sdmc_size;
434 498
435 // Renderer
436 RendererBackend renderer_backend;
437 bool renderer_debug;
438 int vulkan_device;
439
440 u16 resolution_factor{1};
441 int aspect_ratio;
442 int max_anisotropy;
443 bool use_frame_limit;
444 u16 frame_limit;
445 bool use_disk_shader_cache;
446 GPUAccuracy gpu_accuracy;
447 bool use_asynchronous_gpu_emulation;
448 bool use_vsync;
449 bool use_assembly_shaders;
450 bool force_30fps_mode;
451 bool use_fast_gpu_time;
452
453 float bg_red;
454 float bg_green;
455 float bg_blue;
456
457 std::string log_filter;
458
459 bool use_dev_keys;
460
461 // Audio
462 bool audio_muted;
463 std::string sink_id;
464 bool enable_audio_stretching;
465 std::string audio_device_id;
466 float volume;
467
468 // Debugging 499 // Debugging
469 bool record_frame_times; 500 bool record_frame_times;
470 bool use_gdbstub; 501 bool use_gdbstub;
@@ -477,7 +508,11 @@ struct Values {
477 bool disable_cpu_opt; 508 bool disable_cpu_opt;
478 bool disable_macro_jit; 509 bool disable_macro_jit;
479 510
480 // BCAT 511 // Misceallaneous
512 std::string log_filter;
513 bool use_dev_keys;
514
515 // Services
481 std::string bcat_backend; 516 std::string bcat_backend;
482 bool bcat_boxcat_local; 517 bool bcat_boxcat_local;
483 518
@@ -501,4 +536,7 @@ std::string GetTimeZoneString();
501void Apply(); 536void Apply();
502void LogSettings(); 537void LogSettings();
503 538
539// Restore the global state of all applicable settings in the Values struct
540void RestoreGlobalState();
541
504} // namespace Settings 542} // namespace Settings
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index c781b3cfc..78915e6db 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -189,19 +189,24 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
189 // Log user configuration information 189 // Log user configuration information
190 constexpr auto field_type = Telemetry::FieldType::UserConfig; 190 constexpr auto field_type = Telemetry::FieldType::UserConfig;
191 AddField(field_type, "Audio_SinkId", Settings::values.sink_id); 191 AddField(field_type, "Audio_SinkId", Settings::values.sink_id);
192 AddField(field_type, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); 192 AddField(field_type, "Audio_EnableAudioStretching",
193 AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core); 193 Settings::values.enable_audio_stretching.GetValue());
194 AddField(field_type, "Renderer_Backend", TranslateRenderer(Settings::values.renderer_backend)); 194 AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue());
195 AddField(field_type, "Renderer_ResolutionFactor", Settings::values.resolution_factor); 195 AddField(field_type, "Renderer_Backend",
196 AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit); 196 TranslateRenderer(Settings::values.renderer_backend.GetValue()));
197 AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit); 197 AddField(field_type, "Renderer_ResolutionFactor",
198 AddField(field_type, "Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache); 198 Settings::values.resolution_factor.GetValue());
199 AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit.GetValue());
200 AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit.GetValue());
201 AddField(field_type, "Renderer_UseDiskShaderCache",
202 Settings::values.use_disk_shader_cache.GetValue());
199 AddField(field_type, "Renderer_GPUAccuracyLevel", 203 AddField(field_type, "Renderer_GPUAccuracyLevel",
200 TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy)); 204 TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue()));
201 AddField(field_type, "Renderer_UseAsynchronousGpuEmulation", 205 AddField(field_type, "Renderer_UseAsynchronousGpuEmulation",
202 Settings::values.use_asynchronous_gpu_emulation); 206 Settings::values.use_asynchronous_gpu_emulation.GetValue());
203 AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync); 207 AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue());
204 AddField(field_type, "Renderer_UseAssemblyShaders", Settings::values.use_assembly_shaders); 208 AddField(field_type, "Renderer_UseAssemblyShaders",
209 Settings::values.use_assembly_shaders.GetValue());
205 AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode); 210 AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode);
206} 211}
207 212
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index c6479af9f..dd7ce8c99 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -96,7 +96,8 @@ public:
96 } 96 }
97 if (is_written) { 97 if (is_written) {
98 map->MarkAsModified(true, GetModifiedTicks()); 98 map->MarkAsModified(true, GetModifiedTicks());
99 if (Settings::IsGPULevelHigh() && Settings::values.use_asynchronous_gpu_emulation) { 99 if (Settings::IsGPULevelHigh() &&
100 Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
100 MarkForAsyncFlush(map); 101 MarkForAsyncFlush(map);
101 } 102 }
102 if (!map->is_written) { 103 if (!map->is_written) {
@@ -369,7 +370,8 @@ private:
369 } 370 }
370 if (modified_inheritance) { 371 if (modified_inheritance) {
371 map->MarkAsModified(true, GetModifiedTicks()); 372 map->MarkAsModified(true, GetModifiedTicks());
372 if (Settings::IsGPULevelHigh() && Settings::values.use_asynchronous_gpu_emulation) { 373 if (Settings::IsGPULevelHigh() &&
374 Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
373 MarkForAsyncFlush(map); 375 MarkForAsyncFlush(map);
374 } 376 }
375 } 377 }
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 482e49711..758bfe148 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -157,7 +157,7 @@ u64 GPU::GetTicks() const {
157 constexpr u64 gpu_ticks_den = 625; 157 constexpr u64 gpu_ticks_den = 625;
158 158
159 u64 nanoseconds = system.CoreTiming().GetGlobalTimeNs().count(); 159 u64 nanoseconds = system.CoreTiming().GetGlobalTimeNs().count();
160 if (Settings::values.use_fast_gpu_time) { 160 if (Settings::values.use_fast_gpu_time.GetValue()) {
161 nanoseconds /= 256; 161 nanoseconds /= 256;
162 } 162 }
163 const u64 nanoseconds_num = nanoseconds / gpu_ticks_den; 163 const u64 nanoseconds_num = nanoseconds / gpu_ticks_den;
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index e12dab899..0d3a88765 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -132,7 +132,7 @@ public:
132 } 132 }
133 133
134 query->BindCounter(Stream(type).Current(), timestamp); 134 query->BindCounter(Stream(type).Current(), timestamp);
135 if (Settings::values.use_asynchronous_gpu_emulation) { 135 if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
136 AsyncFlushQuery(cpu_addr); 136 AsyncFlushQuery(cpu_addr);
137 } 137 }
138 } 138 }
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 919d1f2d4..dfb06e87e 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -18,7 +18,7 @@ RendererBase::~RendererBase() = default;
18void RendererBase::RefreshBaseSettings() { 18void RendererBase::RefreshBaseSettings() {
19 UpdateCurrentFramebufferLayout(); 19 UpdateCurrentFramebufferLayout();
20 20
21 renderer_settings.use_framelimiter = Settings::values.use_frame_limit; 21 renderer_settings.use_framelimiter = Settings::values.use_frame_limit.GetValue();
22 renderer_settings.set_background_color = true; 22 renderer_settings.set_background_color = true;
23} 23}
24 24
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 208fc6167..c1f20f0ab 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -229,15 +229,15 @@ Device::Device()
229 // uniform buffers as "push constants" 229 // uniform buffers as "push constants"
230 has_fast_buffer_sub_data = is_nvidia && !disable_fast_buffer_sub_data; 230 has_fast_buffer_sub_data = is_nvidia && !disable_fast_buffer_sub_data;
231 231
232 use_assembly_shaders = Settings::values.use_assembly_shaders && GLAD_GL_NV_gpu_program5 && 232 use_assembly_shaders = Settings::values.use_assembly_shaders.GetValue() &&
233 GLAD_GL_NV_compute_program5 && GLAD_GL_NV_transform_feedback && 233 GLAD_GL_NV_gpu_program5 && GLAD_GL_NV_compute_program5 &&
234 GLAD_GL_NV_transform_feedback2; 234 GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2;
235 235
236 LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi); 236 LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi);
237 LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug); 237 LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug);
238 LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug); 238 LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug);
239 239
240 if (Settings::values.use_assembly_shaders && !use_assembly_shaders) { 240 if (Settings::values.use_assembly_shaders.GetValue() && !use_assembly_shaders) {
241 LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported"); 241 LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported");
242 } 242 }
243} 243}
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index 653c3f2f9..2dcc2b0eb 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -213,7 +213,7 @@ ShaderDiskCacheOpenGL::~ShaderDiskCacheOpenGL() = default;
213std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTransferable() { 213std::optional<std::vector<ShaderDiskCacheEntry>> ShaderDiskCacheOpenGL::LoadTransferable() {
214 // Skip games without title id 214 // Skip games without title id
215 const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0; 215 const bool has_title_id = system.CurrentProcess()->GetTitleID() != 0;
216 if (!Settings::values.use_disk_shader_cache || !has_title_id) { 216 if (!Settings::values.use_disk_shader_cache.GetValue() || !has_title_id) {
217 return {}; 217 return {};
218 } 218 }
219 219
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index c40adb6e7..e66cdc083 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -455,8 +455,8 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
455void RendererOpenGL::InitOpenGLObjects() { 455void RendererOpenGL::InitOpenGLObjects() {
456 frame_mailbox = std::make_unique<FrameMailbox>(); 456 frame_mailbox = std::make_unique<FrameMailbox>();
457 457
458 glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 458 glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
459 0.0f); 459 Settings::values.bg_blue.GetValue(), 0.0f);
460 460
461 // Create shader programs 461 // Create shader programs
462 OGLShader vertex_shader; 462 OGLShader vertex_shader;
@@ -561,8 +561,8 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,
561void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { 561void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
562 if (renderer_settings.set_background_color) { 562 if (renderer_settings.set_background_color) {
563 // Update background color before drawing 563 // Update background color before drawing
564 glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 564 glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
565 0.0f); 565 Settings::values.bg_blue.GetValue(), 0.0f);
566 } 566 }
567 567
568 // Set projection matrix 568 // Set projection matrix
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 2d9b18ed9..2258479f5 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -409,7 +409,7 @@ bool RendererVulkan::PickDevices() {
409 return false; 409 return false;
410 } 410 }
411 411
412 const s32 device_index = Settings::values.vulkan_device; 412 const s32 device_index = Settings::values.vulkan_device.GetValue();
413 if (device_index < 0 || device_index >= static_cast<s32>(devices->size())) { 413 if (device_index < 0 || device_index >= static_cast<s32>(devices->size())) {
414 LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index); 414 LOG_ERROR(Render_Vulkan, "Invalid device index {}!", device_index);
415 return false; 415 return false;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 6207d8dfe..cdcddb225 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -249,7 +249,7 @@ public:
249 auto& surface = render_targets[index].target; 249 auto& surface = render_targets[index].target;
250 surface->MarkAsRenderTarget(false, NO_RT); 250 surface->MarkAsRenderTarget(false, NO_RT);
251 const auto& cr_params = surface->GetSurfaceParams(); 251 const auto& cr_params = surface->GetSurfaceParams();
252 if (!cr_params.is_tiled && Settings::values.use_asynchronous_gpu_emulation) { 252 if (!cr_params.is_tiled && Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
253 AsyncFlushSurface(surface); 253 AsyncFlushSurface(surface);
254 } 254 }
255 } 255 }
diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp
index d1939d744..4171e3ef2 100644
--- a/src/video_core/textures/texture.cpp
+++ b/src/video_core/textures/texture.cpp
@@ -48,7 +48,7 @@ constexpr std::array<float, 256> SRGB_CONVERSION_LUT = {
48}; 48};
49 49
50unsigned SettingsMinimumAnisotropy() noexcept { 50unsigned SettingsMinimumAnisotropy() noexcept {
51 switch (static_cast<Anisotropy>(Settings::values.max_anisotropy)) { 51 switch (static_cast<Anisotropy>(Settings::values.max_anisotropy.GetValue())) {
52 default: 52 default:
53 case Anisotropy::Default: 53 case Anisotropy::Default:
54 return 1U; 54 return 1U;
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index f60bdc60a..45f360bdd 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -19,7 +19,7 @@ namespace {
19std::unique_ptr<VideoCore::RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window, 19std::unique_ptr<VideoCore::RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window,
20 Core::System& system, 20 Core::System& system,
21 Core::Frontend::GraphicsContext& context) { 21 Core::Frontend::GraphicsContext& context) {
22 switch (Settings::values.renderer_backend) { 22 switch (Settings::values.renderer_backend.GetValue()) {
23 case Settings::RendererBackend::OpenGL: 23 case Settings::RendererBackend::OpenGL:
24 return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system, context); 24 return std::make_unique<OpenGL::RendererOpenGL>(emu_window, system, context);
25#ifdef HAS_VULKAN 25#ifdef HAS_VULKAN
@@ -42,7 +42,7 @@ std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Cor
42 return nullptr; 42 return nullptr;
43 } 43 }
44 44
45 if (Settings::values.use_asynchronous_gpu_emulation) { 45 if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
46 return std::make_unique<VideoCommon::GPUAsynch>(system, std::move(renderer), 46 return std::make_unique<VideoCommon::GPUAsynch>(system, std::move(renderer),
47 std::move(context)); 47 std::move(context));
48 } 48 }
@@ -51,8 +51,8 @@ std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Cor
51 51
52u16 GetResolutionScaleFactor(const RendererBase& renderer) { 52u16 GetResolutionScaleFactor(const RendererBase& renderer) {
53 return static_cast<u16>( 53 return static_cast<u16>(
54 Settings::values.resolution_factor != 0 54 Settings::values.resolution_factor.GetValue() != 0
55 ? Settings::values.resolution_factor 55 ? Settings::values.resolution_factor.GetValue()
56 : renderer.GetRenderWindow().GetFramebufferLayout().GetScalingRatio()); 56 : renderer.GetRenderWindow().GetFramebufferLayout().GetScalingRatio());
57} 57}
58 58
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 75c27e39e..742b72856 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -24,6 +24,8 @@ add_executable(yuzu
24 compatibility_list.h 24 compatibility_list.h
25 configuration/config.cpp 25 configuration/config.cpp
26 configuration/config.h 26 configuration/config.h
27 configuration/configuration_shared.cpp
28 configuration/configuration_shared.h
27 configuration/configure.ui 29 configuration/configure.ui
28 configuration/configure_audio.cpp 30 configuration/configure_audio.cpp
29 configuration/configure_audio.h 31 configuration/configure_audio.h
@@ -60,9 +62,12 @@ add_executable(yuzu
60 configuration/configure_mouse_advanced.cpp 62 configuration/configure_mouse_advanced.cpp
61 configuration/configure_mouse_advanced.h 63 configuration/configure_mouse_advanced.h
62 configuration/configure_mouse_advanced.ui 64 configuration/configure_mouse_advanced.ui
63 configuration/configure_per_general.cpp 65 configuration/configure_per_game.cpp
64 configuration/configure_per_general.h 66 configuration/configure_per_game.h
65 configuration/configure_per_general.ui 67 configuration/configure_per_game.ui
68 configuration/configure_per_game_addons.cpp
69 configuration/configure_per_game_addons.h
70 configuration/configure_per_game_addons.ui
66 configuration/configure_profile_manager.cpp 71 configuration/configure_profile_manager.cpp
67 configuration/configure_profile_manager.h 72 configuration/configure_profile_manager.h
68 configuration/configure_profile_manager.ui 73 configuration/configure_profile_manager.ui
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 4bfce48a4..5738787ac 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -145,7 +145,7 @@ public:
145 145
146 // disable vsync for any shared contexts 146 // disable vsync for any shared contexts
147 auto format = share_context->format(); 147 auto format = share_context->format();
148 format.setSwapInterval(main_surface ? Settings::values.use_vsync : 0); 148 format.setSwapInterval(main_surface ? Settings::values.use_vsync.GetValue() : 0);
149 149
150 context = std::make_unique<QOpenGLContext>(); 150 context = std::make_unique<QOpenGLContext>();
151 context->setShareContext(share_context); 151 context->setShareContext(share_context);
@@ -495,7 +495,7 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
495 495
496std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { 496std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
497#ifdef HAS_OPENGL 497#ifdef HAS_OPENGL
498 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { 498 if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) {
499 auto c = static_cast<OpenGLSharedContext*>(main_context.get()); 499 auto c = static_cast<OpenGLSharedContext*>(main_context.get());
500 // Bind the shared contexts to the main surface in case the backend wants to take over 500 // Bind the shared contexts to the main surface in case the backend wants to take over
501 // presentation 501 // presentation
@@ -511,7 +511,7 @@ bool GRenderWindow::InitRenderTarget() {
511 511
512 first_frame = false; 512 first_frame = false;
513 513
514 switch (Settings::values.renderer_backend) { 514 switch (Settings::values.renderer_backend.GetValue()) {
515 case Settings::RendererBackend::OpenGL: 515 case Settings::RendererBackend::OpenGL:
516 if (!InitializeOpenGL()) { 516 if (!InitializeOpenGL()) {
517 return false; 517 return false;
@@ -538,7 +538,7 @@ bool GRenderWindow::InitRenderTarget() {
538 OnFramebufferSizeChanged(); 538 OnFramebufferSizeChanged();
539 BackupGeometry(); 539 BackupGeometry();
540 540
541 if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { 541 if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) {
542 if (!LoadOpenGL()) { 542 if (!LoadOpenGL()) {
543 return false; 543 return false;
544 } 544 }
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 5e0d0e7af..1b2b1b2bb 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -13,17 +13,20 @@
13#include "input_common/udp/client.h" 13#include "input_common/udp/client.h"
14#include "yuzu/configuration/config.h" 14#include "yuzu/configuration/config.h"
15 15
16Config::Config() { 16Config::Config(const std::string& config_file, bool is_global) {
17 // TODO: Don't hardcode the path; let the frontend decide where to put the config files. 17 // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
18 qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini"; 18 qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + config_file;
19 FileUtil::CreateFullPath(qt_config_loc); 19 FileUtil::CreateFullPath(qt_config_loc);
20 qt_config = 20 qt_config =
21 std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); 21 std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
22 global = is_global;
22 Reload(); 23 Reload();
23} 24}
24 25
25Config::~Config() { 26Config::~Config() {
26 Save(); 27 if (global) {
28 Save();
29 }
27} 30}
28 31
29const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = { 32const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
@@ -402,16 +405,19 @@ void Config::ApplyDefaultProfileIfInputInvalid() {
402void Config::ReadAudioValues() { 405void Config::ReadAudioValues() {
403 qt_config->beginGroup(QStringLiteral("Audio")); 406 qt_config->beginGroup(QStringLiteral("Audio"));
404 407
405 Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto")) 408 if (global) {
406 .toString() 409 Settings::values.sink_id =
407 .toStdString(); 410 ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto"))
408 Settings::values.enable_audio_stretching = 411 .toString()
409 ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool(); 412 .toStdString();
410 Settings::values.audio_device_id = 413 Settings::values.audio_device_id =
411 ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto")) 414 ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto"))
412 .toString() 415 .toString()
413 .toStdString(); 416 .toStdString();
414 Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat(); 417 }
418 ReadSettingGlobal(Settings::values.enable_audio_stretching,
419 QStringLiteral("enable_audio_stretching"), true);
420 ReadSettingGlobal(Settings::values.volume, QStringLiteral("volume"), 1);
415 421
416 qt_config->endGroup(); 422 qt_config->endGroup();
417} 423}
@@ -440,6 +446,8 @@ void Config::ReadControlValues() {
440 .toInt()); 446 .toInt());
441 Settings::values.udp_pad_index = 447 Settings::values.udp_pad_index =
442 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); 448 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
449 Settings::values.use_docked_mode =
450 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
443 451
444 qt_config->endGroup(); 452 qt_config->endGroup();
445} 453}
@@ -447,7 +455,7 @@ void Config::ReadControlValues() {
447void Config::ReadCoreValues() { 455void Config::ReadCoreValues() {
448 qt_config->beginGroup(QStringLiteral("Core")); 456 qt_config->beginGroup(QStringLiteral("Core"));
449 457
450 Settings::values.use_multi_core = ReadSetting(QStringLiteral("use_multi_core"), false).toBool(); 458 ReadSettingGlobal(Settings::values.use_multi_core, QStringLiteral("use_multi_core"), false);
451 459
452 qt_config->endGroup(); 460 qt_config->endGroup();
453} 461}
@@ -628,32 +636,28 @@ void Config::ReadPathValues() {
628void Config::ReadRendererValues() { 636void Config::ReadRendererValues() {
629 qt_config->beginGroup(QStringLiteral("Renderer")); 637 qt_config->beginGroup(QStringLiteral("Renderer"));
630 638
631 Settings::values.renderer_backend = 639 ReadSettingGlobal(Settings::values.renderer_backend, QStringLiteral("backend"), 0);
632 static_cast<Settings::RendererBackend>(ReadSetting(QStringLiteral("backend"), 0).toInt()); 640 ReadSettingGlobal(Settings::values.renderer_debug, QStringLiteral("debug"), false);
633 Settings::values.renderer_debug = ReadSetting(QStringLiteral("debug"), false).toBool(); 641 ReadSettingGlobal(Settings::values.vulkan_device, QStringLiteral("vulkan_device"), 0);
634 Settings::values.vulkan_device = ReadSetting(QStringLiteral("vulkan_device"), 0).toInt(); 642 ReadSettingGlobal(Settings::values.aspect_ratio, QStringLiteral("aspect_ratio"), 0);
635 Settings::values.aspect_ratio = ReadSetting(QStringLiteral("aspect_ratio"), 0).toInt(); 643 ReadSettingGlobal(Settings::values.max_anisotropy, QStringLiteral("max_anisotropy"), 0);
636 Settings::values.max_anisotropy = ReadSetting(QStringLiteral("max_anisotropy"), 0).toInt(); 644 ReadSettingGlobal(Settings::values.use_frame_limit, QStringLiteral("use_frame_limit"), true);
637 Settings::values.use_frame_limit = 645 ReadSettingGlobal(Settings::values.frame_limit, QStringLiteral("frame_limit"), 100);
638 ReadSetting(QStringLiteral("use_frame_limit"), true).toBool(); 646 ReadSettingGlobal(Settings::values.use_disk_shader_cache,
639 Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toUInt(); 647 QStringLiteral("use_disk_shader_cache"), true);
640 Settings::values.use_disk_shader_cache = 648 ReadSettingGlobal(Settings::values.gpu_accuracy, QStringLiteral("gpu_accuracy"), 0);
641 ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool(); 649 ReadSettingGlobal(Settings::values.use_asynchronous_gpu_emulation,
642 const int gpu_accuracy_level = ReadSetting(QStringLiteral("gpu_accuracy"), 0).toInt(); 650 QStringLiteral("use_asynchronous_gpu_emulation"), false);
643 Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(gpu_accuracy_level); 651 ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true);
644 Settings::values.use_asynchronous_gpu_emulation = 652 ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"),
645 ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool(); 653 false);
646 Settings::values.use_vsync = ReadSetting(QStringLiteral("use_vsync"), true).toBool(); 654 ReadSettingGlobal(Settings::values.use_fast_gpu_time, QStringLiteral("use_fast_gpu_time"),
647 Settings::values.use_assembly_shaders = 655 true);
648 ReadSetting(QStringLiteral("use_assembly_shaders"), false).toBool(); 656 ReadSettingGlobal(Settings::values.force_30fps_mode, QStringLiteral("force_30fps_mode"), false);
649 Settings::values.use_fast_gpu_time = 657
650 ReadSetting(QStringLiteral("use_fast_gpu_time"), true).toBool(); 658 ReadSettingGlobal(Settings::values.bg_red, QStringLiteral("bg_red"), 0.0);
651 Settings::values.force_30fps_mode = 659 ReadSettingGlobal(Settings::values.bg_green, QStringLiteral("bg_green"), 0.0);
652 ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool(); 660 ReadSettingGlobal(Settings::values.bg_blue, QStringLiteral("bg_blue"), 0.0);
653
654 Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat();
655 Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat();
656 Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat();
657 661
658 qt_config->endGroup(); 662 qt_config->endGroup();
659} 663}
@@ -682,35 +686,45 @@ void Config::ReadShortcutValues() {
682void Config::ReadSystemValues() { 686void Config::ReadSystemValues() {
683 qt_config->beginGroup(QStringLiteral("System")); 687 qt_config->beginGroup(QStringLiteral("System"));
684 688
685 Settings::values.use_docked_mode = 689 ReadSettingGlobal(Settings::values.current_user, QStringLiteral("current_user"), 0);
686 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool(); 690 Settings::values.current_user =
687 691 std::clamp<int>(Settings::values.current_user, 0, Service::Account::MAX_USERS - 1);
688 Settings::values.current_user = std::clamp<int>(
689 ReadSetting(QStringLiteral("current_user"), 0).toInt(), 0, Service::Account::MAX_USERS - 1);
690 692
691 Settings::values.language_index = ReadSetting(QStringLiteral("language_index"), 1).toInt(); 693 ReadSettingGlobal(Settings::values.language_index, QStringLiteral("language_index"), 1);
692 694
693 Settings::values.region_index = ReadSetting(QStringLiteral("region_index"), 1).toInt(); 695 ReadSettingGlobal(Settings::values.region_index, QStringLiteral("region_index"), 1);
694 696
695 Settings::values.time_zone_index = ReadSetting(QStringLiteral("time_zone_index"), 0).toInt(); 697 ReadSettingGlobal(Settings::values.time_zone_index, QStringLiteral("time_zone_index"), 0);
696 698
697 const auto rng_seed_enabled = ReadSetting(QStringLiteral("rng_seed_enabled"), false).toBool(); 699 bool rng_seed_enabled;
698 if (rng_seed_enabled) { 700 ReadSettingGlobal(rng_seed_enabled, QStringLiteral("rng_seed_enabled"), false);
699 Settings::values.rng_seed = ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong(); 701 bool rng_seed_global =
700 } else { 702 global || qt_config->value(QStringLiteral("rng_seed/use_global"), true).toBool();
701 Settings::values.rng_seed = std::nullopt; 703 Settings::values.rng_seed.SetGlobal(rng_seed_global);
704 if (global || !rng_seed_global) {
705 if (rng_seed_enabled) {
706 Settings::values.rng_seed.SetValue(
707 ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong());
708 } else {
709 Settings::values.rng_seed.SetValue(std::nullopt);
710 }
702 } 711 }
703 712
704 const auto custom_rtc_enabled = 713 bool custom_rtc_enabled;
705 ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool(); 714 ReadSettingGlobal(custom_rtc_enabled, QStringLiteral("custom_rtc_enabled"), false);
706 if (custom_rtc_enabled) { 715 bool custom_rtc_global =
707 Settings::values.custom_rtc = 716 global || qt_config->value(QStringLiteral("custom_rtc/use_global"), true).toBool();
708 std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong()); 717 Settings::values.custom_rtc.SetGlobal(custom_rtc_global);
709 } else { 718 if (global || !custom_rtc_global) {
710 Settings::values.custom_rtc = std::nullopt; 719 if (custom_rtc_enabled) {
720 Settings::values.custom_rtc.SetValue(
721 std::chrono::seconds(ReadSetting(QStringLiteral("custom_rtc"), 0).toULongLong()));
722 } else {
723 Settings::values.custom_rtc.SetValue(std::nullopt);
724 }
711 } 725 }
712 726
713 Settings::values.sound_index = ReadSetting(QStringLiteral("sound_index"), 1).toInt(); 727 ReadSettingGlobal(Settings::values.sound_index, QStringLiteral("sound_index"), 1);
714 728
715 qt_config->endGroup(); 729 qt_config->endGroup();
716} 730}
@@ -804,18 +818,20 @@ void Config::ReadWebServiceValues() {
804} 818}
805 819
806void Config::ReadValues() { 820void Config::ReadValues() {
807 ReadControlValues(); 821 if (global) {
822 ReadControlValues();
823 ReadDataStorageValues();
824 ReadDebuggingValues();
825 ReadDisabledAddOnValues();
826 ReadServiceValues();
827 ReadUIValues();
828 ReadWebServiceValues();
829 ReadMiscellaneousValues();
830 }
808 ReadCoreValues(); 831 ReadCoreValues();
809 ReadRendererValues(); 832 ReadRendererValues();
810 ReadAudioValues(); 833 ReadAudioValues();
811 ReadDataStorageValues();
812 ReadSystemValues(); 834 ReadSystemValues();
813 ReadMiscellaneousValues();
814 ReadDebuggingValues();
815 ReadWebServiceValues();
816 ReadServiceValues();
817 ReadDisabledAddOnValues();
818 ReadUIValues();
819} 835}
820 836
821void Config::SavePlayerValues() { 837void Config::SavePlayerValues() {
@@ -902,30 +918,35 @@ void Config::SaveTouchscreenValues() {
902} 918}
903 919
904void Config::SaveValues() { 920void Config::SaveValues() {
905 SaveControlValues(); 921 if (global) {
922 SaveControlValues();
923 SaveDataStorageValues();
924 SaveDebuggingValues();
925 SaveDisabledAddOnValues();
926 SaveServiceValues();
927 SaveUIValues();
928 SaveWebServiceValues();
929 SaveMiscellaneousValues();
930 }
906 SaveCoreValues(); 931 SaveCoreValues();
907 SaveRendererValues(); 932 SaveRendererValues();
908 SaveAudioValues(); 933 SaveAudioValues();
909 SaveDataStorageValues();
910 SaveSystemValues(); 934 SaveSystemValues();
911 SaveMiscellaneousValues();
912 SaveDebuggingValues();
913 SaveWebServiceValues();
914 SaveServiceValues();
915 SaveDisabledAddOnValues();
916 SaveUIValues();
917} 935}
918 936
919void Config::SaveAudioValues() { 937void Config::SaveAudioValues() {
920 qt_config->beginGroup(QStringLiteral("Audio")); 938 qt_config->beginGroup(QStringLiteral("Audio"));
921 939
922 WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id), 940 if (global) {
923 QStringLiteral("auto")); 941 WriteSetting(QStringLiteral("output_engine"),
924 WriteSetting(QStringLiteral("enable_audio_stretching"), 942 QString::fromStdString(Settings::values.sink_id), QStringLiteral("auto"));
925 Settings::values.enable_audio_stretching, true); 943 WriteSetting(QStringLiteral("output_device"),
926 WriteSetting(QStringLiteral("output_device"), 944 QString::fromStdString(Settings::values.audio_device_id),
927 QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto")); 945 QStringLiteral("auto"));
928 WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f); 946 }
947 WriteSettingGlobal(QStringLiteral("enable_audio_stretching"),
948 Settings::values.enable_audio_stretching, true);
949 WriteSettingGlobal(QStringLiteral("volume"), Settings::values.volume, 1.0f);
929 950
930 qt_config->endGroup(); 951 qt_config->endGroup();
931} 952}
@@ -948,6 +969,7 @@ void Config::SaveControlValues() {
948 WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, 969 WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
949 InputCommon::CemuhookUDP::DEFAULT_PORT); 970 InputCommon::CemuhookUDP::DEFAULT_PORT);
950 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0); 971 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
972 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
951 973
952 qt_config->endGroup(); 974 qt_config->endGroup();
953} 975}
@@ -955,7 +977,7 @@ void Config::SaveControlValues() {
955void Config::SaveCoreValues() { 977void Config::SaveCoreValues() {
956 qt_config->beginGroup(QStringLiteral("Core")); 978 qt_config->beginGroup(QStringLiteral("Core"));
957 979
958 WriteSetting(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false); 980 WriteSettingGlobal(QStringLiteral("use_multi_core"), Settings::values.use_multi_core, false);
959 981
960 qt_config->endGroup(); 982 qt_config->endGroup();
961} 983}
@@ -1078,29 +1100,34 @@ void Config::SavePathValues() {
1078void Config::SaveRendererValues() { 1100void Config::SaveRendererValues() {
1079 qt_config->beginGroup(QStringLiteral("Renderer")); 1101 qt_config->beginGroup(QStringLiteral("Renderer"));
1080 1102
1081 WriteSetting(QStringLiteral("backend"), static_cast<int>(Settings::values.renderer_backend), 0); 1103 WriteSettingGlobal(QStringLiteral("backend"),
1104 static_cast<int>(Settings::values.renderer_backend.GetValue(global)),
1105 Settings::values.renderer_backend.UsingGlobal(), 0);
1082 WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false); 1106 WriteSetting(QStringLiteral("debug"), Settings::values.renderer_debug, false);
1083 WriteSetting(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0); 1107 WriteSettingGlobal(QStringLiteral("vulkan_device"), Settings::values.vulkan_device, 0);
1084 WriteSetting(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0); 1108 WriteSettingGlobal(QStringLiteral("aspect_ratio"), Settings::values.aspect_ratio, 0);
1085 WriteSetting(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0); 1109 WriteSettingGlobal(QStringLiteral("max_anisotropy"), Settings::values.max_anisotropy, 0);
1086 WriteSetting(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true); 1110 WriteSettingGlobal(QStringLiteral("use_frame_limit"), Settings::values.use_frame_limit, true);
1087 WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100); 1111 WriteSettingGlobal(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100);
1088 WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache, 1112 WriteSettingGlobal(QStringLiteral("use_disk_shader_cache"),
1089 true); 1113 Settings::values.use_disk_shader_cache, true);
1090 WriteSetting(QStringLiteral("gpu_accuracy"), static_cast<int>(Settings::values.gpu_accuracy), 1114 WriteSettingGlobal(QStringLiteral("gpu_accuracy"),
1091 0); 1115 static_cast<int>(Settings::values.gpu_accuracy.GetValue(global)),
1092 WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"), 1116 Settings::values.gpu_accuracy.UsingGlobal(), 0);
1093 Settings::values.use_asynchronous_gpu_emulation, false); 1117 WriteSettingGlobal(QStringLiteral("use_asynchronous_gpu_emulation"),
1094 WriteSetting(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); 1118 Settings::values.use_asynchronous_gpu_emulation, false);
1095 WriteSetting(QStringLiteral("use_assembly_shaders"), Settings::values.use_assembly_shaders, 1119 WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true);
1096 false); 1120 WriteSettingGlobal(QStringLiteral("use_assembly_shaders"),
1097 WriteSetting(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time, true); 1121 Settings::values.use_assembly_shaders, false);
1098 WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false); 1122 WriteSettingGlobal(QStringLiteral("use_fast_gpu_time"), Settings::values.use_fast_gpu_time,
1123 true);
1124 WriteSettingGlobal(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode,
1125 false);
1099 1126
1100 // Cast to double because Qt's written float values are not human-readable 1127 // Cast to double because Qt's written float values are not human-readable
1101 WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0); 1128 WriteSettingGlobal(QStringLiteral("bg_red"), Settings::values.bg_red, 0.0);
1102 WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0); 1129 WriteSettingGlobal(QStringLiteral("bg_green"), Settings::values.bg_green, 0.0);
1103 WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0); 1130 WriteSettingGlobal(QStringLiteral("bg_blue"), Settings::values.bg_blue, 0.0);
1104 1131
1105 qt_config->endGroup(); 1132 qt_config->endGroup();
1106} 1133}
@@ -1128,23 +1155,28 @@ void Config::SaveShortcutValues() {
1128void Config::SaveSystemValues() { 1155void Config::SaveSystemValues() {
1129 qt_config->beginGroup(QStringLiteral("System")); 1156 qt_config->beginGroup(QStringLiteral("System"));
1130 1157
1131 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
1132 WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0); 1158 WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0);
1133 WriteSetting(QStringLiteral("language_index"), Settings::values.language_index, 1); 1159 WriteSettingGlobal(QStringLiteral("language_index"), Settings::values.language_index, 1);
1134 WriteSetting(QStringLiteral("region_index"), Settings::values.region_index, 1); 1160 WriteSettingGlobal(QStringLiteral("region_index"), Settings::values.region_index, 1);
1135 WriteSetting(QStringLiteral("time_zone_index"), Settings::values.time_zone_index, 0); 1161 WriteSettingGlobal(QStringLiteral("time_zone_index"), Settings::values.time_zone_index, 0);
1136 1162
1137 WriteSetting(QStringLiteral("rng_seed_enabled"), Settings::values.rng_seed.has_value(), false); 1163 WriteSettingGlobal(QStringLiteral("rng_seed_enabled"),
1138 WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.value_or(0), 0); 1164 Settings::values.rng_seed.GetValue(global).has_value(),
1139 1165 Settings::values.rng_seed.UsingGlobal(), false);
1140 WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), 1166 WriteSettingGlobal(QStringLiteral("rng_seed"),
1141 false); 1167 Settings::values.rng_seed.GetValue(global).value_or(0),
1142 WriteSetting(QStringLiteral("custom_rtc"), 1168 Settings::values.rng_seed.UsingGlobal(), 0);
1143 QVariant::fromValue<long long>( 1169
1144 Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()), 1170 WriteSettingGlobal(QStringLiteral("custom_rtc_enabled"),
1145 0); 1171 Settings::values.custom_rtc.GetValue(global).has_value(),
1146 1172 Settings::values.custom_rtc.UsingGlobal(), false);
1147 WriteSetting(QStringLiteral("sound_index"), Settings::values.sound_index, 1); 1173 WriteSettingGlobal(
1174 QStringLiteral("custom_rtc"),
1175 QVariant::fromValue<long long>(
1176 Settings::values.custom_rtc.GetValue(global).value_or(std::chrono::seconds{}).count()),
1177 Settings::values.custom_rtc.UsingGlobal(), 0);
1178
1179 WriteSettingGlobal(QStringLiteral("sound_index"), Settings::values.sound_index, 1);
1148 1180
1149 qt_config->endGroup(); 1181 qt_config->endGroup();
1150} 1182}
@@ -1236,6 +1268,34 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value)
1236 return result; 1268 return result;
1237} 1269}
1238 1270
1271template <typename Type>
1272void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name) {
1273 const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
1274 setting.SetGlobal(use_global);
1275 if (global || !use_global) {
1276 setting.SetValue(ReadSetting(name).value<Type>());
1277 }
1278}
1279
1280template <typename Type>
1281void Config::ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name,
1282 const QVariant& default_value) {
1283 const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
1284 setting.SetGlobal(use_global);
1285 if (global || !use_global) {
1286 setting.SetValue(ReadSetting(name, default_value).value<Type>());
1287 }
1288}
1289
1290template <typename Type>
1291void Config::ReadSettingGlobal(Type& setting, const QString& name,
1292 const QVariant& default_value) const {
1293 const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
1294 if (global || !use_global) {
1295 setting = ReadSetting(name, default_value).value<Type>();
1296 }
1297}
1298
1239void Config::WriteSetting(const QString& name, const QVariant& value) { 1299void Config::WriteSetting(const QString& name, const QVariant& value) {
1240 qt_config->setValue(name, value); 1300 qt_config->setValue(name, value);
1241} 1301}
@@ -1246,6 +1306,40 @@ void Config::WriteSetting(const QString& name, const QVariant& value,
1246 qt_config->setValue(name, value); 1306 qt_config->setValue(name, value);
1247} 1307}
1248 1308
1309template <typename Type>
1310void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting) {
1311 if (!global) {
1312 qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
1313 }
1314 if (global || !setting.UsingGlobal()) {
1315 qt_config->setValue(name, setting.GetValue(global));
1316 }
1317}
1318
1319template <typename Type>
1320void Config::WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting,
1321 const QVariant& default_value) {
1322 if (!global) {
1323 qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
1324 }
1325 if (global || !setting.UsingGlobal()) {
1326 qt_config->setValue(name + QStringLiteral("/default"),
1327 setting.GetValue(global) == default_value.value<Type>());
1328 qt_config->setValue(name, setting.GetValue(global));
1329 }
1330}
1331
1332void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
1333 const QVariant& default_value) {
1334 if (!global) {
1335 qt_config->setValue(name + QStringLiteral("/use_global"), use_global);
1336 }
1337 if (global || !use_global) {
1338 qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
1339 qt_config->setValue(name, value);
1340 }
1341}
1342
1249void Config::Reload() { 1343void Config::Reload() {
1250 ReadValues(); 1344 ReadValues();
1251 // To apply default value changes 1345 // To apply default value changes
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 09316382c..681f0bca5 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -7,6 +7,7 @@
7#include <array> 7#include <array>
8#include <memory> 8#include <memory>
9#include <string> 9#include <string>
10#include <QMetaType>
10#include <QVariant> 11#include <QVariant>
11#include "core/settings.h" 12#include "core/settings.h"
12#include "yuzu/uisettings.h" 13#include "yuzu/uisettings.h"
@@ -15,7 +16,7 @@ class QSettings;
15 16
16class Config { 17class Config {
17public: 18public:
18 Config(); 19 explicit Config(const std::string& config_loc = "qt-config.ini", bool is_global = true);
19 ~Config(); 20 ~Config();
20 21
21 void Reload(); 22 void Reload();
@@ -82,9 +83,33 @@ private:
82 83
83 QVariant ReadSetting(const QString& name) const; 84 QVariant ReadSetting(const QString& name) const;
84 QVariant ReadSetting(const QString& name, const QVariant& default_value) const; 85 QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
86 // Templated ReadSettingGlobal functions will also look for the use_global setting and set
87 // both the value and the global state properly
88 template <typename Type>
89 void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name);
90 template <typename Type>
91 void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name,
92 const QVariant& default_value);
93 template <typename Type>
94 void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const;
95 // Templated WriteSettingGlobal functions will also write the global state if needed and will
96 // skip writing the actual setting if it defers to the global value
85 void WriteSetting(const QString& name, const QVariant& value); 97 void WriteSetting(const QString& name, const QVariant& value);
86 void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); 98 void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
99 template <typename Type>
100 void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting);
101 template <typename Type>
102 void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting,
103 const QVariant& default_value);
104 void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
105 const QVariant& default_value);
87 106
88 std::unique_ptr<QSettings> qt_config; 107 std::unique_ptr<QSettings> qt_config;
89 std::string qt_config_loc; 108 std::string qt_config_loc;
109
110 bool global;
90}; 111};
112
113// These metatype declarations cannot be in core/settings.h because core is devoid of QT
114Q_DECLARE_METATYPE(Settings::RendererBackend);
115Q_DECLARE_METATYPE(Settings::GPUAccuracy);
diff --git a/src/yuzu/configuration/configuration_shared.cpp b/src/yuzu/configuration/configuration_shared.cpp
new file mode 100644
index 000000000..bb47c3933
--- /dev/null
+++ b/src/yuzu/configuration/configuration_shared.cpp
@@ -0,0 +1,76 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QCheckBox>
6#include <QComboBox>
7#include "core/settings.h"
8#include "yuzu/configuration/configuration_shared.h"
9#include "yuzu/configuration/configure_per_game.h"
10
11void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting,
12 const QCheckBox* checkbox) {
13 if (checkbox->checkState() == Qt::PartiallyChecked) {
14 setting->SetGlobal(true);
15 } else {
16 setting->SetGlobal(false);
17 setting->SetValue(checkbox->checkState() == Qt::Checked);
18 }
19}
20
21void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting,
22 const QComboBox* combobox) {
23 if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
24 setting->SetGlobal(true);
25 } else {
26 setting->SetGlobal(false);
27 setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET);
28 }
29}
30
31void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting,
32 const QComboBox* combobox) {
33 if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
34 setting->SetGlobal(true);
35 } else {
36 setting->SetGlobal(false);
37 setting->SetValue(static_cast<Settings::RendererBackend>(
38 combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET));
39 }
40}
41
42void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox,
43 const Settings::Setting<bool>* setting) {
44 if (setting->UsingGlobal()) {
45 checkbox->setCheckState(Qt::PartiallyChecked);
46 } else {
47 checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked);
48 }
49}
50
51void ConfigurationShared::SetPerGameSetting(QComboBox* combobox,
52 const Settings::Setting<int>* setting) {
53 combobox->setCurrentIndex(setting->UsingGlobal()
54 ? ConfigurationShared::USE_GLOBAL_INDEX
55 : setting->GetValue() + ConfigurationShared::USE_GLOBAL_OFFSET);
56}
57
58void ConfigurationShared::SetPerGameSetting(
59 QComboBox* combobox, const Settings::Setting<Settings::RendererBackend>* setting) {
60 combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
61 : static_cast<int>(setting->GetValue()) +
62 ConfigurationShared::USE_GLOBAL_OFFSET);
63}
64
65void ConfigurationShared::SetPerGameSetting(
66 QComboBox* combobox, const Settings::Setting<Settings::GPUAccuracy>* setting) {
67 combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
68 : static_cast<int>(setting->GetValue()) +
69 ConfigurationShared::USE_GLOBAL_OFFSET);
70}
71
72void ConfigurationShared::InsertGlobalItem(QComboBox* combobox) {
73 const QString use_global_text = ConfigurePerGame::tr("Use global configuration");
74 combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text);
75 combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX);
76}
diff --git a/src/yuzu/configuration/configuration_shared.h b/src/yuzu/configuration/configuration_shared.h
new file mode 100644
index 000000000..b11b1b950
--- /dev/null
+++ b/src/yuzu/configuration/configuration_shared.h
@@ -0,0 +1,36 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <QCheckBox>
8#include <QComboBox>
9#include <QString>
10#include "core/settings.h"
11
12namespace ConfigurationShared {
13
14constexpr int USE_GLOBAL_INDEX = 0;
15constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1;
16constexpr int USE_GLOBAL_OFFSET = 2;
17
18// Global-aware apply and set functions
19
20void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox);
21void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox);
22void ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting,
23 const QComboBox* combobox);
24void ApplyPerGameSetting(Settings::Setting<Settings::GPUAccuracy>* setting,
25 const QComboBox* combobox);
26
27void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting);
28void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<int>* setting);
29void SetPerGameSetting(QComboBox* combobox,
30 const Settings::Setting<Settings::RendererBackend>* setting);
31void SetPerGameSetting(QComboBox* combobox,
32 const Settings::Setting<Settings::GPUAccuracy>* setting);
33
34void InsertGlobalItem(QComboBox* combobox);
35
36} // namespace ConfigurationShared
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index f370c690f..cc021beec 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -11,6 +11,7 @@
11#include "core/core.h" 11#include "core/core.h"
12#include "core/settings.h" 12#include "core/settings.h"
13#include "ui_configure_audio.h" 13#include "ui_configure_audio.h"
14#include "yuzu/configuration/configuration_shared.h"
14#include "yuzu/configuration/configure_audio.h" 15#include "yuzu/configuration/configure_audio.h"
15 16
16ConfigureAudio::ConfigureAudio(QWidget* parent) 17ConfigureAudio::ConfigureAudio(QWidget* parent)
@@ -24,6 +25,11 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
24 connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, 25 connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
25 &ConfigureAudio::UpdateAudioDevices); 26 &ConfigureAudio::UpdateAudioDevices);
26 27
28 ui->volume_label->setVisible(Settings::configuring_global);
29 ui->volume_combo_box->setVisible(!Settings::configuring_global);
30
31 SetupPerGameUI();
32
27 SetConfiguration(); 33 SetConfiguration();
28 34
29 const bool is_powered_on = Core::System::GetInstance().IsPoweredOn(); 35 const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
@@ -41,8 +47,22 @@ void ConfigureAudio::SetConfiguration() {
41 47
42 SetAudioDeviceFromDeviceID(); 48 SetAudioDeviceFromDeviceID();
43 49
44 ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); 50 ui->volume_slider->setValue(Settings::values.volume.GetValue() * ui->volume_slider->maximum());
45 ui->volume_slider->setValue(Settings::values.volume * ui->volume_slider->maximum()); 51
52 if (Settings::configuring_global) {
53 ui->toggle_audio_stretching->setChecked(
54 Settings::values.enable_audio_stretching.GetValue());
55 } else {
56 ConfigurationShared::SetPerGameSetting(ui->toggle_audio_stretching,
57 &Settings::values.enable_audio_stretching);
58 if (Settings::values.volume.UsingGlobal()) {
59 ui->volume_combo_box->setCurrentIndex(0);
60 ui->volume_slider->setEnabled(false);
61 } else {
62 ui->volume_combo_box->setCurrentIndex(1);
63 ui->volume_slider->setEnabled(true);
64 }
65 }
46 SetVolumeIndicatorText(ui->volume_slider->sliderPosition()); 66 SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
47} 67}
48 68
@@ -80,15 +100,36 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
80} 100}
81 101
82void ConfigureAudio::ApplyConfiguration() { 102void ConfigureAudio::ApplyConfiguration() {
83 Settings::values.sink_id = 103 if (Settings::configuring_global) {
84 ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) 104 Settings::values.sink_id =
85 .toStdString(); 105 ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
86 Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked(); 106 .toStdString();
87 Settings::values.audio_device_id = 107 Settings::values.audio_device_id =
88 ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) 108 ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
89 .toStdString(); 109 .toStdString();
90 Settings::values.volume = 110
91 static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum(); 111 // Guard if during game and set to game-specific value
112 if (Settings::values.enable_audio_stretching.UsingGlobal()) {
113 Settings::values.enable_audio_stretching.SetValue(
114 ui->toggle_audio_stretching->isChecked());
115 }
116 if (Settings::values.volume.UsingGlobal()) {
117 Settings::values.volume.SetValue(
118 static_cast<float>(ui->volume_slider->sliderPosition()) /
119 ui->volume_slider->maximum());
120 }
121 } else {
122 ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching,
123 ui->toggle_audio_stretching);
124 if (ui->volume_combo_box->currentIndex() == 0) {
125 Settings::values.volume.SetGlobal(true);
126 } else {
127 Settings::values.volume.SetGlobal(false);
128 Settings::values.volume.SetValue(
129 static_cast<float>(ui->volume_slider->sliderPosition()) /
130 ui->volume_slider->maximum());
131 }
132 }
92} 133}
93 134
94void ConfigureAudio::changeEvent(QEvent* event) { 135void ConfigureAudio::changeEvent(QEvent* event) {
@@ -122,3 +163,22 @@ void ConfigureAudio::RetranslateUI() {
122 ui->retranslateUi(this); 163 ui->retranslateUi(this);
123 SetVolumeIndicatorText(ui->volume_slider->sliderPosition()); 164 SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
124} 165}
166
167void ConfigureAudio::SetupPerGameUI() {
168 if (Settings::configuring_global) {
169 ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
170 ui->toggle_audio_stretching->setEnabled(
171 Settings::values.enable_audio_stretching.UsingGlobal());
172
173 return;
174 }
175
176 ui->toggle_audio_stretching->setTristate(true);
177 connect(ui->volume_combo_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
178 this, [this](int index) { ui->volume_slider->setEnabled(index == 1); });
179
180 ui->output_sink_combo_box->setVisible(false);
181 ui->output_sink_label->setVisible(false);
182 ui->audio_device_combo_box->setVisible(false);
183 ui->audio_device_label->setVisible(false);
184}
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
index ea83bd72d..d84f4a682 100644
--- a/src/yuzu/configuration/configure_audio.h
+++ b/src/yuzu/configuration/configure_audio.h
@@ -34,5 +34,7 @@ private:
34 void SetAudioDeviceFromDeviceID(); 34 void SetAudioDeviceFromDeviceID();
35 void SetVolumeIndicatorText(int percentage); 35 void SetVolumeIndicatorText(int percentage);
36 36
37 void SetupPerGameUI();
38
37 std::unique_ptr<Ui::ConfigureAudio> ui; 39 std::unique_ptr<Ui::ConfigureAudio> ui;
38}; 40};
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index a098b9acc..862ccb988 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -6,8 +6,8 @@
6 <rect> 6 <rect>
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>188</width> 9 <width>367</width>
10 <height>246</height> 10 <height>368</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <layout class="QVBoxLayout"> 13 <layout class="QVBoxLayout">
@@ -18,9 +18,9 @@
18 </property> 18 </property>
19 <layout class="QVBoxLayout"> 19 <layout class="QVBoxLayout">
20 <item> 20 <item>
21 <layout class="QHBoxLayout"> 21 <layout class="QHBoxLayout" name="_3">
22 <item> 22 <item>
23 <widget class="QLabel" name="label_1"> 23 <widget class="QLabel" name="output_sink_label">
24 <property name="text"> 24 <property name="text">
25 <string>Output Engine:</string> 25 <string>Output Engine:</string>
26 </property> 26 </property>
@@ -31,20 +31,20 @@
31 </item> 31 </item>
32 </layout> 32 </layout>
33 </item> 33 </item>
34 <item>
35 <widget class="QCheckBox" name="toggle_audio_stretching">
36 <property name="toolTip">
37 <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
38 </property>
39 <property name="text">
40 <string>Enable audio stretching</string>
41 </property>
42 </widget>
43 </item>
44 <item> 34 <item>
45 <layout class="QHBoxLayout"> 35 <widget class="QCheckBox" name="toggle_audio_stretching">
36 <property name="toolTip">
37 <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
38 </property>
39 <property name="text">
40 <string>Enable audio stretching</string>
41 </property>
42 </widget>
43 </item>
44 <item>
45 <layout class="QHBoxLayout" name="_2">
46 <item> 46 <item>
47 <widget class="QLabel" name="label_2"> 47 <widget class="QLabel" name="audio_device_label">
48 <property name="text"> 48 <property name="text">
49 <string>Audio Device:</string> 49 <string>Audio Device:</string>
50 </property> 50 </property>
@@ -61,7 +61,21 @@
61 <number>0</number> 61 <number>0</number>
62 </property> 62 </property>
63 <item> 63 <item>
64 <widget class="QLabel" name="label_3"> 64 <widget class="QComboBox" name="volume_combo_box">
65 <item>
66 <property name="text">
67 <string>Use global volume</string>
68 </property>
69 </item>
70 <item>
71 <property name="text">
72 <string>Set volume:</string>
73 </property>
74 </item>
75 </widget>
76 </item>
77 <item>
78 <widget class="QLabel" name="volume_label">
65 <property name="text"> 79 <property name="text">
66 <string>Volume:</string> 80 <string>Volume:</string>
67 </property> 81 </property>
@@ -74,7 +88,7 @@
74 </property> 88 </property>
75 <property name="sizeHint" stdset="0"> 89 <property name="sizeHint" stdset="0">
76 <size> 90 <size>
77 <width>40</width> 91 <width>30</width>
78 <height>20</height> 92 <height>20</height>
79 </size> 93 </size>
80 </property> 94 </property>
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index df4473b46..5918e9972 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -14,6 +14,8 @@
14 14
15ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry) 15ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
16 : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) { 16 : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {
17 Settings::configuring_global = true;
18
17 ui->setupUi(this); 19 ui->setupUi(this);
18 ui->hotkeysTab->Populate(registry); 20 ui->hotkeysTab->Populate(registry);
19 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 21 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 74b2ad537..1fb62d1cf 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -7,17 +7,21 @@
7#include "core/core.h" 7#include "core/core.h"
8#include "core/settings.h" 8#include "core/settings.h"
9#include "ui_configure_general.h" 9#include "ui_configure_general.h"
10#include "yuzu/configuration/configuration_shared.h"
10#include "yuzu/configuration/configure_general.h" 11#include "yuzu/configuration/configure_general.h"
11#include "yuzu/uisettings.h" 12#include "yuzu/uisettings.h"
12 13
13ConfigureGeneral::ConfigureGeneral(QWidget* parent) 14ConfigureGeneral::ConfigureGeneral(QWidget* parent)
14 : QWidget(parent), ui(new Ui::ConfigureGeneral) { 15 : QWidget(parent), ui(new Ui::ConfigureGeneral) {
15
16 ui->setupUi(this); 16 ui->setupUi(this);
17 17
18 SetupPerGameUI();
19
18 SetConfiguration(); 20 SetConfiguration();
19 21
20 connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled); 22 connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit, [this]() {
23 ui->frame_limit->setEnabled(ui->toggle_frame_limit->checkState() == Qt::Checked);
24 });
21} 25}
22 26
23ConfigureGeneral::~ConfigureGeneral() = default; 27ConfigureGeneral::~ConfigureGeneral() = default;
@@ -26,27 +30,56 @@ void ConfigureGeneral::SetConfiguration() {
26 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); 30 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
27 31
28 ui->use_multi_core->setEnabled(runtime_lock); 32 ui->use_multi_core->setEnabled(runtime_lock);
29 ui->use_multi_core->setChecked(Settings::values.use_multi_core); 33 ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue());
30 34
31 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); 35 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
32 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); 36 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
33 ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background); 37 ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
34 ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse); 38 ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse);
35 39
36 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); 40 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue());
37 ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); 41 ui->frame_limit->setValue(Settings::values.frame_limit.GetValue());
38 ui->frame_limit->setValue(Settings::values.frame_limit); 42
43 if (!Settings::configuring_global) {
44 if (Settings::values.use_multi_core.UsingGlobal()) {
45 ui->use_multi_core->setCheckState(Qt::PartiallyChecked);
46 }
47 if (Settings::values.use_frame_limit.UsingGlobal()) {
48 ui->toggle_frame_limit->setCheckState(Qt::PartiallyChecked);
49 }
50 }
51
52 ui->frame_limit->setEnabled(ui->toggle_frame_limit->checkState() == Qt::Checked &&
53 ui->toggle_frame_limit->isEnabled());
39} 54}
40 55
41void ConfigureGeneral::ApplyConfiguration() { 56void ConfigureGeneral::ApplyConfiguration() {
42 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); 57 if (Settings::configuring_global) {
43 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); 58 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
44 UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked(); 59 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
45 UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked(); 60 UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
46 61 UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
47 Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); 62
48 Settings::values.frame_limit = ui->frame_limit->value(); 63 // Guard if during game and set to game-specific value
49 Settings::values.use_multi_core = ui->use_multi_core->isChecked(); 64 if (Settings::values.use_frame_limit.UsingGlobal()) {
65 Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() ==
66 Qt::Checked);
67 Settings::values.frame_limit.SetValue(ui->frame_limit->value());
68 Settings::values.use_multi_core.SetValue(ui->use_multi_core->isChecked());
69 }
70 } else {
71 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core,
72 ui->use_multi_core);
73
74 bool global_frame_limit = ui->toggle_frame_limit->checkState() == Qt::PartiallyChecked;
75 Settings::values.use_frame_limit.SetGlobal(global_frame_limit);
76 Settings::values.frame_limit.SetGlobal(global_frame_limit);
77 if (!global_frame_limit) {
78 Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() ==
79 Qt::Checked);
80 Settings::values.frame_limit.SetValue(ui->frame_limit->value());
81 }
82 }
50} 83}
51 84
52void ConfigureGeneral::changeEvent(QEvent* event) { 85void ConfigureGeneral::changeEvent(QEvent* event) {
@@ -60,3 +93,20 @@ void ConfigureGeneral::changeEvent(QEvent* event) {
60void ConfigureGeneral::RetranslateUI() { 93void ConfigureGeneral::RetranslateUI() {
61 ui->retranslateUi(this); 94 ui->retranslateUi(this);
62} 95}
96
97void ConfigureGeneral::SetupPerGameUI() {
98 if (Settings::configuring_global) {
99 ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal());
100 ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal());
101
102 return;
103 }
104
105 ui->toggle_check_exit->setVisible(false);
106 ui->toggle_user_on_boot->setVisible(false);
107 ui->toggle_background_pause->setVisible(false);
108 ui->toggle_hide_mouse->setVisible(false);
109
110 ui->toggle_frame_limit->setTristate(true);
111 ui->use_multi_core->setTristate(true);
112}
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index ef05ce065..9c785c22e 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -28,5 +28,7 @@ private:
28 28
29 void SetConfiguration(); 29 void SetConfiguration();
30 30
31 void SetupPerGameUI();
32
31 std::unique_ptr<Ui::ConfigureGeneral> ui; 33 std::unique_ptr<Ui::ConfigureGeneral> ui;
32}; 34};
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 431f51d73..cb4706bd6 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -13,6 +13,7 @@
13#include "core/core.h" 13#include "core/core.h"
14#include "core/settings.h" 14#include "core/settings.h"
15#include "ui_configure_graphics.h" 15#include "ui_configure_graphics.h"
16#include "yuzu/configuration/configuration_shared.h"
16#include "yuzu/configuration/configure_graphics.h" 17#include "yuzu/configuration/configure_graphics.h"
17 18
18#ifdef HAS_VULKAN 19#ifdef HAS_VULKAN
@@ -21,11 +22,13 @@
21 22
22ConfigureGraphics::ConfigureGraphics(QWidget* parent) 23ConfigureGraphics::ConfigureGraphics(QWidget* parent)
23 : QWidget(parent), ui(new Ui::ConfigureGraphics) { 24 : QWidget(parent), ui(new Ui::ConfigureGraphics) {
24 vulkan_device = Settings::values.vulkan_device; 25 vulkan_device = Settings::values.vulkan_device.GetValue();
25 RetrieveVulkanDevices(); 26 RetrieveVulkanDevices();
26 27
27 ui->setupUi(this); 28 ui->setupUi(this);
28 29
30 SetupPerGameUI();
31
29 SetConfiguration(); 32 SetConfiguration();
30 33
31 connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, 34 connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this,
@@ -40,6 +43,9 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
40 } 43 }
41 UpdateBackgroundColorButton(new_bg_color); 44 UpdateBackgroundColorButton(new_bg_color);
42 }); 45 });
46
47 ui->bg_label->setVisible(Settings::configuring_global);
48 ui->bg_combobox->setVisible(!Settings::configuring_global);
43} 49}
44 50
45void ConfigureGraphics::UpdateDeviceSelection(int device) { 51void ConfigureGraphics::UpdateDeviceSelection(int device) {
@@ -57,27 +63,95 @@ void ConfigureGraphics::SetConfiguration() {
57 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); 63 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
58 64
59 ui->api->setEnabled(runtime_lock); 65 ui->api->setEnabled(runtime_lock);
60 ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend));
61 ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio);
62 ui->use_disk_shader_cache->setEnabled(runtime_lock);
63 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
64 ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); 66 ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
65 ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); 67 ui->use_disk_shader_cache->setEnabled(runtime_lock);
66 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, 68
67 Settings::values.bg_blue)); 69 if (Settings::configuring_global) {
70 ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue()));
71 ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
72 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
73 ui->use_asynchronous_gpu_emulation->setChecked(
74 Settings::values.use_asynchronous_gpu_emulation.GetValue());
75 } else {
76 ConfigurationShared::SetPerGameSetting(ui->use_disk_shader_cache,
77 &Settings::values.use_disk_shader_cache);
78 ConfigurationShared::SetPerGameSetting(ui->use_asynchronous_gpu_emulation,
79 &Settings::values.use_asynchronous_gpu_emulation);
80
81 ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend);
82 ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox,
83 &Settings::values.aspect_ratio);
84
85 ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1);
86 ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal());
87 }
88
89 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(),
90 Settings::values.bg_green.GetValue(),
91 Settings::values.bg_blue.GetValue()));
68 UpdateDeviceComboBox(); 92 UpdateDeviceComboBox();
69} 93}
70 94
71void ConfigureGraphics::ApplyConfiguration() { 95void ConfigureGraphics::ApplyConfiguration() {
72 Settings::values.renderer_backend = GetCurrentGraphicsBackend(); 96 if (Settings::configuring_global) {
73 Settings::values.vulkan_device = vulkan_device; 97 // Guard if during game and set to game-specific value
74 Settings::values.aspect_ratio = ui->aspect_ratio_combobox->currentIndex(); 98 if (Settings::values.renderer_backend.UsingGlobal()) {
75 Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); 99 Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
76 Settings::values.use_asynchronous_gpu_emulation = 100 }
77 ui->use_asynchronous_gpu_emulation->isChecked(); 101 if (Settings::values.vulkan_device.UsingGlobal()) {
78 Settings::values.bg_red = static_cast<float>(bg_color.redF()); 102 Settings::values.vulkan_device.SetValue(vulkan_device);
79 Settings::values.bg_green = static_cast<float>(bg_color.greenF()); 103 }
80 Settings::values.bg_blue = static_cast<float>(bg_color.blueF()); 104 if (Settings::values.aspect_ratio.UsingGlobal()) {
105 Settings::values.aspect_ratio.SetValue(ui->aspect_ratio_combobox->currentIndex());
106 }
107 if (Settings::values.use_disk_shader_cache.UsingGlobal()) {
108 Settings::values.use_disk_shader_cache.SetValue(ui->use_disk_shader_cache->isChecked());
109 }
110 if (Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()) {
111 Settings::values.use_asynchronous_gpu_emulation.SetValue(
112 ui->use_asynchronous_gpu_emulation->isChecked());
113 }
114 if (Settings::values.bg_red.UsingGlobal()) {
115 Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF()));
116 Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF()));
117 Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF()));
118 }
119 } else {
120 if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
121 Settings::values.renderer_backend.SetGlobal(true);
122 Settings::values.vulkan_device.SetGlobal(true);
123 } else {
124 Settings::values.renderer_backend.SetGlobal(false);
125 Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
126 if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) {
127 Settings::values.vulkan_device.SetGlobal(false);
128 Settings::values.vulkan_device.SetValue(vulkan_device);
129 } else {
130 Settings::values.vulkan_device.SetGlobal(true);
131 }
132 }
133
134 ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio,
135 ui->aspect_ratio_combobox);
136
137 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache,
138 ui->use_disk_shader_cache);
139 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation,
140 ui->use_asynchronous_gpu_emulation);
141
142 if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
143 Settings::values.bg_red.SetGlobal(true);
144 Settings::values.bg_green.SetGlobal(true);
145 Settings::values.bg_blue.SetGlobal(true);
146 } else {
147 Settings::values.bg_red.SetGlobal(false);
148 Settings::values.bg_green.SetGlobal(false);
149 Settings::values.bg_blue.SetGlobal(false);
150 Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF()));
151 Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF()));
152 Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF()));
153 }
154 }
81} 155}
82 156
83void ConfigureGraphics::changeEvent(QEvent* event) { 157void ConfigureGraphics::changeEvent(QEvent* event) {
@@ -106,6 +180,11 @@ void ConfigureGraphics::UpdateDeviceComboBox() {
106 ui->device->clear(); 180 ui->device->clear();
107 181
108 bool enabled = false; 182 bool enabled = false;
183
184 if (!Settings::configuring_global &&
185 ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
186 vulkan_device = Settings::values.vulkan_device.GetValue();
187 }
109 switch (GetCurrentGraphicsBackend()) { 188 switch (GetCurrentGraphicsBackend()) {
110 case Settings::RendererBackend::OpenGL: 189 case Settings::RendererBackend::OpenGL:
111 ui->device->addItem(tr("OpenGL Graphics Device")); 190 ui->device->addItem(tr("OpenGL Graphics Device"));
@@ -119,6 +198,9 @@ void ConfigureGraphics::UpdateDeviceComboBox() {
119 enabled = !vulkan_devices.empty(); 198 enabled = !vulkan_devices.empty();
120 break; 199 break;
121 } 200 }
201 // If in per-game config and use global is selected, don't enable.
202 enabled &= !(!Settings::configuring_global &&
203 ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX);
122 ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn()); 204 ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn());
123} 205}
124 206
@@ -132,5 +214,37 @@ void ConfigureGraphics::RetrieveVulkanDevices() {
132} 214}
133 215
134Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const { 216Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
135 return static_cast<Settings::RendererBackend>(ui->api->currentIndex()); 217 if (Settings::configuring_global) {
218 return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
219 }
220
221 if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
222 Settings::values.renderer_backend.SetGlobal(true);
223 return Settings::values.renderer_backend.GetValue();
224 }
225 Settings::values.renderer_backend.SetGlobal(false);
226 return static_cast<Settings::RendererBackend>(ui->api->currentIndex() -
227 ConfigurationShared::USE_GLOBAL_OFFSET);
228}
229
230void ConfigureGraphics::SetupPerGameUI() {
231 if (Settings::configuring_global) {
232 ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal());
233 ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal());
234 ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal());
235 ui->use_asynchronous_gpu_emulation->setEnabled(
236 Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
237 ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());
238 ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal());
239
240 return;
241 }
242
243 connect(ui->bg_combobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
244 [this](int index) { ui->bg_button->setEnabled(index == 1); });
245
246 ui->use_disk_shader_cache->setTristate(true);
247 ui->use_asynchronous_gpu_emulation->setTristate(true);
248 ConfigurationShared::InsertGlobalItem(ui->aspect_ratio_combobox);
249 ConfigurationShared::InsertGlobalItem(ui->api);
136} 250}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index 7e0596d9c..24f01c739 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -35,6 +35,8 @@ private:
35 35
36 void RetrieveVulkanDevices(); 36 void RetrieveVulkanDevices();
37 37
38 void SetupPerGameUI();
39
38 Settings::RendererBackend GetCurrentGraphicsBackend() const; 40 Settings::RendererBackend GetCurrentGraphicsBackend() const;
39 41
40 std::unique_ptr<Ui::ConfigureGraphics> ui; 42 std::unique_ptr<Ui::ConfigureGraphics> ui;
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 6e75447a5..62418fc14 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -122,6 +122,29 @@
122 <item> 122 <item>
123 <layout class="QHBoxLayout" name="horizontalLayout_3"> 123 <layout class="QHBoxLayout" name="horizontalLayout_3">
124 <item> 124 <item>
125 <widget class="QComboBox" name="bg_combobox">
126 <property name="currentText">
127 <string>Use global background color</string>
128 </property>
129 <property name="currentIndex">
130 <number>0</number>
131 </property>
132 <property name="maxVisibleItems">
133 <number>10</number>
134 </property>
135 <item>
136 <property name="text">
137 <string>Use global background color</string>
138 </property>
139 </item>
140 <item>
141 <property name="text">
142 <string>Set background color:</string>
143 </property>
144 </item>
145 </widget>
146 </item>
147 <item>
125 <widget class="QLabel" name="bg_label"> 148 <widget class="QLabel" name="bg_label">
126 <property name="text"> 149 <property name="text">
127 <string>Background Color:</string> 150 <string>Background Color:</string>
@@ -129,6 +152,19 @@
129 </widget> 152 </widget>
130 </item> 153 </item>
131 <item> 154 <item>
155 <spacer name="horizontalSpacer">
156 <property name="orientation">
157 <enum>Qt::Horizontal</enum>
158 </property>
159 <property name="sizeHint" stdset="0">
160 <size>
161 <width>40</width>
162 <height>20</height>
163 </size>
164 </property>
165 </spacer>
166 </item>
167 <item>
132 <widget class="QPushButton" name="bg_button"> 168 <widget class="QPushButton" name="bg_button">
133 <property name="maximumSize"> 169 <property name="maximumSize">
134 <size> 170 <size>
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index be5006ad3..7c0fa7ec5 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -5,6 +5,7 @@
5#include "core/core.h" 5#include "core/core.h"
6#include "core/settings.h" 6#include "core/settings.h"
7#include "ui_configure_graphics_advanced.h" 7#include "ui_configure_graphics_advanced.h"
8#include "yuzu/configuration/configuration_shared.h"
8#include "yuzu/configuration/configure_graphics_advanced.h" 9#include "yuzu/configuration/configure_graphics_advanced.h"
9 10
10ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent) 11ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent)
@@ -12,6 +13,8 @@ ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent)
12 13
13 ui->setupUi(this); 14 ui->setupUi(this);
14 15
16 SetupPerGameUI();
17
15 SetConfiguration(); 18 SetConfiguration();
16} 19}
17 20
@@ -19,26 +22,81 @@ ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default;
19 22
20void ConfigureGraphicsAdvanced::SetConfiguration() { 23void ConfigureGraphicsAdvanced::SetConfiguration() {
21 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); 24 const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
22 ui->gpu_accuracy->setCurrentIndex(static_cast<int>(Settings::values.gpu_accuracy));
23 ui->use_vsync->setEnabled(runtime_lock); 25 ui->use_vsync->setEnabled(runtime_lock);
24 ui->use_vsync->setChecked(Settings::values.use_vsync);
25 ui->use_assembly_shaders->setEnabled(runtime_lock); 26 ui->use_assembly_shaders->setEnabled(runtime_lock);
26 ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders);
27 ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time);
28 ui->force_30fps_mode->setEnabled(runtime_lock); 27 ui->force_30fps_mode->setEnabled(runtime_lock);
29 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
30 ui->anisotropic_filtering_combobox->setEnabled(runtime_lock); 28 ui->anisotropic_filtering_combobox->setEnabled(runtime_lock);
31 ui->anisotropic_filtering_combobox->setCurrentIndex(Settings::values.max_anisotropy); 29
30 if (Settings::configuring_global) {
31 ui->gpu_accuracy->setCurrentIndex(
32 static_cast<int>(Settings::values.gpu_accuracy.GetValue()));
33 ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue());
34 ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue());
35 ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
36 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode.GetValue());
37 ui->anisotropic_filtering_combobox->setCurrentIndex(
38 Settings::values.max_anisotropy.GetValue());
39 } else {
40 ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy);
41 ConfigurationShared::SetPerGameSetting(ui->use_vsync, &Settings::values.use_vsync);
42 ConfigurationShared::SetPerGameSetting(ui->use_assembly_shaders,
43 &Settings::values.use_assembly_shaders);
44 ConfigurationShared::SetPerGameSetting(ui->use_fast_gpu_time,
45 &Settings::values.use_fast_gpu_time);
46 ConfigurationShared::SetPerGameSetting(ui->force_30fps_mode,
47 &Settings::values.force_30fps_mode);
48 ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox,
49 &Settings::values.max_anisotropy);
50 }
32} 51}
33 52
34void ConfigureGraphicsAdvanced::ApplyConfiguration() { 53void ConfigureGraphicsAdvanced::ApplyConfiguration() {
35 auto gpu_accuracy = static_cast<Settings::GPUAccuracy>(ui->gpu_accuracy->currentIndex()); 54 // Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots)
36 Settings::values.gpu_accuracy = gpu_accuracy; 55 const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>(
37 Settings::values.use_vsync = ui->use_vsync->isChecked(); 56 ui->gpu_accuracy->currentIndex() -
38 Settings::values.use_assembly_shaders = ui->use_assembly_shaders->isChecked(); 57 ((Settings::configuring_global) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
39 Settings::values.use_fast_gpu_time = ui->use_fast_gpu_time->isChecked(); 58
40 Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked(); 59 if (Settings::configuring_global) {
41 Settings::values.max_anisotropy = ui->anisotropic_filtering_combobox->currentIndex(); 60 // Must guard in case of a during-game configuration when set to be game-specific.
61 if (Settings::values.gpu_accuracy.UsingGlobal()) {
62 Settings::values.gpu_accuracy.SetValue(gpu_accuracy);
63 }
64 if (Settings::values.use_vsync.UsingGlobal()) {
65 Settings::values.use_vsync.SetValue(ui->use_vsync->isChecked());
66 }
67 if (Settings::values.use_assembly_shaders.UsingGlobal()) {
68 Settings::values.use_assembly_shaders.SetValue(ui->use_assembly_shaders->isChecked());
69 }
70 if (Settings::values.use_fast_gpu_time.UsingGlobal()) {
71 Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked());
72 }
73 if (Settings::values.force_30fps_mode.UsingGlobal()) {
74 Settings::values.force_30fps_mode.SetValue(ui->force_30fps_mode->isChecked());
75 }
76 if (Settings::values.max_anisotropy.UsingGlobal()) {
77 Settings::values.max_anisotropy.SetValue(
78 ui->anisotropic_filtering_combobox->currentIndex());
79 }
80 } else {
81 ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
82 ui->anisotropic_filtering_combobox);
83 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync);
84 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders,
85 ui->use_assembly_shaders);
86 ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,
87 ui->use_fast_gpu_time);
88 ConfigurationShared::ApplyPerGameSetting(&Settings::values.force_30fps_mode,
89 ui->force_30fps_mode);
90 ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
91 ui->anisotropic_filtering_combobox);
92
93 if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
94 Settings::values.gpu_accuracy.SetGlobal(true);
95 } else {
96 Settings::values.gpu_accuracy.SetGlobal(false);
97 Settings::values.gpu_accuracy.SetValue(gpu_accuracy);
98 }
99 }
42} 100}
43 101
44void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { 102void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -52,3 +110,25 @@ void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
52void ConfigureGraphicsAdvanced::RetranslateUI() { 110void ConfigureGraphicsAdvanced::RetranslateUI() {
53 ui->retranslateUi(this); 111 ui->retranslateUi(this);
54} 112}
113
114void ConfigureGraphicsAdvanced::SetupPerGameUI() {
115 // Disable if not global (only happens during game)
116 if (Settings::configuring_global) {
117 ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal());
118 ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal());
119 ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal());
120 ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal());
121 ui->force_30fps_mode->setEnabled(Settings::values.force_30fps_mode.UsingGlobal());
122 ui->anisotropic_filtering_combobox->setEnabled(
123 Settings::values.max_anisotropy.UsingGlobal());
124
125 return;
126 }
127
128 ConfigurationShared::InsertGlobalItem(ui->gpu_accuracy);
129 ui->use_vsync->setTristate(true);
130 ui->use_assembly_shaders->setTristate(true);
131 ui->use_fast_gpu_time->setTristate(true);
132 ui->force_30fps_mode->setTristate(true);
133 ConfigurationShared::InsertGlobalItem(ui->anisotropic_filtering_combobox);
134}
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index bbc9d4355..c043588ff 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -26,5 +26,7 @@ private:
26 26
27 void SetConfiguration(); 27 void SetConfiguration();
28 28
29 void SetupPerGameUI();
30
29 std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui; 31 std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui;
30}; 32};
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
new file mode 100644
index 000000000..1e49f0787
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -0,0 +1,140 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <memory>
7#include <utility>
8
9#include <QCheckBox>
10#include <QHeaderView>
11#include <QMenu>
12#include <QStandardItemModel>
13#include <QString>
14#include <QTimer>
15#include <QTreeView>
16
17#include "common/common_paths.h"
18#include "common/file_util.h"
19#include "core/file_sys/control_metadata.h"
20#include "core/file_sys/patch_manager.h"
21#include "core/file_sys/xts_archive.h"
22#include "core/loader/loader.h"
23#include "ui_configure_per_game.h"
24#include "yuzu/configuration/config.h"
25#include "yuzu/configuration/configure_input.h"
26#include "yuzu/configuration/configure_per_game.h"
27#include "yuzu/uisettings.h"
28#include "yuzu/util/util.h"
29
30ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id)
31 : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) {
32 game_config = std::make_unique<Config>(fmt::format("{:016X}.ini", title_id), false);
33
34 Settings::configuring_global = false;
35
36 ui->setupUi(this);
37 setFocusPolicy(Qt::ClickFocus);
38 setWindowTitle(tr("Properties"));
39
40 ui->addonsTab->SetTitleId(title_id);
41
42 scene = new QGraphicsScene;
43 ui->icon_view->setScene(scene);
44
45 LoadConfiguration();
46}
47
48ConfigurePerGame::~ConfigurePerGame() = default;
49
50void ConfigurePerGame::ApplyConfiguration() {
51 ui->addonsTab->ApplyConfiguration();
52 ui->generalTab->ApplyConfiguration();
53 ui->systemTab->ApplyConfiguration();
54 ui->graphicsTab->ApplyConfiguration();
55 ui->graphicsAdvancedTab->ApplyConfiguration();
56 ui->audioTab->ApplyConfiguration();
57
58 Settings::Apply();
59 Settings::LogSettings();
60
61 game_config->Save();
62}
63
64void ConfigurePerGame::changeEvent(QEvent* event) {
65 if (event->type() == QEvent::LanguageChange) {
66 RetranslateUI();
67 }
68
69 QDialog::changeEvent(event);
70}
71
72void ConfigurePerGame::RetranslateUI() {
73 ui->retranslateUi(this);
74}
75
76void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file) {
77 this->file = std::move(file);
78 LoadConfiguration();
79}
80
81void ConfigurePerGame::LoadConfiguration() {
82 if (file == nullptr) {
83 return;
84 }
85
86 ui->addonsTab->LoadFromFile(file);
87
88 ui->display_title_id->setText(
89 QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper());
90
91 FileSys::PatchManager pm{title_id};
92 const auto control = pm.GetControlMetadata();
93 const auto loader = Loader::GetLoader(file);
94
95 if (control.first != nullptr) {
96 ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
97 ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
98 ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
99 } else {
100 std::string title;
101 if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
102 ui->display_name->setText(QString::fromStdString(title));
103
104 FileSys::NACP nacp;
105 if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success)
106 ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName()));
107
108 ui->display_version->setText(QStringLiteral("1.0.0"));
109 }
110
111 if (control.second != nullptr) {
112 scene->clear();
113
114 QPixmap map;
115 const auto bytes = control.second->ReadAllBytes();
116 map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
117
118 scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
119 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
120 } else {
121 std::vector<u8> bytes;
122 if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
123 scene->clear();
124
125 QPixmap map;
126 map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
127
128 scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
129 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
130 }
131 }
132
133 ui->display_filename->setText(QString::fromStdString(file->GetName()));
134
135 ui->display_format->setText(
136 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
137
138 const auto valueText = ReadableByteSize(file->GetSize());
139 ui->display_size->setText(valueText);
140}
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
new file mode 100644
index 000000000..5f9a08cef
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -0,0 +1,51 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <vector>
9
10#include <QDialog>
11#include <QList>
12
13#include "core/file_sys/vfs_types.h"
14#include "yuzu/configuration/config.h"
15
16class QGraphicsScene;
17class QStandardItem;
18class QStandardItemModel;
19class QTreeView;
20class QVBoxLayout;
21
22namespace Ui {
23class ConfigurePerGame;
24}
25
26class ConfigurePerGame : public QDialog {
27 Q_OBJECT
28
29public:
30 explicit ConfigurePerGame(QWidget* parent, u64 title_id);
31 ~ConfigurePerGame() override;
32
33 /// Save all button configurations to settings file
34 void ApplyConfiguration();
35
36 void LoadFromFile(FileSys::VirtualFile file);
37
38private:
39 void changeEvent(QEvent* event) override;
40 void RetranslateUI();
41
42 void LoadConfiguration();
43
44 std::unique_ptr<Ui::ConfigurePerGame> ui;
45 FileSys::VirtualFile file;
46 u64 title_id;
47
48 QGraphicsScene* scene;
49
50 std::unique_ptr<Config> game_config;
51};
diff --git a/src/yuzu/configuration/configure_per_game.ui b/src/yuzu/configuration/configure_per_game.ui
new file mode 100644
index 000000000..d2057c4ab
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game.ui
@@ -0,0 +1,350 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigurePerGame</class>
4 <widget class="QDialog" name="ConfigurePerGame">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>800</width>
10 <height>600</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Dialog</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout_3">
17 <item>
18 <layout class="QHBoxLayout" name="horizontalLayout">
19 <item>
20 <widget class="QGroupBox" name="groupBox">
21 <property name="sizePolicy">
22 <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
23 <horstretch>0</horstretch>
24 <verstretch>0</verstretch>
25 </sizepolicy>
26 </property>
27 <property name="title">
28 <string>Info</string>
29 </property>
30 <layout class="QVBoxLayout" name="verticalLayout">
31 <item alignment="Qt::AlignHCenter">
32 <widget class="QGraphicsView" name="icon_view">
33 <property name="sizePolicy">
34 <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
35 <horstretch>0</horstretch>
36 <verstretch>0</verstretch>
37 </sizepolicy>
38 </property>
39 <property name="minimumSize">
40 <size>
41 <width>256</width>
42 <height>256</height>
43 </size>
44 </property>
45 <property name="maximumSize">
46 <size>
47 <width>256</width>
48 <height>256</height>
49 </size>
50 </property>
51 <property name="verticalScrollBarPolicy">
52 <enum>Qt::ScrollBarAlwaysOff</enum>
53 </property>
54 <property name="horizontalScrollBarPolicy">
55 <enum>Qt::ScrollBarAlwaysOff</enum>
56 </property>
57 <property name="interactive">
58 <bool>false</bool>
59 </property>
60 </widget>
61 </item>
62 <item>
63 <layout class="QGridLayout" name="gridLayout_2">
64 <item row="6" column="1">
65 <widget class="QLineEdit" name="display_size">
66 <property name="enabled">
67 <bool>true</bool>
68 </property>
69 <property name="readOnly">
70 <bool>true</bool>
71 </property>
72 </widget>
73 </item>
74 <item row="3" column="1">
75 <widget class="QLineEdit" name="display_version">
76 <property name="enabled">
77 <bool>true</bool>
78 </property>
79 <property name="readOnly">
80 <bool>true</bool>
81 </property>
82 </widget>
83 </item>
84 <item row="1" column="0">
85 <widget class="QLabel" name="label">
86 <property name="text">
87 <string>Name</string>
88 </property>
89 </widget>
90 </item>
91 <item row="4" column="0">
92 <widget class="QLabel" name="label_4">
93 <property name="text">
94 <string>Title ID</string>
95 </property>
96 </widget>
97 </item>
98 <item row="4" column="1">
99 <widget class="QLineEdit" name="display_title_id">
100 <property name="enabled">
101 <bool>true</bool>
102 </property>
103 <property name="readOnly">
104 <bool>true</bool>
105 </property>
106 </widget>
107 </item>
108 <item row="7" column="1">
109 <widget class="QLineEdit" name="display_filename">
110 <property name="enabled">
111 <bool>true</bool>
112 </property>
113 <property name="readOnly">
114 <bool>true</bool>
115 </property>
116 </widget>
117 </item>
118 <item row="5" column="1">
119 <widget class="QLineEdit" name="display_format">
120 <property name="enabled">
121 <bool>true</bool>
122 </property>
123 <property name="readOnly">
124 <bool>true</bool>
125 </property>
126 </widget>
127 </item>
128 <item row="7" column="0">
129 <widget class="QLabel" name="label_7">
130 <property name="text">
131 <string>Filename</string>
132 </property>
133 </widget>
134 </item>
135 <item row="1" column="1">
136 <widget class="QLineEdit" name="display_name">
137 <property name="enabled">
138 <bool>true</bool>
139 </property>
140 <property name="readOnly">
141 <bool>true</bool>
142 </property>
143 </widget>
144 </item>
145 <item row="2" column="1">
146 <widget class="QLineEdit" name="display_developer">
147 <property name="enabled">
148 <bool>true</bool>
149 </property>
150 <property name="readOnly">
151 <bool>true</bool>
152 </property>
153 </widget>
154 </item>
155 <item row="5" column="0">
156 <widget class="QLabel" name="label_5">
157 <property name="text">
158 <string>Format</string>
159 </property>
160 </widget>
161 </item>
162 <item row="3" column="0">
163 <widget class="QLabel" name="label_3">
164 <property name="text">
165 <string>Version</string>
166 </property>
167 </widget>
168 </item>
169 <item row="6" column="0">
170 <widget class="QLabel" name="label_6">
171 <property name="text">
172 <string>Size</string>
173 </property>
174 </widget>
175 </item>
176 <item row="2" column="0">
177 <widget class="QLabel" name="label_2">
178 <property name="text">
179 <string>Developer</string>
180 </property>
181 </widget>
182 </item>
183 </layout>
184 </item>
185 <item>
186 <spacer name="verticalSpacer">
187 <property name="orientation">
188 <enum>Qt::Vertical</enum>
189 </property>
190 <property name="sizeHint" stdset="0">
191 <size>
192 <width>20</width>
193 <height>40</height>
194 </size>
195 </property>
196 </spacer>
197 </item>
198 </layout>
199 </widget>
200 </item>
201 <item>
202 <layout class="QVBoxLayout" name="VerticalLayout">
203 <item>
204 <layout class="QVBoxLayout" name="verticalLayout_2"/>
205 </item>
206 <item>
207 <widget class="QTabWidget" name="tabWidget">
208 <property name="enabled">
209 <bool>true</bool>
210 </property>
211 <property name="currentIndex">
212 <number>0</number>
213 </property>
214 <property name="usesScrollButtons">
215 <bool>true</bool>
216 </property>
217 <property name="documentMode">
218 <bool>false</bool>
219 </property>
220 <property name="tabsClosable">
221 <bool>false</bool>
222 </property>
223 <widget class="ConfigurePerGameAddons" name="addonsTab">
224 <attribute name="title">
225 <string>Add-Ons</string>
226 </attribute>
227 </widget>
228 <widget class="ConfigureGeneral" name="generalTab">
229 <attribute name="title">
230 <string>General</string>
231 </attribute>
232 </widget>
233 <widget class="ConfigureSystem" name="systemTab">
234 <attribute name="title">
235 <string>System</string>
236 </attribute>
237 </widget>
238 <widget class="ConfigureGraphics" name="graphicsTab">
239 <attribute name="title">
240 <string>Graphics</string>
241 </attribute>
242 </widget>
243 <widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab">
244 <attribute name="title">
245 <string>Adv. Graphics</string>
246 </attribute>
247 </widget>
248 <widget class="ConfigureAudio" name="audioTab">
249 <attribute name="title">
250 <string>Audio</string>
251 </attribute>
252 </widget>
253 </widget>
254 </item>
255 </layout>
256 </item>
257 </layout>
258 </item>
259 <item>
260 <widget class="QDialogButtonBox" name="buttonBox">
261 <property name="sizePolicy">
262 <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
263 <horstretch>0</horstretch>
264 <verstretch>0</verstretch>
265 </sizepolicy>
266 </property>
267 <property name="orientation">
268 <enum>Qt::Horizontal</enum>
269 </property>
270 <property name="standardButtons">
271 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
272 </property>
273 </widget>
274 </item>
275 </layout>
276 </widget>
277 <customwidgets>
278 <customwidget>
279 <class>ConfigureGeneral</class>
280 <extends>QWidget</extends>
281 <header>configuration/configure_general.h</header>
282 <container>1</container>
283 </customwidget>
284 <customwidget>
285 <class>ConfigureSystem</class>
286 <extends>QWidget</extends>
287 <header>configuration/configure_system.h</header>
288 <container>1</container>
289 </customwidget>
290 <customwidget>
291 <class>ConfigureAudio</class>
292 <extends>QWidget</extends>
293 <header>configuration/configure_audio.h</header>
294 <container>1</container>
295 </customwidget>
296 <customwidget>
297 <class>ConfigureGraphics</class>
298 <extends>QWidget</extends>
299 <header>configuration/configure_graphics.h</header>
300 <container>1</container>
301 </customwidget>
302 <customwidget>
303 <class>ConfigureGraphicsAdvanced</class>
304 <extends>QWidget</extends>
305 <header>configuration/configure_graphics_advanced.h</header>
306 <container>1</container>
307 </customwidget>
308 <customwidget>
309 <class>ConfigurePerGameAddons</class>
310 <extends>QWidget</extends>
311 <header>configuration/configure_per_game_addons.h</header>
312 <container>1</container>
313 </customwidget>
314 </customwidgets>
315 <resources/>
316 <connections>
317 <connection>
318 <sender>buttonBox</sender>
319 <signal>accepted()</signal>
320 <receiver>ConfigurePerGame</receiver>
321 <slot>accept()</slot>
322 <hints>
323 <hint type="sourcelabel">
324 <x>248</x>
325 <y>254</y>
326 </hint>
327 <hint type="destinationlabel">
328 <x>157</x>
329 <y>274</y>
330 </hint>
331 </hints>
332 </connection>
333 <connection>
334 <sender>buttonBox</sender>
335 <signal>rejected()</signal>
336 <receiver>ConfigurePerGame</receiver>
337 <slot>reject()</slot>
338 <hints>
339 <hint type="sourcelabel">
340 <x>316</x>
341 <y>260</y>
342 </hint>
343 <hint type="destinationlabel">
344 <x>286</x>
345 <y>274</y>
346 </hint>
347 </hints>
348 </connection>
349 </connections>
350</ui>
diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index d7f259f12..478d5d3a1 100644
--- a/src/yuzu/configuration/configure_per_general.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -15,23 +15,20 @@
15 15
16#include "common/common_paths.h" 16#include "common/common_paths.h"
17#include "common/file_util.h" 17#include "common/file_util.h"
18#include "core/file_sys/control_metadata.h" 18#include "core/core.h"
19#include "core/file_sys/patch_manager.h" 19#include "core/file_sys/patch_manager.h"
20#include "core/file_sys/xts_archive.h" 20#include "core/file_sys/xts_archive.h"
21#include "core/loader/loader.h" 21#include "core/loader/loader.h"
22#include "ui_configure_per_general.h" 22#include "ui_configure_per_game_addons.h"
23#include "yuzu/configuration/config.h" 23#include "yuzu/configuration/config.h"
24#include "yuzu/configuration/configure_input.h" 24#include "yuzu/configuration/configure_input.h"
25#include "yuzu/configuration/configure_per_general.h" 25#include "yuzu/configuration/configure_per_game_addons.h"
26#include "yuzu/uisettings.h" 26#include "yuzu/uisettings.h"
27#include "yuzu/util/util.h" 27#include "yuzu/util/util.h"
28 28
29ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) 29ConfigurePerGameAddons::ConfigurePerGameAddons(QWidget* parent)
30 : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) { 30 : QWidget(parent), ui(new Ui::ConfigurePerGameAddons) {
31
32 ui->setupUi(this); 31 ui->setupUi(this);
33 setFocusPolicy(Qt::ClickFocus);
34 setWindowTitle(tr("Properties"));
35 32
36 layout = new QVBoxLayout; 33 layout = new QVBoxLayout;
37 tree_view = new QTreeView; 34 tree_view = new QTreeView;
@@ -52,7 +49,7 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
52 item_model->setHeaderData(1, Qt::Horizontal, tr("Version")); 49 item_model->setHeaderData(1, Qt::Horizontal, tr("Version"));
53 50
54 // We must register all custom types with the Qt Automoc system so that we are able to use it 51 // We must register all custom types with the Qt Automoc system so that we are able to use it
55 // with signals/slots. In this case, QList falls under the umbrells of custom types. 52 // with signals/slots. In this case, QList falls under the umbrella of custom types.
56 qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); 53 qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
57 54
58 layout->setContentsMargins(0, 0, 0, 0); 55 layout->setContentsMargins(0, 0, 0, 0);
@@ -61,18 +58,15 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
61 58
62 ui->scrollArea->setLayout(layout); 59 ui->scrollArea->setLayout(layout);
63 60
64 scene = new QGraphicsScene; 61 ui->scrollArea->setEnabled(!Core::System::GetInstance().IsPoweredOn());
65 ui->icon_view->setScene(scene);
66 62
67 connect(item_model, &QStandardItemModel::itemChanged, 63 connect(item_model, &QStandardItemModel::itemChanged,
68 [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); 64 [] { UISettings::values.is_game_list_reload_pending.exchange(true); });
69
70 LoadConfiguration();
71} 65}
72 66
73ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; 67ConfigurePerGameAddons::~ConfigurePerGameAddons() = default;
74 68
75void ConfigurePerGameGeneral::ApplyConfiguration() { 69void ConfigurePerGameAddons::ApplyConfiguration() {
76 std::vector<std::string> disabled_addons; 70 std::vector<std::string> disabled_addons;
77 71
78 for (const auto& item : list_items) { 72 for (const auto& item : list_items) {
@@ -92,72 +86,35 @@ void ConfigurePerGameGeneral::ApplyConfiguration() {
92 Settings::values.disabled_addons[title_id] = disabled_addons; 86 Settings::values.disabled_addons[title_id] = disabled_addons;
93} 87}
94 88
95void ConfigurePerGameGeneral::changeEvent(QEvent* event) { 89void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file) {
90 this->file = std::move(file);
91 LoadConfiguration();
92}
93
94void ConfigurePerGameAddons::SetTitleId(u64 id) {
95 this->title_id = id;
96}
97
98void ConfigurePerGameAddons::changeEvent(QEvent* event) {
96 if (event->type() == QEvent::LanguageChange) { 99 if (event->type() == QEvent::LanguageChange) {
97 RetranslateUI(); 100 RetranslateUI();
98 } 101 }
99 102
100 QDialog::changeEvent(event); 103 QWidget::changeEvent(event);
101} 104}
102 105
103void ConfigurePerGameGeneral::RetranslateUI() { 106void ConfigurePerGameAddons::RetranslateUI() {
104 ui->retranslateUi(this); 107 ui->retranslateUi(this);
105} 108}
106 109
107void ConfigurePerGameGeneral::LoadFromFile(FileSys::VirtualFile file) { 110void ConfigurePerGameAddons::LoadConfiguration() {
108 this->file = std::move(file);
109 LoadConfiguration();
110}
111
112void ConfigurePerGameGeneral::LoadConfiguration() {
113 if (file == nullptr) { 111 if (file == nullptr) {
114 return; 112 return;
115 } 113 }
116 114
117 ui->display_title_id->setText(QString::fromStdString(fmt::format("{:016X}", title_id)));
118
119 FileSys::PatchManager pm{title_id}; 115 FileSys::PatchManager pm{title_id};
120 const auto control = pm.GetControlMetadata();
121 const auto loader = Loader::GetLoader(file); 116 const auto loader = Loader::GetLoader(file);
122 117
123 if (control.first != nullptr) {
124 ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
125 ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
126 ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
127 } else {
128 std::string title;
129 if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
130 ui->display_name->setText(QString::fromStdString(title));
131
132 FileSys::NACP nacp;
133 if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success)
134 ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName()));
135
136 ui->display_version->setText(QStringLiteral("1.0.0"));
137 }
138
139 if (control.second != nullptr) {
140 scene->clear();
141
142 QPixmap map;
143 const auto bytes = control.second->ReadAllBytes();
144 map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
145
146 scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
147 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
148 } else {
149 std::vector<u8> bytes;
150 if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
151 scene->clear();
152
153 QPixmap map;
154 map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
155
156 scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
157 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
158 }
159 }
160
161 FileSys::VirtualFile update_raw; 118 FileSys::VirtualFile update_raw;
162 loader->ReadUpdateRaw(update_raw); 119 loader->ReadUpdateRaw(update_raw);
163 120
@@ -182,12 +139,4 @@ void ConfigurePerGameGeneral::LoadConfiguration() {
182 } 139 }
183 140
184 tree_view->setColumnWidth(0, 5 * tree_view->width() / 16); 141 tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
185
186 ui->display_filename->setText(QString::fromStdString(file->GetName()));
187
188 ui->display_format->setText(
189 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
190
191 const auto valueText = ReadableByteSize(file->GetSize());
192 ui->display_size->setText(valueText);
193} 142}
diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_game_addons.h
index a3b2cdeff..a00ec3539 100644
--- a/src/yuzu/configuration/configure_per_general.h
+++ b/src/yuzu/configuration/configure_per_game_addons.h
@@ -1,4 +1,4 @@
1// Copyright 2016 Citra Emulator Project 1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
@@ -7,7 +7,6 @@
7#include <memory> 7#include <memory>
8#include <vector> 8#include <vector>
9 9
10#include <QDialog>
11#include <QList> 10#include <QList>
12 11
13#include "core/file_sys/vfs_types.h" 12#include "core/file_sys/vfs_types.h"
@@ -19,35 +18,36 @@ class QTreeView;
19class QVBoxLayout; 18class QVBoxLayout;
20 19
21namespace Ui { 20namespace Ui {
22class ConfigurePerGameGeneral; 21class ConfigurePerGameAddons;
23} 22}
24 23
25class ConfigurePerGameGeneral : public QDialog { 24class ConfigurePerGameAddons : public QWidget {
26 Q_OBJECT 25 Q_OBJECT
27 26
28public: 27public:
29 explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); 28 explicit ConfigurePerGameAddons(QWidget* parent = nullptr);
30 ~ConfigurePerGameGeneral() override; 29 ~ConfigurePerGameAddons() override;
31 30
32 /// Save all button configurations to settings file 31 /// Save all button configurations to settings file
33 void ApplyConfiguration(); 32 void ApplyConfiguration();
34 33
35 void LoadFromFile(FileSys::VirtualFile file); 34 void LoadFromFile(FileSys::VirtualFile file);
36 35
36 void SetTitleId(u64 id);
37
37private: 38private:
38 void changeEvent(QEvent* event) override; 39 void changeEvent(QEvent* event) override;
39 void RetranslateUI(); 40 void RetranslateUI();
40 41
41 void LoadConfiguration(); 42 void LoadConfiguration();
42 43
43 std::unique_ptr<Ui::ConfigurePerGameGeneral> ui; 44 std::unique_ptr<Ui::ConfigurePerGameAddons> ui;
44 FileSys::VirtualFile file; 45 FileSys::VirtualFile file;
45 u64 title_id; 46 u64 title_id;
46 47
47 QVBoxLayout* layout; 48 QVBoxLayout* layout;
48 QTreeView* tree_view; 49 QTreeView* tree_view;
49 QStandardItemModel* item_model; 50 QStandardItemModel* item_model;
50 QGraphicsScene* scene;
51 51
52 std::vector<QList<QStandardItem*>> list_items; 52 std::vector<QList<QStandardItem*>> list_items;
53}; 53};
diff --git a/src/yuzu/configuration/configure_per_game_addons.ui b/src/yuzu/configuration/configure_per_game_addons.ui
new file mode 100644
index 000000000..aefdebfcd
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_game_addons.ui
@@ -0,0 +1,38 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigurePerGameAddons</class>
4 <widget class="QWidget" name="ConfigurePerGameAddons">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>400</width>
10 <height>300</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Form</string>
15 </property>
16 <layout class="QGridLayout" name="gridLayout">
17 <item row="0" column="0">
18 <widget class="QScrollArea" name="scrollArea">
19 <property name="widgetResizable">
20 <bool>true</bool>
21 </property>
22 <widget class="QWidget" name="scrollAreaWidgetContents">
23 <property name="geometry">
24 <rect>
25 <x>0</x>
26 <y>0</y>
27 <width>380</width>
28 <height>280</height>
29 </rect>
30 </property>
31 </widget>
32 </widget>
33 </item>
34 </layout>
35 </widget>
36 <resources/>
37 <connections/>
38</ui>
diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui
deleted file mode 100644
index 8fdd96fa4..000000000
--- a/src/yuzu/configuration/configure_per_general.ui
+++ /dev/null
@@ -1,276 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigurePerGameGeneral</class>
4 <widget class="QDialog" name="ConfigurePerGameGeneral">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>400</width>
10 <height>520</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>ConfigurePerGameGeneral</string>
15 </property>
16 <layout class="QHBoxLayout" name="HorizontalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="VerticalLayout">
19 <item>
20 <widget class="QGroupBox" name="GeneralGroupBox">
21 <property name="title">
22 <string>Info</string>
23 </property>
24 <layout class="QHBoxLayout" name="GeneralHorizontalLayout">
25 <item>
26 <layout class="QGridLayout" name="gridLayout_2">
27 <item row="6" column="1" colspan="2">
28 <widget class="QLineEdit" name="display_filename">
29 <property name="enabled">
30 <bool>true</bool>
31 </property>
32 <property name="readOnly">
33 <bool>true</bool>
34 </property>
35 </widget>
36 </item>
37 <item row="0" column="1">
38 <widget class="QLineEdit" name="display_name">
39 <property name="enabled">
40 <bool>true</bool>
41 </property>
42 <property name="readOnly">
43 <bool>true</bool>
44 </property>
45 </widget>
46 </item>
47 <item row="1" column="0">
48 <widget class="QLabel" name="label_2">
49 <property name="text">
50 <string>Developer</string>
51 </property>
52 </widget>
53 </item>
54 <item row="5" column="1" colspan="2">
55 <widget class="QLineEdit" name="display_size">
56 <property name="enabled">
57 <bool>true</bool>
58 </property>
59 <property name="readOnly">
60 <bool>true</bool>
61 </property>
62 </widget>
63 </item>
64 <item row="0" column="0">
65 <widget class="QLabel" name="label">
66 <property name="text">
67 <string>Name</string>
68 </property>
69 </widget>
70 </item>
71 <item row="6" column="0">
72 <widget class="QLabel" name="label_7">
73 <property name="text">
74 <string>Filename</string>
75 </property>
76 </widget>
77 </item>
78 <item row="2" column="0">
79 <widget class="QLabel" name="label_3">
80 <property name="text">
81 <string>Version</string>
82 </property>
83 </widget>
84 </item>
85 <item row="4" column="0">
86 <widget class="QLabel" name="label_5">
87 <property name="text">
88 <string>Format</string>
89 </property>
90 </widget>
91 </item>
92 <item row="2" column="1">
93 <widget class="QLineEdit" name="display_version">
94 <property name="enabled">
95 <bool>true</bool>
96 </property>
97 <property name="readOnly">
98 <bool>true</bool>
99 </property>
100 </widget>
101 </item>
102 <item row="4" column="1">
103 <widget class="QLineEdit" name="display_format">
104 <property name="enabled">
105 <bool>true</bool>
106 </property>
107 <property name="readOnly">
108 <bool>true</bool>
109 </property>
110 </widget>
111 </item>
112 <item row="5" column="0">
113 <widget class="QLabel" name="label_6">
114 <property name="text">
115 <string>Size</string>
116 </property>
117 </widget>
118 </item>
119 <item row="1" column="1">
120 <widget class="QLineEdit" name="display_developer">
121 <property name="enabled">
122 <bool>true</bool>
123 </property>
124 <property name="readOnly">
125 <bool>true</bool>
126 </property>
127 </widget>
128 </item>
129 <item row="3" column="0">
130 <widget class="QLabel" name="label_4">
131 <property name="text">
132 <string>Title ID</string>
133 </property>
134 </widget>
135 </item>
136 <item row="3" column="1">
137 <widget class="QLineEdit" name="display_title_id">
138 <property name="enabled">
139 <bool>true</bool>
140 </property>
141 <property name="readOnly">
142 <bool>true</bool>
143 </property>
144 </widget>
145 </item>
146 <item row="0" column="2" rowspan="5">
147 <widget class="QGraphicsView" name="icon_view">
148 <property name="sizePolicy">
149 <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
150 <horstretch>0</horstretch>
151 <verstretch>0</verstretch>
152 </sizepolicy>
153 </property>
154 <property name="minimumSize">
155 <size>
156 <width>128</width>
157 <height>128</height>
158 </size>
159 </property>
160 <property name="maximumSize">
161 <size>
162 <width>128</width>
163 <height>128</height>
164 </size>
165 </property>
166 <property name="verticalScrollBarPolicy">
167 <enum>Qt::ScrollBarAlwaysOff</enum>
168 </property>
169 <property name="horizontalScrollBarPolicy">
170 <enum>Qt::ScrollBarAlwaysOff</enum>
171 </property>
172 <property name="sizeAdjustPolicy">
173 <enum>QAbstractScrollArea::AdjustToContents</enum>
174 </property>
175 <property name="interactive">
176 <bool>false</bool>
177 </property>
178 </widget>
179 </item>
180 </layout>
181 </item>
182 </layout>
183 </widget>
184 </item>
185 <item>
186 <widget class="QGroupBox" name="PerformanceGroupBox">
187 <property name="title">
188 <string>Add-Ons</string>
189 </property>
190 <layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
191 <item>
192 <widget class="QScrollArea" name="scrollArea">
193 <property name="widgetResizable">
194 <bool>true</bool>
195 </property>
196 <widget class="QWidget" name="scrollAreaWidgetContents">
197 <property name="geometry">
198 <rect>
199 <x>0</x>
200 <y>0</y>
201 <width>350</width>
202 <height>169</height>
203 </rect>
204 </property>
205 </widget>
206 </widget>
207 </item>
208 <item>
209 <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/>
210 </item>
211 </layout>
212 </widget>
213 </item>
214 <item>
215 <spacer name="verticalSpacer">
216 <property name="orientation">
217 <enum>Qt::Vertical</enum>
218 </property>
219 <property name="sizeType">
220 <enum>QSizePolicy::Fixed</enum>
221 </property>
222 <property name="sizeHint" stdset="0">
223 <size>
224 <width>20</width>
225 <height>40</height>
226 </size>
227 </property>
228 </spacer>
229 </item>
230 <item>
231 <widget class="QDialogButtonBox" name="buttonBox">
232 <property name="standardButtons">
233 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
234 </property>
235 </widget>
236 </item>
237 </layout>
238 </item>
239 </layout>
240 </widget>
241 <resources/>
242 <connections>
243 <connection>
244 <sender>buttonBox</sender>
245 <signal>accepted()</signal>
246 <receiver>ConfigurePerGameGeneral</receiver>
247 <slot>accept()</slot>
248 <hints>
249 <hint type="sourcelabel">
250 <x>269</x>
251 <y>567</y>
252 </hint>
253 <hint type="destinationlabel">
254 <x>269</x>
255 <y>294</y>
256 </hint>
257 </hints>
258 </connection>
259 <connection>
260 <sender>buttonBox</sender>
261 <signal>rejected()</signal>
262 <receiver>ConfigurePerGameGeneral</receiver>
263 <slot>reject()</slot>
264 <hints>
265 <hint type="sourcelabel">
266 <x>269</x>
267 <y>567</y>
268 </hint>
269 <hint type="destinationlabel">
270 <x>269</x>
271 <y>294</y>
272 </hint>
273 </hints>
274 </connection>
275 </connections>
276</ui>
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 10315e7a6..68e02738b 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -14,6 +14,7 @@
14#include "core/core.h" 14#include "core/core.h"
15#include "core/settings.h" 15#include "core/settings.h"
16#include "ui_configure_system.h" 16#include "ui_configure_system.h"
17#include "yuzu/configuration/configuration_shared.h"
17#include "yuzu/configuration/configure_system.h" 18#include "yuzu/configuration/configure_system.h"
18 19
19ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { 20ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
@@ -21,20 +22,25 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::
21 connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, 22 connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
22 &ConfigureSystem::RefreshConsoleID); 23 &ConfigureSystem::RefreshConsoleID);
23 24
24 connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { 25 connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
25 ui->rng_seed_edit->setEnabled(checked); 26 ui->rng_seed_edit->setEnabled(state == Qt::Checked);
26 if (!checked) { 27 if (state != Qt::Checked) {
27 ui->rng_seed_edit->setText(QStringLiteral("00000000")); 28 ui->rng_seed_edit->setText(QStringLiteral("00000000"));
28 } 29 }
29 }); 30 });
30 31
31 connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](bool checked) { 32 connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
32 ui->custom_rtc_edit->setEnabled(checked); 33 ui->custom_rtc_edit->setEnabled(state == Qt::Checked);
33 if (!checked) { 34 if (state != Qt::Checked) {
34 ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime()); 35 ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
35 } 36 }
36 }); 37 });
37 38
39 ui->label_console_id->setVisible(Settings::configuring_global);
40 ui->button_regenerate_console_id->setVisible(Settings::configuring_global);
41
42 SetupPerGameUI();
43
38 SetConfiguration(); 44 SetConfiguration();
39} 45}
40 46
@@ -54,26 +60,58 @@ void ConfigureSystem::RetranslateUI() {
54 60
55void ConfigureSystem::SetConfiguration() { 61void ConfigureSystem::SetConfiguration() {
56 enabled = !Core::System::GetInstance().IsPoweredOn(); 62 enabled = !Core::System::GetInstance().IsPoweredOn();
63 const auto rng_seed =
64 QStringLiteral("%1")
65 .arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'})
66 .toUpper();
67 const auto rtc_time = Settings::values.custom_rtc.GetValue().value_or(
68 std::chrono::seconds(QDateTime::currentSecsSinceEpoch()));
57 69
58 ui->combo_language->setCurrentIndex(Settings::values.language_index); 70 if (Settings::configuring_global) {
59 ui->combo_region->setCurrentIndex(Settings::values.region_index); 71 ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue());
60 ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index); 72 ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue());
61 ui->combo_sound->setCurrentIndex(Settings::values.sound_index); 73 ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue());
62 74 ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue());
63 ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); 75
64 ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.has_value()); 76 ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value());
65 77 ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() &&
66 const auto rng_seed = QStringLiteral("%1") 78 Settings::values.rng_seed.UsingGlobal());
67 .arg(Settings::values.rng_seed.value_or(0), 8, 16, QLatin1Char{'0'}) 79 ui->rng_seed_edit->setText(rng_seed);
68 .toUpper(); 80
69 ui->rng_seed_edit->setText(rng_seed); 81 ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value());
70 82 ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() &&
71 ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); 83 Settings::values.rng_seed.UsingGlobal());
72 ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); 84 ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count()));
85 } else {
86 ConfigurationShared::SetPerGameSetting(ui->combo_language,
87 &Settings::values.language_index);
88 ConfigurationShared::SetPerGameSetting(ui->combo_region, &Settings::values.region_index);
89 ConfigurationShared::SetPerGameSetting(ui->combo_time_zone,
90 &Settings::values.time_zone_index);
91 ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index);
92
93 if (Settings::values.rng_seed.UsingGlobal()) {
94 ui->rng_seed_checkbox->setCheckState(Qt::PartiallyChecked);
95 } else {
96 ui->rng_seed_checkbox->setCheckState(
97 Settings::values.rng_seed.GetValue().has_value() ? Qt::Checked : Qt::Unchecked);
98 ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value());
99 if (Settings::values.rng_seed.GetValue().has_value()) {
100 ui->rng_seed_edit->setText(rng_seed);
101 }
102 }
73 103
74 const auto rtc_time = Settings::values.custom_rtc.value_or( 104 if (Settings::values.custom_rtc.UsingGlobal()) {
75 std::chrono::seconds(QDateTime::currentSecsSinceEpoch())); 105 ui->custom_rtc_checkbox->setCheckState(Qt::PartiallyChecked);
76 ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count())); 106 } else {
107 ui->custom_rtc_checkbox->setCheckState(
108 Settings::values.custom_rtc.GetValue().has_value() ? Qt::Checked : Qt::Unchecked);
109 ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value());
110 if (Settings::values.custom_rtc.GetValue().has_value()) {
111 ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count()));
112 }
113 }
114 }
77} 115}
78 116
79void ConfigureSystem::ReadSystemSettings() {} 117void ConfigureSystem::ReadSystemSettings() {}
@@ -83,22 +121,78 @@ void ConfigureSystem::ApplyConfiguration() {
83 return; 121 return;
84 } 122 }
85 123
86 Settings::values.language_index = ui->combo_language->currentIndex(); 124 if (Settings::configuring_global) {
87 Settings::values.region_index = ui->combo_region->currentIndex(); 125 // Guard if during game and set to game-specific value
88 Settings::values.time_zone_index = ui->combo_time_zone->currentIndex(); 126 if (Settings::values.language_index.UsingGlobal()) {
89 Settings::values.sound_index = ui->combo_sound->currentIndex(); 127 Settings::values.language_index.SetValue(ui->combo_language->currentIndex());
128 }
129 if (Settings::values.region_index.UsingGlobal()) {
130 Settings::values.region_index.SetValue(ui->combo_region->currentIndex());
131 }
132 if (Settings::values.time_zone_index.UsingGlobal()) {
133 Settings::values.time_zone_index.SetValue(ui->combo_time_zone->currentIndex());
134 }
135 if (Settings::values.sound_index.UsingGlobal()) {
136 Settings::values.sound_index.SetValue(ui->combo_sound->currentIndex());
137 }
138
139 if (Settings::values.rng_seed.UsingGlobal()) {
140 if (ui->rng_seed_checkbox->isChecked()) {
141 Settings::values.rng_seed.SetValue(
142 ui->rng_seed_edit->text().toULongLong(nullptr, 16));
143 } else {
144 Settings::values.rng_seed.SetValue(std::nullopt);
145 }
146 }
90 147
91 if (ui->rng_seed_checkbox->isChecked()) { 148 if (Settings::values.custom_rtc.UsingGlobal()) {
92 Settings::values.rng_seed = ui->rng_seed_edit->text().toULongLong(nullptr, 16); 149 if (ui->custom_rtc_checkbox->isChecked()) {
150 Settings::values.custom_rtc.SetValue(
151 std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()));
152 } else {
153 Settings::values.custom_rtc.SetValue(std::nullopt);
154 }
155 }
93 } else { 156 } else {
94 Settings::values.rng_seed = std::nullopt; 157 ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index,
95 } 158 ui->combo_language);
159 ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region);
160 ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index,
161 ui->combo_time_zone);
162 ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound);
163
164 switch (ui->rng_seed_checkbox->checkState()) {
165 case Qt::Checked:
166 Settings::values.rng_seed.SetGlobal(false);
167 Settings::values.rng_seed.SetValue(ui->rng_seed_edit->text().toULongLong(nullptr, 16));
168 break;
169 case Qt::Unchecked:
170 Settings::values.rng_seed.SetGlobal(false);
171 Settings::values.rng_seed.SetValue(std::nullopt);
172 break;
173 case Qt::PartiallyChecked:
174 Settings::values.rng_seed.SetGlobal(false);
175 Settings::values.rng_seed.SetValue(std::nullopt);
176 Settings::values.rng_seed.SetGlobal(true);
177 break;
178 }
96 179
97 if (ui->custom_rtc_checkbox->isChecked()) { 180 switch (ui->custom_rtc_checkbox->checkState()) {
98 Settings::values.custom_rtc = 181 case Qt::Checked:
99 std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()); 182 Settings::values.custom_rtc.SetGlobal(false);
100 } else { 183 Settings::values.custom_rtc.SetValue(
101 Settings::values.custom_rtc = std::nullopt; 184 std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()));
185 break;
186 case Qt::Unchecked:
187 Settings::values.custom_rtc.SetGlobal(false);
188 Settings::values.custom_rtc.SetValue(std::nullopt);
189 break;
190 case Qt::PartiallyChecked:
191 Settings::values.custom_rtc.SetGlobal(false);
192 Settings::values.custom_rtc.SetValue(std::nullopt);
193 Settings::values.custom_rtc.SetGlobal(true);
194 break;
195 }
102 } 196 }
103 197
104 Settings::Apply(); 198 Settings::Apply();
@@ -120,3 +214,25 @@ void ConfigureSystem::RefreshConsoleID() {
120 ui->label_console_id->setText( 214 ui->label_console_id->setText(
121 tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); 215 tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
122} 216}
217
218void ConfigureSystem::SetupPerGameUI() {
219 if (Settings::configuring_global) {
220 ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal());
221 ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal());
222 ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal());
223 ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal());
224 ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal());
225 ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal());
226 ui->custom_rtc_checkbox->setEnabled(Settings::values.custom_rtc.UsingGlobal());
227 ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.UsingGlobal());
228
229 return;
230 }
231
232 ConfigurationShared::InsertGlobalItem(ui->combo_language);
233 ConfigurationShared::InsertGlobalItem(ui->combo_region);
234 ConfigurationShared::InsertGlobalItem(ui->combo_time_zone);
235 ConfigurationShared::InsertGlobalItem(ui->combo_sound);
236 ui->rng_seed_checkbox->setTristate(true);
237 ui->custom_rtc_checkbox->setTristate(true);
238}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index 26d42d5c5..f317ef8b5 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -32,6 +32,8 @@ private:
32 32
33 void RefreshConsoleID(); 33 void RefreshConsoleID();
34 34
35 void SetupPerGameUI();
36
35 std::unique_ptr<Ui::ConfigureSystem> ui; 37 std::unique_ptr<Ui::ConfigureSystem> ui;
36 bool enabled = false; 38 bool enabled = false;
37 39
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 9844e4764..4d501a8f9 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -16,7 +16,7 @@
16#include "applets/software_keyboard.h" 16#include "applets/software_keyboard.h"
17#include "applets/web_browser.h" 17#include "applets/web_browser.h"
18#include "configuration/configure_input.h" 18#include "configuration/configure_input.h"
19#include "configuration/configure_per_general.h" 19#include "configuration/configure_per_game.h"
20#include "core/file_sys/vfs.h" 20#include "core/file_sys/vfs.h"
21#include "core/file_sys/vfs_real.h" 21#include "core/file_sys/vfs_real.h"
22#include "core/frontend/applets/general_frontend.h" 22#include "core/frontend/applets/general_frontend.h"
@@ -534,15 +534,15 @@ void GMainWindow::InitializeWidgets() {
534 if (emulation_running) { 534 if (emulation_running) {
535 return; 535 return;
536 } 536 }
537 bool is_async = 537 bool is_async = !Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
538 !Settings::values.use_asynchronous_gpu_emulation || Settings::values.use_multi_core; 538 Settings::values.use_multi_core.GetValue();
539 Settings::values.use_asynchronous_gpu_emulation = is_async; 539 Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async);
540 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); 540 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
541 Settings::Apply(); 541 Settings::Apply();
542 }); 542 });
543 async_status_button->setText(tr("ASYNC")); 543 async_status_button->setText(tr("ASYNC"));
544 async_status_button->setCheckable(true); 544 async_status_button->setCheckable(true);
545 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); 545 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
546 546
547 // Setup Multicore button 547 // Setup Multicore button
548 multicore_status_button = new QPushButton(); 548 multicore_status_button = new QPushButton();
@@ -552,17 +552,17 @@ void GMainWindow::InitializeWidgets() {
552 if (emulation_running) { 552 if (emulation_running) {
553 return; 553 return;
554 } 554 }
555 Settings::values.use_multi_core = !Settings::values.use_multi_core; 555 Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue());
556 bool is_async = 556 bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
557 Settings::values.use_asynchronous_gpu_emulation || Settings::values.use_multi_core; 557 Settings::values.use_multi_core.GetValue();
558 Settings::values.use_asynchronous_gpu_emulation = is_async; 558 Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async);
559 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation); 559 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
560 multicore_status_button->setChecked(Settings::values.use_multi_core); 560 multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
561 Settings::Apply(); 561 Settings::Apply();
562 }); 562 });
563 multicore_status_button->setText(tr("MULTICORE")); 563 multicore_status_button->setText(tr("MULTICORE"));
564 multicore_status_button->setCheckable(true); 564 multicore_status_button->setCheckable(true);
565 multicore_status_button->setChecked(Settings::values.use_multi_core); 565 multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
566 statusBar()->insertPermanentWidget(0, multicore_status_button); 566 statusBar()->insertPermanentWidget(0, multicore_status_button);
567 statusBar()->insertPermanentWidget(0, async_status_button); 567 statusBar()->insertPermanentWidget(0, async_status_button);
568 568
@@ -581,16 +581,16 @@ void GMainWindow::InitializeWidgets() {
581 renderer_status_button->setCheckable(false); 581 renderer_status_button->setCheckable(false);
582 renderer_status_button->setDisabled(true); 582 renderer_status_button->setDisabled(true);
583#else 583#else
584 renderer_status_button->setChecked(Settings::values.renderer_backend == 584 renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
585 Settings::RendererBackend::Vulkan); 585 Settings::RendererBackend::Vulkan);
586 connect(renderer_status_button, &QPushButton::clicked, [=] { 586 connect(renderer_status_button, &QPushButton::clicked, [=] {
587 if (emulation_running) { 587 if (emulation_running) {
588 return; 588 return;
589 } 589 }
590 if (renderer_status_button->isChecked()) { 590 if (renderer_status_button->isChecked()) {
591 Settings::values.renderer_backend = Settings::RendererBackend::Vulkan; 591 Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Vulkan);
592 } else { 592 } else {
593 Settings::values.renderer_backend = Settings::RendererBackend::OpenGL; 593 Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL);
594 } 594 }
595 595
596 Settings::Apply(); 596 Settings::Apply();
@@ -727,21 +727,24 @@ void GMainWindow::InitializeHotkeys() {
727 }); 727 });
728 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Speed Limit"), this), 728 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Speed Limit"), this),
729 &QShortcut::activated, this, [&] { 729 &QShortcut::activated, this, [&] {
730 Settings::values.use_frame_limit = !Settings::values.use_frame_limit; 730 Settings::values.use_frame_limit.SetValue(
731 !Settings::values.use_frame_limit.GetValue());
731 UpdateStatusBar(); 732 UpdateStatusBar();
732 }); 733 });
733 constexpr u16 SPEED_LIMIT_STEP = 5; 734 constexpr u16 SPEED_LIMIT_STEP = 5;
734 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Increase Speed Limit"), this), 735 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Increase Speed Limit"), this),
735 &QShortcut::activated, this, [&] { 736 &QShortcut::activated, this, [&] {
736 if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) { 737 if (Settings::values.frame_limit.GetValue() < 9999 - SPEED_LIMIT_STEP) {
737 Settings::values.frame_limit += SPEED_LIMIT_STEP; 738 Settings::values.frame_limit.SetValue(SPEED_LIMIT_STEP +
739 Settings::values.frame_limit.GetValue());
738 UpdateStatusBar(); 740 UpdateStatusBar();
739 } 741 }
740 }); 742 });
741 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Decrease Speed Limit"), this), 743 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Decrease Speed Limit"), this),
742 &QShortcut::activated, this, [&] { 744 &QShortcut::activated, this, [&] {
743 if (Settings::values.frame_limit > SPEED_LIMIT_STEP) { 745 if (Settings::values.frame_limit.GetValue() > SPEED_LIMIT_STEP) {
744 Settings::values.frame_limit -= SPEED_LIMIT_STEP; 746 Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() -
747 SPEED_LIMIT_STEP);
745 UpdateStatusBar(); 748 UpdateStatusBar();
746 } 749 }
747 }); 750 });
@@ -1039,6 +1042,17 @@ void GMainWindow::BootGame(const QString& filename) {
1039 LOG_INFO(Frontend, "yuzu starting..."); 1042 LOG_INFO(Frontend, "yuzu starting...");
1040 StoreRecentFile(filename); // Put the filename on top of the list 1043 StoreRecentFile(filename); // Put the filename on top of the list
1041 1044
1045 u64 title_id{0};
1046
1047 const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData());
1048 const auto loader = Loader::GetLoader(v_file);
1049 if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) {
1050 // Load per game settings
1051 Config per_game_config(fmt::format("{:016X}.ini", title_id), false);
1052 }
1053
1054 Settings::LogSettings();
1055
1042 if (UISettings::values.select_user_on_boot) { 1056 if (UISettings::values.select_user_on_boot) {
1043 SelectAndSetCurrentUser(); 1057 SelectAndSetCurrentUser();
1044 } 1058 }
@@ -1063,6 +1077,7 @@ void GMainWindow::BootGame(const QString& filename) {
1063 &LoadingScreen::OnLoadProgress, Qt::QueuedConnection); 1077 &LoadingScreen::OnLoadProgress, Qt::QueuedConnection);
1064 1078
1065 // Update the GUI 1079 // Update the GUI
1080 UpdateStatusButtons();
1066 if (ui.action_Single_Window_Mode->isChecked()) { 1081 if (ui.action_Single_Window_Mode->isChecked()) {
1067 game_list->hide(); 1082 game_list->hide();
1068 game_list_placeholder->hide(); 1083 game_list_placeholder->hide();
@@ -1078,8 +1093,6 @@ void GMainWindow::BootGame(const QString& filename) {
1078 ui.centralwidget->setMouseTracking(true); 1093 ui.centralwidget->setMouseTracking(true);
1079 } 1094 }
1080 1095
1081 const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
1082
1083 std::string title_name; 1096 std::string title_name;
1084 std::string title_version; 1097 std::string title_version;
1085 const auto res = Core::System::GetInstance().GetGameName(title_name); 1098 const auto res = Core::System::GetInstance().GetGameName(title_name);
@@ -1521,7 +1534,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1521 return; 1534 return;
1522 } 1535 }
1523 1536
1524 ConfigurePerGameGeneral dialog(this, title_id); 1537 ConfigurePerGame dialog(this, title_id);
1525 dialog.LoadFromFile(v_file); 1538 dialog.LoadFromFile(v_file);
1526 auto result = dialog.exec(); 1539 auto result = dialog.exec();
1527 if (result == QDialog::Accepted) { 1540 if (result == QDialog::Accepted) {
@@ -1532,7 +1545,14 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1532 game_list->PopulateAsync(UISettings::values.game_dirs); 1545 game_list->PopulateAsync(UISettings::values.game_dirs);
1533 } 1546 }
1534 1547
1535 config->Save(); 1548 // Do not cause the global config to write local settings into the config file
1549 Settings::RestoreGlobalState();
1550
1551 if (!Core::System::GetInstance().IsPoweredOn()) {
1552 config->Save();
1553 }
1554 } else {
1555 Settings::RestoreGlobalState();
1536 } 1556 }
1537} 1557}
1538 1558
@@ -1819,6 +1839,9 @@ void GMainWindow::OnStopGame() {
1819 } 1839 }
1820 1840
1821 ShutdownGame(); 1841 ShutdownGame();
1842
1843 Settings::RestoreGlobalState();
1844 UpdateStatusButtons();
1822} 1845}
1823 1846
1824void GMainWindow::OnLoadComplete() { 1847void GMainWindow::OnLoadComplete() {
@@ -1926,7 +1949,7 @@ void GMainWindow::ToggleWindowMode() {
1926 1949
1927void GMainWindow::ResetWindowSize() { 1950void GMainWindow::ResetWindowSize() {
1928 const auto aspect_ratio = Layout::EmulationAspectRatio( 1951 const auto aspect_ratio = Layout::EmulationAspectRatio(
1929 static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio), 1952 static_cast<Layout::AspectRatio>(Settings::values.aspect_ratio.GetValue()),
1930 static_cast<float>(Layout::ScreenUndocked::Height) / Layout::ScreenUndocked::Width); 1953 static_cast<float>(Layout::ScreenUndocked::Height) / Layout::ScreenUndocked::Width);
1931 if (!ui.action_Single_Window_Mode->isChecked()) { 1954 if (!ui.action_Single_Window_Mode->isChecked()) {
1932 render_window->resize(Layout::ScreenUndocked::Height / aspect_ratio, 1955 render_window->resize(Layout::ScreenUndocked::Height / aspect_ratio,
@@ -1974,16 +1997,7 @@ void GMainWindow::OnConfigure() {
1974 ui.centralwidget->setMouseTracking(false); 1997 ui.centralwidget->setMouseTracking(false);
1975 } 1998 }
1976 1999
1977 dock_status_button->setChecked(Settings::values.use_docked_mode); 2000 UpdateStatusButtons();
1978 multicore_status_button->setChecked(Settings::values.use_multi_core);
1979 Settings::values.use_asynchronous_gpu_emulation =
1980 Settings::values.use_asynchronous_gpu_emulation || Settings::values.use_multi_core;
1981 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation);
1982
1983#ifdef HAS_VULKAN
1984 renderer_status_button->setChecked(Settings::values.renderer_backend ==
1985 Settings::RendererBackend::Vulkan);
1986#endif
1987} 2001}
1988 2002
1989void GMainWindow::OnLoadAmiibo() { 2003void GMainWindow::OnLoadAmiibo() {
@@ -2097,21 +2111,34 @@ void GMainWindow::UpdateStatusBar() {
2097 2111
2098 auto results = Core::System::GetInstance().GetAndResetPerfStats(); 2112 auto results = Core::System::GetInstance().GetAndResetPerfStats();
2099 2113
2100 if (Settings::values.use_frame_limit) { 2114 if (Settings::values.use_frame_limit.GetValue()) {
2101 emu_speed_label->setText(tr("Speed: %1% / %2%") 2115 emu_speed_label->setText(tr("Speed: %1% / %2%")
2102 .arg(results.emulation_speed * 100.0, 0, 'f', 0) 2116 .arg(results.emulation_speed * 100.0, 0, 'f', 0)
2103 .arg(Settings::values.frame_limit)); 2117 .arg(Settings::values.frame_limit.GetValue()));
2104 } else { 2118 } else {
2105 emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0)); 2119 emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
2106 } 2120 }
2107 game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0)); 2121 game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0));
2108 emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2)); 2122 emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
2109 2123
2110 emu_speed_label->setVisible(!Settings::values.use_multi_core); 2124 emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue());
2111 game_fps_label->setVisible(true); 2125 game_fps_label->setVisible(true);
2112 emu_frametime_label->setVisible(true); 2126 emu_frametime_label->setVisible(true);
2113} 2127}
2114 2128
2129void GMainWindow::UpdateStatusButtons() {
2130 dock_status_button->setChecked(Settings::values.use_docked_mode);
2131 multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
2132 Settings::values.use_asynchronous_gpu_emulation.SetValue(
2133 Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
2134 Settings::values.use_multi_core.GetValue());
2135 async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
2136#ifdef HAS_VULKAN
2137 renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
2138 Settings::RendererBackend::Vulkan);
2139#endif
2140}
2141
2115void GMainWindow::HideMouseCursor() { 2142void GMainWindow::HideMouseCursor() {
2116 if (emu_thread == nullptr || UISettings::values.hide_mouse == false) { 2143 if (emu_thread == nullptr || UISettings::values.hide_mouse == false) {
2117 mouse_hide_timer.stop(); 2144 mouse_hide_timer.stop();
@@ -2195,6 +2222,9 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
2195 if (answer == QMessageBox::Yes) { 2222 if (answer == QMessageBox::Yes) {
2196 if (emu_thread) { 2223 if (emu_thread) {
2197 ShutdownGame(); 2224 ShutdownGame();
2225
2226 Settings::RestoreGlobalState();
2227 UpdateStatusButtons();
2198 } 2228 }
2199 } else { 2229 } else {
2200 // Only show the message if the game is still running. 2230 // Only show the message if the game is still running.
@@ -2357,9 +2387,13 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
2357 hotkey_registry.SaveHotkeys(); 2387 hotkey_registry.SaveHotkeys();
2358 2388
2359 // Shutdown session if the emu thread is active... 2389 // Shutdown session if the emu thread is active...
2360 if (emu_thread != nullptr) 2390 if (emu_thread != nullptr) {
2361 ShutdownGame(); 2391 ShutdownGame();
2362 2392
2393 Settings::RestoreGlobalState();
2394 UpdateStatusButtons();
2395 }
2396
2363 render_window->close(); 2397 render_window->close();
2364 2398
2365 QWidget::closeEvent(event); 2399 QWidget::closeEvent(event);
@@ -2539,8 +2573,6 @@ int main(int argc, char* argv[]) {
2539 QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window, 2573 QObject::connect(&app, &QGuiApplication::applicationStateChanged, &main_window,
2540 &GMainWindow::OnAppFocusStateChanged); 2574 &GMainWindow::OnAppFocusStateChanged);
2541 2575
2542 Settings::LogSettings();
2543
2544 int result = app.exec(); 2576 int result = app.exec();
2545 detached_tasks.WaitForAllTasks(); 2577 detached_tasks.WaitForAllTasks();
2546 return result; 2578 return result;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 66c84e5c0..8e3d39c38 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -221,6 +221,7 @@ private:
221 void UpdateWindowTitle(const std::string& title_name = {}, 221 void UpdateWindowTitle(const std::string& title_name = {},
222 const std::string& title_version = {}); 222 const std::string& title_version = {});
223 void UpdateStatusBar(); 223 void UpdateStatusBar();
224 void UpdateStatusButtons();
224 void HideMouseCursor(); 225 void HideMouseCursor();
225 void ShowMouseCursor(); 226 void ShowMouseCursor();
226 void OpenURL(const QUrl& url); 227 void OpenURL(const QUrl& url);
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 659b9f701..23763144f 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -354,63 +354,72 @@ void Config::ReadValues() {
354 354
355 const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false); 355 const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
356 if (rng_seed_enabled) { 356 if (rng_seed_enabled) {
357 Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0); 357 Settings::values.rng_seed.SetValue(sdl2_config->GetInteger("System", "rng_seed", 0));
358 } else { 358 } else {
359 Settings::values.rng_seed = std::nullopt; 359 Settings::values.rng_seed.SetValue(std::nullopt);
360 } 360 }
361 361
362 const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); 362 const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false);
363 if (custom_rtc_enabled) { 363 if (custom_rtc_enabled) {
364 Settings::values.custom_rtc = 364 Settings::values.custom_rtc.SetValue(
365 std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)); 365 std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)));
366 } else { 366 } else {
367 Settings::values.custom_rtc = std::nullopt; 367 Settings::values.custom_rtc.SetValue(std::nullopt);
368 } 368 }
369 369
370 Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); 370 Settings::values.language_index.SetValue(
371 Settings::values.time_zone_index = sdl2_config->GetInteger("System", "time_zone_index", 0); 371 sdl2_config->GetInteger("System", "language_index", 1));
372 Settings::values.time_zone_index.SetValue(
373 sdl2_config->GetInteger("System", "time_zone_index", 0));
372 374
373 // Core 375 // Core
374 Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); 376 Settings::values.use_multi_core.SetValue(
377 sdl2_config->GetBoolean("Core", "use_multi_core", false));
375 378
376 // Renderer 379 // Renderer
377 const int renderer_backend = sdl2_config->GetInteger( 380 const int renderer_backend = sdl2_config->GetInteger(
378 "Renderer", "backend", static_cast<int>(Settings::RendererBackend::OpenGL)); 381 "Renderer", "backend", static_cast<int>(Settings::RendererBackend::OpenGL));
379 Settings::values.renderer_backend = static_cast<Settings::RendererBackend>(renderer_backend); 382 Settings::values.renderer_backend.SetValue(
383 static_cast<Settings::RendererBackend>(renderer_backend));
380 Settings::values.renderer_debug = sdl2_config->GetBoolean("Renderer", "debug", false); 384 Settings::values.renderer_debug = sdl2_config->GetBoolean("Renderer", "debug", false);
381 Settings::values.vulkan_device = sdl2_config->GetInteger("Renderer", "vulkan_device", 0); 385 Settings::values.vulkan_device.SetValue(
382 386 sdl2_config->GetInteger("Renderer", "vulkan_device", 0));
383 Settings::values.aspect_ratio = 387
384 static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0)); 388 Settings::values.aspect_ratio.SetValue(
385 Settings::values.max_anisotropy = 389 static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0)));
386 static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0)); 390 Settings::values.max_anisotropy.SetValue(
387 Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); 391 static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0)));
388 Settings::values.frame_limit = 392 Settings::values.use_frame_limit.SetValue(
389 static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); 393 sdl2_config->GetBoolean("Renderer", "use_frame_limit", true));
390 Settings::values.use_disk_shader_cache = 394 Settings::values.frame_limit.SetValue(
391 sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false); 395 static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)));
396 Settings::values.use_disk_shader_cache.SetValue(
397 sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false));
392 const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0); 398 const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0);
393 Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(gpu_accuracy_level); 399 Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level));
394 Settings::values.use_asynchronous_gpu_emulation = 400 Settings::values.use_asynchronous_gpu_emulation.SetValue(
395 sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false); 401 sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false));
396 Settings::values.use_vsync = 402 Settings::values.use_vsync.SetValue(
397 static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1)); 403 static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1)));
398 Settings::values.use_assembly_shaders = 404 Settings::values.use_assembly_shaders.SetValue(
399 sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", false); 405 sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", false));
400 Settings::values.use_fast_gpu_time = 406 Settings::values.use_fast_gpu_time.SetValue(
401 sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true); 407 sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true));
402 408
403 Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); 409 Settings::values.bg_red.SetValue(
404 Settings::values.bg_green = 410 static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)));
405 static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)); 411 Settings::values.bg_green.SetValue(
406 Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)); 412 static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)));
413 Settings::values.bg_blue.SetValue(
414 static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)));
407 415
408 // Audio 416 // Audio
409 Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); 417 Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
410 Settings::values.enable_audio_stretching = 418 Settings::values.enable_audio_stretching.SetValue(
411 sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); 419 sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true));
412 Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); 420 Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
413 Settings::values.volume = static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1)); 421 Settings::values.volume.SetValue(
422 static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1)));
414 423
415 // Miscellaneous 424 // Miscellaneous
416 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); 425 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index 09cc0a3b5..e78025737 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -165,7 +165,7 @@ std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateShared
165 165
166void EmuWindow_SDL2_GL::Present() { 166void EmuWindow_SDL2_GL::Present() {
167 SDL_GL_MakeCurrent(render_window, window_context); 167 SDL_GL_MakeCurrent(render_window, window_context);
168 SDL_GL_SetSwapInterval(Settings::values.use_vsync ? 1 : 0); 168 SDL_GL_SetSwapInterval(Settings::values.use_vsync.GetValue() ? 1 : 0);
169 while (IsOpen()) { 169 while (IsOpen()) {
170 system.Renderer().TryPresent(100); 170 system.Renderer().TryPresent(100);
171 SDL_GL_SwapWindow(render_window); 171 SDL_GL_SwapWindow(render_window);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index e6c6a839d..512b060a7 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -181,7 +181,7 @@ int main(int argc, char** argv) {
181 Core::System& system{Core::System::GetInstance()}; 181 Core::System& system{Core::System::GetInstance()};
182 182
183 std::unique_ptr<EmuWindow_SDL2> emu_window; 183 std::unique_ptr<EmuWindow_SDL2> emu_window;
184 switch (Settings::values.renderer_backend) { 184 switch (Settings::values.renderer_backend.GetValue()) {
185 case Settings::RendererBackend::OpenGL: 185 case Settings::RendererBackend::OpenGL:
186 emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen); 186 emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen);
187 break; 187 break;
diff --git a/src/yuzu_tester/config.cpp b/src/yuzu_tester/config.cpp
index 1566c2e3f..acb22885e 100644
--- a/src/yuzu_tester/config.cpp
+++ b/src/yuzu_tester/config.cpp
@@ -81,6 +81,9 @@ void Config::ReadValues() {
81 Settings::values.touchscreen.diameter_x = 15; 81 Settings::values.touchscreen.diameter_x = 15;
82 Settings::values.touchscreen.diameter_y = 15; 82 Settings::values.touchscreen.diameter_y = 15;
83 83
84 Settings::values.use_docked_mode =
85 sdl2_config->GetBoolean("Controls", "use_docked_mode", false);
86
84 // Data Storage 87 // Data Storage
85 Settings::values.use_virtual_sd = 88 Settings::values.use_virtual_sd =
86 sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); 89 sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
@@ -92,57 +95,59 @@ void Config::ReadValues() {
92 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); 95 FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
93 96
94 // System 97 // System
95 Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
96
97 Settings::values.current_user = std::clamp<int>( 98 Settings::values.current_user = std::clamp<int>(
98 sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); 99 sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);
99 100
100 const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false); 101 const auto rng_seed_enabled = sdl2_config->GetBoolean("System", "rng_seed_enabled", false);
101 if (rng_seed_enabled) { 102 if (rng_seed_enabled) {
102 Settings::values.rng_seed = sdl2_config->GetInteger("System", "rng_seed", 0); 103 Settings::values.rng_seed.SetValue(sdl2_config->GetInteger("System", "rng_seed", 0));
103 } else { 104 } else {
104 Settings::values.rng_seed = std::nullopt; 105 Settings::values.rng_seed.SetValue(std::nullopt);
105 } 106 }
106 107
107 const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false); 108 const auto custom_rtc_enabled = sdl2_config->GetBoolean("System", "custom_rtc_enabled", false);
108 if (custom_rtc_enabled) { 109 if (custom_rtc_enabled) {
109 Settings::values.custom_rtc = 110 Settings::values.custom_rtc.SetValue(
110 std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)); 111 std::chrono::seconds(sdl2_config->GetInteger("System", "custom_rtc", 0)));
111 } else { 112 } else {
112 Settings::values.custom_rtc = std::nullopt; 113 Settings::values.custom_rtc.SetValue(std::nullopt);
113 } 114 }
114 115
115 // Core 116 // Core
116 Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); 117 Settings::values.use_multi_core.SetValue(
118 sdl2_config->GetBoolean("Core", "use_multi_core", false));
117 119
118 // Renderer 120 // Renderer
119 Settings::values.aspect_ratio = 121 Settings::values.aspect_ratio.SetValue(
120 static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0)); 122 static_cast<int>(sdl2_config->GetInteger("Renderer", "aspect_ratio", 0)));
121 Settings::values.max_anisotropy = 123 Settings::values.max_anisotropy.SetValue(
122 static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0)); 124 static_cast<int>(sdl2_config->GetInteger("Renderer", "max_anisotropy", 0)));
123 Settings::values.use_frame_limit = false; 125 Settings::values.use_frame_limit.SetValue(false);
124 Settings::values.frame_limit = 100; 126 Settings::values.frame_limit.SetValue(100);
125 Settings::values.use_disk_shader_cache = 127 Settings::values.use_disk_shader_cache.SetValue(
126 sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false); 128 sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", false));
127 const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0); 129 const int gpu_accuracy_level = sdl2_config->GetInteger("Renderer", "gpu_accuracy", 0);
128 Settings::values.gpu_accuracy = static_cast<Settings::GPUAccuracy>(gpu_accuracy_level); 130 Settings::values.gpu_accuracy.SetValue(static_cast<Settings::GPUAccuracy>(gpu_accuracy_level));
129 Settings::values.use_asynchronous_gpu_emulation = 131 Settings::values.use_asynchronous_gpu_emulation.SetValue(
130 sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false); 132 sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false));
131 Settings::values.use_fast_gpu_time = 133 Settings::values.use_fast_gpu_time.SetValue(
132 sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true); 134 sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true));
133 135
134 Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); 136 Settings::values.bg_red.SetValue(
135 Settings::values.bg_green = 137 static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)));
136 static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)); 138 Settings::values.bg_green.SetValue(
137 Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)); 139 static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0)));
140 Settings::values.bg_blue.SetValue(
141 static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0)));
138 142
139 // Audio 143 // Audio
140 Settings::values.sink_id = "null"; 144 Settings::values.sink_id = "null";
141 Settings::values.enable_audio_stretching = false; 145 Settings::values.enable_audio_stretching.SetValue(false);
142 Settings::values.audio_device_id = "auto"; 146 Settings::values.audio_device_id = "auto";
143 Settings::values.volume = 0; 147 Settings::values.volume.SetValue(0);
144 148
145 Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); 149 Settings::values.language_index.SetValue(
150 sdl2_config->GetInteger("System", "language_index", 1));
146 151
147 // Miscellaneous 152 // Miscellaneous
148 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); 153 Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");