diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.java b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.java
index 9664f8464..a24749083 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.java
+++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.java
@@ -8,7 +8,7 @@ import org.citra.citra_emu.CitraApplication;
 public class EmulationMenuSettings {
     private static SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(CitraApplication.getAppContext());
 
-    // These must match what is defined in src/core/settings.h
+    // These must match what is defined in src/common/settings.h
     public static final int LayoutOption_Default = 0;
     public static final int LayoutOption_SingleScreen = 1;
     public static final int LayoutOption_LargeScreen = 2;
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 746c26e09..e511b438e 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -11,10 +11,10 @@
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "common/param_package.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/hle/service/cfg/cfg.h"
 #include "core/hle/service/service.h"
-#include "core/settings.h"
 #include "input_common/main.h"
 #include "input_common/udp/client.h"
 #include "jni/camera/ndk_camera.h"
@@ -139,9 +139,9 @@ void Config::ReadValues() {
     Settings::values.factor_3d =
         static_cast<u8>(sdl2_config->GetInteger("Renderer", "factor_3d", 0));
     std::string default_shader = "none (builtin)";
-    if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph)
         default_shader = "dubois (builtin)";
-    else if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced)
+    else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced)
         default_shader = "horizontal (builtin)";
     Settings::values.pp_shader_name =
         sdl2_config->GetString("Renderer", "pp_shader_name", default_shader);
@@ -186,9 +186,9 @@ void Config::ReadValues() {
         sdl2_config->GetBoolean("Utility", "preload_textures", false);
 
     // Audio
-    Settings::values.enable_dsp_lle = sdl2_config->GetBoolean("Audio", "enable_dsp_lle", false);
-    Settings::values.enable_dsp_lle_multithread =
-        sdl2_config->GetBoolean("Audio", "enable_dsp_lle_multithread", false);
+    Settings::values.audio_emulation =
+        static_cast<Settings::AudioEmulation>(sdl2_config->GetInteger(
+            "Audio", "audio_emulation", static_cast<int>(Settings::AudioEmulation::HLE)));
     Settings::values.sink_id = sdl2_config->GetString("Audio", "output_engine", "auto");
     Settings::values.enable_audio_stretching =
         sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
diff --git a/src/android/app/src/main/jni/emu_window/emu_window.cpp b/src/android/app/src/main/jni/emu_window/emu_window.cpp
index 86faef554..c2cc91ef5 100644
--- a/src/android/app/src/main/jni/emu_window/emu_window.cpp
+++ b/src/android/app/src/main/jni/emu_window/emu_window.cpp
@@ -11,7 +11,7 @@
 #include <glad/glad.h>
 
 #include "common/logging/log.h"
-#include "core/settings.h"
+#include "common/settings.h"
 #include "input_common/main.h"
 #include "jni/emu_window/emu_window.h"
 #include "jni/id_cache.h"
diff --git a/src/android/app/src/main/jni/game_settings.cpp b/src/android/app/src/main/jni/game_settings.cpp
index 3feb42704..d8382b0e1 100644
--- a/src/android/app/src/main/jni/game_settings.cpp
+++ b/src/android/app/src/main/jni/game_settings.cpp
@@ -2,7 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include "core/settings.h"
+#include "common/settings.h"
 
 namespace GameSettings {
 
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index 60c091912..b1b372923 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -6,7 +6,7 @@
 #include "common/logging/backend.h"
 #include "common/logging/filter.h"
 #include "common/logging/log.h"
-#include "core/settings.h"
+#include "common/settings.h"
 #include "jni/applets/mii_selector.h"
 #include "jni/applets/swkbd.h"
 #include "jni/camera/still_image_camera.h"
@@ -156,7 +156,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
 
     // Initialize Logger
     Log::Filter log_filter;
-    log_filter.ParseFilterString(Settings::values.log_filter);
+    log_filter.ParseFilterString(Settings::values.log_filter.GetValue());
     Log::SetGlobalFilter(log_filter);
     Log::AddBackend(std::make_unique<Log::LogcatBackend>());
     FileUtil::CreateFullPath(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 04c38305e..78907ea4a 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -17,6 +17,7 @@
 #include "common/microprofile.h"
 #include "common/scm_rev.h"
 #include "common/scope_exit.h"
+#include "common/settings.h"
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/frontend/applets/default_applets.h"
@@ -26,7 +27,6 @@
 #include "core/hle/service/am/am.h"
 #include "core/hle/service/nfc/nfc.h"
 #include "core/savestate.h"
-#include "core/settings.h"
 #include "jni/android_common/android_common.h"
 #include "jni/applets/mii_selector.h"
 #include "jni/applets/swkbd.h"
@@ -238,7 +238,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
             }
         } else {
             // Ensure no audio bleeds out while game is paused
-            const float volume = Settings::values.volume;
+            const float volume = Settings::values.volume.GetValue();
             SCOPE_EXIT({ Settings::values.volume = volume; });
             Settings::values.volume = 0;
 
diff --git a/src/audio_core/dsp_interface.cpp b/src/audio_core/dsp_interface.cpp
index 74ab37f54..560ed7a3c 100644
--- a/src/audio_core/dsp_interface.cpp
+++ b/src/audio_core/dsp_interface.cpp
@@ -7,17 +7,18 @@
 #include "audio_core/sink.h"
 #include "audio_core/sink_details.h"
 #include "common/assert.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/dumping/backend.h"
-#include "core/settings.h"
 
 namespace AudioCore {
 
 DspInterface::DspInterface() = default;
 DspInterface::~DspInterface() = default;
 
-void DspInterface::SetSink(const std::string& sink_id, const std::string& audio_device) {
-    sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id);
+void DspInterface::SetSink(std::string_view sink_id, std::string_view audio_device) {
+    sink = CreateSinkFromID(Settings::values.sink_id.GetValue(),
+                            Settings::values.audio_device_id.GetValue());
     sink->SetCallback(
         [this](s16* buffer, std::size_t num_frames) { OutputCallback(buffer, num_frames); });
     time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate());
diff --git a/src/audio_core/dsp_interface.h b/src/audio_core/dsp_interface.h
index 066499a41..ba951b5d1 100644
--- a/src/audio_core/dsp_interface.h
+++ b/src/audio_core/dsp_interface.h
@@ -94,7 +94,7 @@ public:
     virtual void UnloadComponent() = 0;
 
     /// Select the sink to use based on sink id.
-    void SetSink(const std::string& sink_id, const std::string& audio_device);
+    void SetSink(std::string_view sink_id, std::string_view audio_device);
     /// Get the current sink
     Sink& GetSink();
     /// Enable/Disable audio stretching.
diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp
index 217a3eb1d..acd3092cc 100644
--- a/src/citra/citra.cpp
+++ b/src/citra/citra.cpp
@@ -22,6 +22,7 @@
 #include "common/logging/log.h"
 #include "common/scm_rev.h"
 #include "common/scope_exit.h"
+#include "common/settings.h"
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/dumping/backend.h"
@@ -34,7 +35,6 @@
 #include "core/hle/service/cfg/cfg.h"
 #include "core/loader/loader.h"
 #include "core/movie.h"
-#include "core/settings.h"
 #include "input_common/main.h"
 #include "network/network.h"
 #include "video_core/renderer_base.h"
@@ -174,7 +174,7 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
 
 static void InitializeLogging() {
     Log::Filter log_filter(Log::Level::Debug);
-    log_filter.ParseFilterString(Settings::values.log_filter);
+    log_filter.ParseFilterString(Settings::values.log_filter.GetValue());
     Log::SetGlobalFilter(log_filter);
 
     Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
@@ -192,8 +192,8 @@ int main(int argc, char** argv) {
     Common::DetachedTasks detached_tasks;
     Config config;
     int option_index = 0;
-    bool use_gdbstub = Settings::values.use_gdbstub;
-    u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
+    bool use_gdbstub = Settings::values.use_gdbstub.GetValue();
+    u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port.GetValue());
     std::string movie_record;
     std::string movie_record_author;
     std::string movie_play;
@@ -363,7 +363,7 @@ int main(int argc, char** argv) {
     EmuWindow_SDL2::InitializeSDL2();
 
     const auto emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen, false)};
-    const bool use_secondary_window{Settings::values.layout_option ==
+    const bool use_secondary_window{Settings::values.layout_option.GetValue() ==
                                     Settings::LayoutOption::SeparateWindows};
     const auto secondary_window =
         use_secondary_window ? std::make_unique<EmuWindow_SDL2>(false, true) : nullptr;
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index eaa5aeb5a..fb8559c8f 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -13,9 +13,9 @@
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "common/param_package.h"
+#include "common/settings.h"
 #include "core/frontend/mic.h"
 #include "core/hle/service/service.h"
-#include "core/settings.h"
 #include "input_common/main.h"
 #include "input_common/udp/client.h"
 #include "network/network_settings.h"
@@ -128,10 +128,6 @@ void Config::ReadValues() {
         sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", true);
     Settings::values.frame_limit =
         static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
-    Settings::values.use_frame_limit_alternate =
-        sdl2_config->GetBoolean("Renderer", "use_frame_limit_alternate", false);
-    Settings::values.frame_limit_alternate =
-        static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit_alternate", 200));
     Settings::values.use_vsync_new =
         static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync_new", 1));
     Settings::values.texture_filter_name =
@@ -144,10 +140,11 @@ void Config::ReadValues() {
     Settings::values.factor_3d =
         static_cast<u8>(sdl2_config->GetInteger("Renderer", "factor_3d", 0));
     std::string default_shader = "none (builtin)";
-    if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph)
         default_shader = "dubois (builtin)";
-    else if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced ||
-             Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced)
+    else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
+             Settings::values.render_3d.GetValue() ==
+                 Settings::StereoRenderOption::ReverseInterlaced)
         default_shader = "horizontal (builtin)";
     Settings::values.pp_shader_name =
         sdl2_config->GetString("Renderer", "pp_shader_name", default_shader);
@@ -188,9 +185,8 @@ void Config::ReadValues() {
         sdl2_config->GetBoolean("Utility", "preload_textures", false);
 
     // Audio
-    Settings::values.enable_dsp_lle = sdl2_config->GetBoolean("Audio", "enable_dsp_lle", false);
-    Settings::values.enable_dsp_lle_multithread =
-        sdl2_config->GetBoolean("Audio", "enable_dsp_lle_multithread", false);
+    Settings::values.audio_emulation = static_cast<Settings::AudioEmulation>(
+        sdl2_config->GetInteger("Audio", "audio_emulation", 0));
     Settings::values.sink_id = sdl2_config->GetString("Audio", "output_engine", "auto");
     Settings::values.enable_audio_stretching =
         sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index 461638156..9e7b7924f 100644
--- a/src/citra/emu_window/emu_window_sdl2.cpp
+++ b/src/citra/emu_window/emu_window_sdl2.cpp
@@ -12,9 +12,9 @@
 #include "citra/emu_window/emu_window_sdl2.h"
 #include "common/logging/log.h"
 #include "common/scm_rev.h"
+#include "common/settings.h"
 #include "core/3ds.h"
 #include "core/core.h"
-#include "core/settings.h"
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
 #include "input_common/motion_emu.h"
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 413c3a078..29f8292d8 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -38,6 +38,8 @@ add_executable(citra-qt
     configuration/config.cpp
     configuration/config.h
     configuration/configure.ui
+    configuration/configuration_shared.cpp
+    configuration/configuration_shared.h
     configuration/configure_audio.cpp
     configuration/configure_audio.h
     configuration/configure_audio.ui
@@ -67,6 +69,9 @@ add_executable(citra-qt
     configuration/configure_motion_touch.cpp
     configuration/configure_motion_touch.h
     configuration/configure_motion_touch.ui
+    configuration/configure_per_game.cpp
+    configuration/configure_per_game.h
+    configuration/configure_per_game.ui
     configuration/configure_storage.cpp
     configuration/configure_storage.h
     configuration/configure_storage.ui
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index e09c4d4d5..ef89f890b 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -15,11 +15,11 @@
 #include "citra_qt/main.h"
 #include "common/microprofile.h"
 #include "common/scm_rev.h"
+#include "common/settings.h"
 #include "core/3ds.h"
 #include "core/core.h"
 #include "core/frontend/scope_acquire_context.h"
 #include "core/perf_stats.h"
-#include "core/settings.h"
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
 #include "input_common/motion_emu.h"
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index a453341a7..7b8323593 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -17,17 +17,15 @@
 #include "network/network.h"
 #include "network/network_settings.h"
 
-Config::Config() {
-    // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
-    qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini";
-    FileUtil::CreateFullPath(qt_config_loc);
-    qt_config =
-        std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
-    Reload();
+Config::Config(const std::string& config_name, ConfigType config_type) : type{config_type} {
+    global = config_type == ConfigType::GlobalConfig;
+    Initialize(config_name);
 }
 
 Config::~Config() {
-    Save();
+    if (global) {
+        Save();
+    }
 }
 
 const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
@@ -85,46 +83,176 @@ const std::array<UISettings::Shortcut, 24> default_hotkeys{
      {QStringLiteral("Toggle Texture Dumping"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+D"), Qt::ApplicationShortcut}}}};
 // clang-format on
 
+void Config::Initialize(const std::string& config_name) {
+    const std::string fs_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir);
+    const std::string config_file = fmt::format("{}.ini", config_name);
+
+    switch (type) {
+    case ConfigType::GlobalConfig:
+        qt_config_loc = fmt::format("{}/{}", fs_config_loc, config_file);
+        break;
+    case ConfigType::PerGameConfig:
+        qt_config_loc = fmt::format("{}/custom/{}", fs_config_loc, config_file);
+        break;
+    }
+
+    FileUtil::CreateFullPath(qt_config_loc);
+    qt_config =
+        std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
+    Reload();
+}
+
+/* {Read,Write}BasicSetting and WriteGlobalSetting templates must be defined here before their
+ * usages later in this file. This allows explicit definition of some types that don't work
+ * nicely with the general version.
+ */
+
+// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant, nor
+// can it implicitly convert a QVariant back to a {std::,Q}string
+template <>
+void Config::ReadBasicSetting(Settings::Setting<std::string>& setting) {
+    const QString name = QString::fromStdString(setting.GetLabel());
+    const auto default_value = QString::fromStdString(setting.GetDefault());
+    if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
+        setting.SetValue(default_value.toStdString());
+    } else {
+        setting.SetValue(qt_config->value(name, default_value).toString().toStdString());
+    }
+}
+
+template <typename Type, bool ranged>
+void Config::ReadBasicSetting(Settings::Setting<Type, ranged>& setting) {
+    const QString name = QString::fromStdString(setting.GetLabel());
+    const Type default_value = setting.GetDefault();
+    if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
+        setting.SetValue(default_value);
+    } else {
+        QVariant value{};
+        if constexpr (std::is_enum_v<Type>) {
+            using TypeU = std::underlying_type_t<Type>;
+            value = qt_config->value(name, static_cast<TypeU>(default_value));
+            setting.SetValue(static_cast<Type>(value.value<TypeU>()));
+        } else {
+            value = qt_config->value(name, QVariant::fromValue(default_value));
+            setting.SetValue(value.value<Type>());
+        }
+    }
+}
+
+template <typename Type, bool ranged>
+void Config::ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting) {
+    QString name = QString::fromStdString(setting.GetLabel());
+    const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
+    setting.SetGlobal(use_global);
+    if (global || !use_global) {
+        QVariant value{};
+        if constexpr (std::is_enum_v<Type>) {
+            using TypeU = std::underlying_type_t<Type>;
+            value = QVariant::fromValue<TypeU>(static_cast<TypeU>(setting.GetDefault()));
+            setting.SetValue(static_cast<Type>(ReadSetting(name, value).value<TypeU>()));
+        } else {
+            value = QVariant::fromValue<Type>(setting.GetDefault());
+            setting.SetValue(ReadSetting(name, value).value<Type>());
+        }
+    }
+}
+
+template <>
+void Config::ReadGlobalSetting(Settings::SwitchableSetting<std::string>& setting) {
+    QString name = QString::fromStdString(setting.GetLabel());
+    const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
+    setting.SetGlobal(use_global);
+    if (global || !use_global) {
+        const QString default_value = QString::fromStdString(setting.GetDefault());
+        setting.SetValue(
+            ReadSetting(name, QVariant::fromValue(default_value)).toString().toStdString());
+    }
+}
+
+// Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant
+template <>
+void Config::WriteBasicSetting(const Settings::Setting<std::string>& setting) {
+    const QString name = QString::fromStdString(setting.GetLabel());
+    const std::string& value = setting.GetValue();
+    qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
+    qt_config->setValue(name, QString::fromStdString(value));
+}
+
+template <typename Type, bool ranged>
+void Config::WriteBasicSetting(const Settings::Setting<Type, ranged>& setting) {
+    const QString name = QString::fromStdString(setting.GetLabel());
+    const Type value = setting.GetValue();
+    qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
+    if constexpr (std::is_enum_v<Type>) {
+        qt_config->setValue(name, static_cast<std::underlying_type_t<Type>>(value));
+    } else {
+        qt_config->setValue(name, QVariant::fromValue(value));
+    }
+}
+
+template <typename Type, bool ranged>
+void Config::WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting) {
+    const QString name = QString::fromStdString(setting.GetLabel());
+    const Type& value = setting.GetValue(global);
+    if (!global) {
+        qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
+    }
+    if (global || !setting.UsingGlobal()) {
+        qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
+        if constexpr (std::is_enum_v<Type>) {
+            qt_config->setValue(name, static_cast<std::underlying_type_t<Type>>(value));
+        } else {
+            qt_config->setValue(name, QVariant::fromValue(value));
+        }
+    }
+}
+
+template <>
+void Config::WriteGlobalSetting(const Settings::SwitchableSetting<std::string>& setting) {
+    const QString name = QString::fromStdString(setting.GetLabel());
+    const std::string& value = setting.GetValue(global);
+    if (!global) {
+        qt_config->setValue(name + QStringLiteral("/use_global"), setting.UsingGlobal());
+    }
+    if (global || !setting.UsingGlobal()) {
+        qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault());
+        qt_config->setValue(name, QString::fromStdString(value));
+    }
+}
+
 void Config::ReadValues() {
-    ReadControlValues();
+    if (global) {
+        ReadControlValues();
+        ReadCameraValues();
+        ReadDataStorageValues();
+        ReadMiscellaneousValues();
+        ReadDebuggingValues();
+        ReadWebServiceValues();
+        ReadVideoDumpingValues();
+        ReadUtilityValues();
+    }
+
+    ReadUIValues();
     ReadCoreValues();
     ReadRendererValues();
     ReadLayoutValues();
     ReadAudioValues();
-    ReadCameraValues();
-    ReadDataStorageValues();
     ReadSystemValues();
-    ReadMiscellaneousValues();
-    ReadDebuggingValues();
-    ReadWebServiceValues();
-    ReadVideoDumpingValues();
-    ReadUIValues();
-    ReadUtilityValues();
 }
 
 void Config::ReadAudioValues() {
     qt_config->beginGroup(QStringLiteral("Audio"));
 
-    Settings::values.enable_dsp_lle = ReadSetting(QStringLiteral("enable_dsp_lle"), false).toBool();
-    Settings::values.enable_dsp_lle_multithread =
-        ReadSetting(QStringLiteral("enable_dsp_lle_multithread"), false).toBool();
-    Settings::values.sink_id = ReadSetting(QStringLiteral("output_engine"), QStringLiteral("auto"))
-                                   .toString()
-                                   .toStdString();
-    Settings::values.enable_audio_stretching =
-        ReadSetting(QStringLiteral("enable_audio_stretching"), true).toBool();
-    Settings::values.audio_device_id =
-        ReadSetting(QStringLiteral("output_device"), QStringLiteral("auto"))
-            .toString()
-            .toStdString();
-    Settings::values.volume = ReadSetting(QStringLiteral("volume"), 1).toFloat();
-    Settings::values.mic_input_type = static_cast<Settings::MicInputType>(
-        ReadSetting(QStringLiteral("mic_input_type"), 0).toInt());
-    Settings::values.mic_input_device =
-        ReadSetting(QStringLiteral("mic_input_device"),
-                    QString::fromUtf8(Frontend::Mic::default_device_name))
-            .toString()
-            .toStdString();
+    ReadGlobalSetting(Settings::values.audio_emulation);
+    ReadGlobalSetting(Settings::values.enable_audio_stretching);
+    ReadGlobalSetting(Settings::values.volume);
+
+    if (global) {
+        ReadBasicSetting(Settings::values.sink_id);
+        ReadBasicSetting(Settings::values.audio_device_id);
+        ReadBasicSetting(Settings::values.mic_input_device);
+        ReadBasicSetting(Settings::values.mic_input_type);
+    }
 
     qt_config->endGroup();
 }
@@ -281,11 +409,9 @@ void Config::ReadControlValues() {
 void Config::ReadUtilityValues() {
     qt_config->beginGroup(QStringLiteral("Utility"));
 
-    Settings::values.dump_textures = ReadSetting(QStringLiteral("dump_textures"), false).toBool();
-    Settings::values.custom_textures =
-        ReadSetting(QStringLiteral("custom_textures"), false).toBool();
-    Settings::values.preload_textures =
-        ReadSetting(QStringLiteral("preload_textures"), false).toBool();
+    ReadBasicSetting(Settings::values.dump_textures);
+    ReadBasicSetting(Settings::values.custom_textures);
+    ReadBasicSetting(Settings::values.preload_textures);
 
     qt_config->endGroup();
 }
@@ -293,9 +419,11 @@ void Config::ReadUtilityValues() {
 void Config::ReadCoreValues() {
     qt_config->beginGroup(QStringLiteral("Core"));
 
-    Settings::values.use_cpu_jit = ReadSetting(QStringLiteral("use_cpu_jit"), true).toBool();
-    Settings::values.cpu_clock_percentage =
-        ReadSetting(QStringLiteral("cpu_clock_percentage"), 100).toInt();
+    ReadGlobalSetting(Settings::values.cpu_clock_percentage);
+
+    if (global) {
+        ReadBasicSetting(Settings::values.use_cpu_jit);
+    }
 
     qt_config->endGroup();
 }
@@ -303,10 +431,9 @@ void Config::ReadCoreValues() {
 void Config::ReadDataStorageValues() {
     qt_config->beginGroup(QStringLiteral("Data Storage"));
 
-    Settings::values.use_virtual_sd = ReadSetting(QStringLiteral("use_virtual_sd"), true).toBool();
+    ReadBasicSetting(Settings::values.use_virtual_sd);
+    ReadBasicSetting(Settings::values.use_custom_storage);
 
-    Settings::values.use_custom_storage =
-        ReadSetting(QStringLiteral("use_custom_storage"), false).toBool();
     const std::string nand_dir =
         ReadSetting(QStringLiteral("nand_directory"), QStringLiteral("")).toString().toStdString();
     const std::string sdmc_dir =
@@ -326,8 +453,8 @@ void Config::ReadDebuggingValues() {
     // Intentionally not using the QT default setting as this is intended to be changed in the ini
     Settings::values.record_frame_times =
         qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
-    Settings::values.use_gdbstub = ReadSetting(QStringLiteral("use_gdbstub"), false).toBool();
-    Settings::values.gdbstub_port = ReadSetting(QStringLiteral("gdbstub_port"), 24689).toInt();
+    ReadBasicSetting(Settings::values.use_gdbstub);
+    ReadBasicSetting(Settings::values.gdbstub_port);
 
     qt_config->beginGroup(QStringLiteral("LLE"));
     for (const auto& service_module : Service::service_module_map) {
@@ -335,45 +462,38 @@ void Config::ReadDebuggingValues() {
         Settings::values.lle_modules.emplace(service_module.name, use_lle);
     }
     qt_config->endGroup();
-
     qt_config->endGroup();
 }
 
 void Config::ReadLayoutValues() {
     qt_config->beginGroup(QStringLiteral("Layout"));
 
-    Settings::values.render_3d = static_cast<Settings::StereoRenderOption>(
-        ReadSetting(QStringLiteral("render_3d"), 0).toInt());
-    Settings::values.factor_3d = ReadSetting(QStringLiteral("factor_3d"), 0).toInt();
-    Settings::values.mono_render_left_eye =
-        ReadSetting(QStringLiteral("mono_render_left_eye"), true).toBool();
+    ReadGlobalSetting(Settings::values.render_3d);
+    ReadGlobalSetting(Settings::values.factor_3d);
     Settings::values.pp_shader_name =
-        ReadSetting(QStringLiteral("pp_shader_name"),
-                    (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
-                        ? QStringLiteral("dubois (builtin)")
-                        : QStringLiteral("none (builtin)"))
+        ReadSetting(QStringLiteral("pp_shader_name"), (Settings::values.render_3d.GetValue() ==
+                                                       Settings::StereoRenderOption::Anaglyph)
+                                                          ? QStringLiteral("dubois (builtin)")
+                                                          : QStringLiteral("none (builtin)"))
             .toString()
             .toStdString();
-    Settings::values.filter_mode = ReadSetting(QStringLiteral("filter_mode"), true).toBool();
-    Settings::values.layout_option =
-        static_cast<Settings::LayoutOption>(ReadSetting(QStringLiteral("layout_option")).toInt());
-    Settings::values.swap_screen = ReadSetting(QStringLiteral("swap_screen"), false).toBool();
-    Settings::values.upright_screen = ReadSetting(QStringLiteral("upright_screen"), false).toBool();
-    Settings::values.custom_layout = ReadSetting(QStringLiteral("custom_layout"), false).toBool();
-    Settings::values.custom_top_left = ReadSetting(QStringLiteral("custom_top_left"), 0).toInt();
-    Settings::values.custom_top_top = ReadSetting(QStringLiteral("custom_top_top"), 0).toInt();
-    Settings::values.custom_top_right =
-        ReadSetting(QStringLiteral("custom_top_right"), 400).toInt();
-    Settings::values.custom_top_bottom =
-        ReadSetting(QStringLiteral("custom_top_bottom"), 240).toInt();
-    Settings::values.custom_bottom_left =
-        ReadSetting(QStringLiteral("custom_bottom_left"), 40).toInt();
-    Settings::values.custom_bottom_top =
-        ReadSetting(QStringLiteral("custom_bottom_top"), 240).toInt();
-    Settings::values.custom_bottom_right =
-        ReadSetting(QStringLiteral("custom_bottom_right"), 360).toInt();
-    Settings::values.custom_bottom_bottom =
-        ReadSetting(QStringLiteral("custom_bottom_bottom"), 480).toInt();
+    ReadGlobalSetting(Settings::values.filter_mode);
+    ReadGlobalSetting(Settings::values.layout_option);
+    ReadGlobalSetting(Settings::values.swap_screen);
+    ReadGlobalSetting(Settings::values.upright_screen);
+
+    if (global) {
+        ReadBasicSetting(Settings::values.mono_render_left_eye);
+        ReadBasicSetting(Settings::values.custom_layout);
+        ReadBasicSetting(Settings::values.custom_top_left);
+        ReadBasicSetting(Settings::values.custom_top_top);
+        ReadBasicSetting(Settings::values.custom_top_right);
+        ReadBasicSetting(Settings::values.custom_top_bottom);
+        ReadBasicSetting(Settings::values.custom_bottom_left);
+        ReadBasicSetting(Settings::values.custom_bottom_top);
+        ReadBasicSetting(Settings::values.custom_bottom_right);
+        ReadBasicSetting(Settings::values.custom_bottom_bottom);
+    }
 
     qt_config->endGroup();
 }
@@ -381,10 +501,7 @@ void Config::ReadLayoutValues() {
 void Config::ReadMiscellaneousValues() {
     qt_config->beginGroup(QStringLiteral("Miscellaneous"));
 
-    Settings::values.log_filter =
-        ReadSetting(QStringLiteral("log_filter"), QStringLiteral("*:Info"))
-            .toString()
-            .toStdString();
+    ReadBasicSetting(Settings::values.log_filter);
 
     qt_config->endGroup();
 }
@@ -434,46 +551,49 @@ void Config::ReadMultiplayerValues() {
 void Config::ReadPathValues() {
     qt_config->beginGroup(QStringLiteral("Paths"));
 
-    UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
-    UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
-    UISettings::values.movie_record_path =
-        ReadSetting(QStringLiteral("movieRecordPath")).toString();
-    UISettings::values.movie_playback_path =
-        ReadSetting(QStringLiteral("moviePlaybackPath")).toString();
-    UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();
-    UISettings::values.video_dumping_path =
-        ReadSetting(QStringLiteral("videoDumpingPath")).toString();
-    UISettings::values.game_dir_deprecated =
-        ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
-    UISettings::values.game_dir_deprecated_deepscan =
-        ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
-    int size = qt_config->beginReadArray(QStringLiteral("gamedirs"));
-    for (int i = 0; i < size; ++i) {
-        qt_config->setArrayIndex(i);
-        UISettings::GameDir game_dir;
-        game_dir.path = ReadSetting(QStringLiteral("path")).toString();
-        game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool();
-        game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool();
-        UISettings::values.game_dirs.append(game_dir);
-    }
-    qt_config->endArray();
-    // create NAND and SD card directories if empty, these are not removable through the UI,
-    // also carries over old game list settings if present
-    if (UISettings::values.game_dirs.isEmpty()) {
-        UISettings::GameDir game_dir;
-        game_dir.path = QStringLiteral("INSTALLED");
-        game_dir.expanded = true;
-        UISettings::values.game_dirs.append(game_dir);
-        game_dir.path = QStringLiteral("SYSTEM");
-        UISettings::values.game_dirs.append(game_dir);
-        if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) {
-            game_dir.path = UISettings::values.game_dir_deprecated;
-            game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
+    ReadGlobalSetting(UISettings::values.screenshot_path);
+
+    if (global) {
+        UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
+        UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
+        UISettings::values.movie_record_path =
+            ReadSetting(QStringLiteral("movieRecordPath")).toString();
+        UISettings::values.movie_playback_path =
+            ReadSetting(QStringLiteral("moviePlaybackPath")).toString();
+        UISettings::values.video_dumping_path =
+            ReadSetting(QStringLiteral("videoDumpingPath")).toString();
+        UISettings::values.game_dir_deprecated =
+            ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
+        UISettings::values.game_dir_deprecated_deepscan =
+            ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
+        int size = qt_config->beginReadArray(QStringLiteral("gamedirs"));
+        for (int i = 0; i < size; ++i) {
+            qt_config->setArrayIndex(i);
+            UISettings::GameDir game_dir;
+            game_dir.path = ReadSetting(QStringLiteral("path")).toString();
+            game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool();
+            game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool();
             UISettings::values.game_dirs.append(game_dir);
         }
+        qt_config->endArray();
+        // create NAND and SD card directories if empty, these are not removable through the UI,
+        // also carries over old game list settings if present
+        if (UISettings::values.game_dirs.isEmpty()) {
+            UISettings::GameDir game_dir;
+            game_dir.path = QStringLiteral("INSTALLED");
+            game_dir.expanded = true;
+            UISettings::values.game_dirs.append(game_dir);
+            game_dir.path = QStringLiteral("SYSTEM");
+            UISettings::values.game_dirs.append(game_dir);
+            if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) {
+                game_dir.path = UISettings::values.game_dir_deprecated;
+                game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
+                UISettings::values.game_dirs.append(game_dir);
+            }
+        }
+        UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
+        UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
     }
-    UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
-    UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
 
     qt_config->endGroup();
 }
@@ -481,38 +601,29 @@ void Config::ReadPathValues() {
 void Config::ReadRendererValues() {
     qt_config->beginGroup(QStringLiteral("Renderer"));
 
-    Settings::values.use_hw_renderer =
-        ReadSetting(QStringLiteral("use_hw_renderer"), true).toBool();
-    Settings::values.use_hw_shader = ReadSetting(QStringLiteral("use_hw_shader"), true).toBool();
+    ReadGlobalSetting(Settings::values.use_hw_renderer);
+    ReadGlobalSetting(Settings::values.use_hw_shader);
 #ifdef __APPLE__
     // Hardware shader is broken on macos with Intel GPUs thanks to poor drivers.
     // We still want to provide this option for test/development purposes, but disable it by
     // default.
-    Settings::values.separable_shader =
-        ReadSetting(QStringLiteral("separable_shader"), false).toBool();
+    ReadGlobalSetting(Settings::values.separable_shader);
 #endif
-    Settings::values.shaders_accurate_mul =
-        ReadSetting(QStringLiteral("shaders_accurate_mul"), true).toBool();
-    Settings::values.use_shader_jit = ReadSetting(QStringLiteral("use_shader_jit"), true).toBool();
-    Settings::values.use_disk_shader_cache =
-        ReadSetting(QStringLiteral("use_disk_shader_cache"), true).toBool();
-    Settings::values.use_vsync_new = ReadSetting(QStringLiteral("use_vsync_new"), true).toBool();
-    Settings::values.resolution_factor =
-        static_cast<u16>(ReadSetting(QStringLiteral("resolution_factor"), 1).toInt());
-    Settings::values.frame_limit = ReadSetting(QStringLiteral("frame_limit"), 100).toInt();
-    Settings::values.use_frame_limit_alternate =
-        ReadSetting(QStringLiteral("use_frame_limit_alternate"), false).toBool();
-    Settings::values.frame_limit_alternate =
-        ReadSetting(QStringLiteral("frame_limit_alternate"), 200).toInt();
+    ReadGlobalSetting(Settings::values.shaders_accurate_mul);
+    ReadGlobalSetting(Settings::values.use_disk_shader_cache);
+    ReadGlobalSetting(Settings::values.use_vsync_new);
+    ReadGlobalSetting(Settings::values.resolution_factor);
+    ReadGlobalSetting(Settings::values.frame_limit);
 
-    Settings::values.bg_red = ReadSetting(QStringLiteral("bg_red"), 0.0).toFloat();
-    Settings::values.bg_green = ReadSetting(QStringLiteral("bg_green"), 0.0).toFloat();
-    Settings::values.bg_blue = ReadSetting(QStringLiteral("bg_blue"), 0.0).toFloat();
+    ReadGlobalSetting(Settings::values.bg_red);
+    ReadGlobalSetting(Settings::values.bg_green);
+    ReadGlobalSetting(Settings::values.bg_blue);
 
-    Settings::values.texture_filter_name =
-        ReadSetting(QStringLiteral("texture_filter_name"), QStringLiteral("none"))
-            .toString()
-            .toStdString();
+    ReadGlobalSetting(Settings::values.texture_filter_name);
+
+    if (global) {
+        ReadBasicSetting(Settings::values.use_shader_jit);
+    }
 
     qt_config->endGroup();
 }
@@ -539,16 +650,14 @@ void Config::ReadShortcutValues() {
 void Config::ReadSystemValues() {
     qt_config->beginGroup(QStringLiteral("System"));
 
-    Settings::values.is_new_3ds = ReadSetting(QStringLiteral("is_new_3ds"), true).toBool();
-    Settings::values.region_value =
-        ReadSetting(QStringLiteral("region_value"), Settings::REGION_VALUE_AUTO_SELECT).toInt();
-    Settings::values.init_clock = static_cast<Settings::InitClock>(
-        ReadSetting(QStringLiteral("init_clock"), static_cast<u32>(Settings::InitClock::SystemTime))
-            .toInt());
-    Settings::values.init_time =
-        ReadSetting(QStringLiteral("init_time"), 946681277ULL).toULongLong();
-    Settings::values.init_time_offset =
-        ReadSetting(QStringLiteral("init_time_offset"), 0LL).toLongLong();
+    ReadGlobalSetting(Settings::values.is_new_3ds);
+    ReadGlobalSetting(Settings::values.region_value);
+
+    if (global) {
+        ReadBasicSetting(Settings::values.init_clock);
+        ReadBasicSetting(Settings::values.init_time);
+        ReadBasicSetting(Settings::values.init_time_offset);
+    }
 
     qt_config->endGroup();
 }
@@ -599,39 +708,33 @@ void Config::ReadVideoDumpingValues() {
 void Config::ReadUIValues() {
     qt_config->beginGroup(QStringLiteral("UI"));
 
-    UISettings::values.theme =
-        ReadSetting(QStringLiteral("theme"), QString::fromUtf8(UISettings::themes[0].second))
-            .toString();
-    UISettings::values.enable_discord_presence =
-        ReadSetting(QStringLiteral("enable_discord_presence"), true).toBool();
-    UISettings::values.screenshot_resolution_factor =
-        static_cast<u16>(ReadSetting(QStringLiteral("screenshot_resolution_factor"), 0).toUInt());
-
-    ReadUpdaterValues();
-    ReadUILayoutValues();
-    ReadUIGameListValues();
     ReadPathValues();
-    ReadShortcutValues();
-    ReadMultiplayerValues();
 
-    UISettings::values.single_window_mode =
-        ReadSetting(QStringLiteral("singleWindowMode"), true).toBool();
-    UISettings::values.fullscreen = ReadSetting(QStringLiteral("fullscreen"), false).toBool();
-    UISettings::values.display_titlebar =
-        ReadSetting(QStringLiteral("displayTitleBars"), true).toBool();
-    UISettings::values.show_filter_bar =
-        ReadSetting(QStringLiteral("showFilterBar"), true).toBool();
-    UISettings::values.show_status_bar =
-        ReadSetting(QStringLiteral("showStatusBar"), true).toBool();
-    UISettings::values.confirm_before_closing =
-        ReadSetting(QStringLiteral("confirmClose"), true).toBool();
-    UISettings::values.first_start = ReadSetting(QStringLiteral("firstStart"), true).toBool();
-    UISettings::values.callout_flags = ReadSetting(QStringLiteral("calloutFlags"), 0).toUInt();
-    UISettings::values.show_console = ReadSetting(QStringLiteral("showConsole"), false).toBool();
-    UISettings::values.pause_when_in_background =
-        ReadSetting(QStringLiteral("pauseWhenInBackground"), false).toBool();
-    UISettings::values.hide_mouse =
-        ReadSetting(QStringLiteral("hideInactiveMouse"), false).toBool();
+    if (global) {
+        UISettings::values.theme =
+            ReadSetting(QStringLiteral("theme"), QString::fromUtf8(UISettings::themes[0].second))
+                .toString();
+        ReadBasicSetting(UISettings::values.enable_discord_presence);
+        ReadBasicSetting(UISettings::values.screenshot_resolution_factor);
+
+        ReadUpdaterValues();
+        ReadUILayoutValues();
+        ReadUIGameListValues();
+        ReadShortcutValues();
+        ReadMultiplayerValues();
+
+        ReadBasicSetting(UISettings::values.single_window_mode);
+        ReadBasicSetting(UISettings::values.fullscreen);
+        ReadBasicSetting(UISettings::values.display_titlebar);
+        ReadBasicSetting(UISettings::values.show_filter_bar);
+        ReadBasicSetting(UISettings::values.show_status_bar);
+        ReadBasicSetting(UISettings::values.confirm_before_closing);
+        ReadBasicSetting(UISettings::values.first_start);
+        ReadBasicSetting(UISettings::values.callout_flags);
+        ReadBasicSetting(UISettings::values.show_console);
+        ReadBasicSetting(UISettings::values.pause_when_in_background);
+        ReadBasicSetting(UISettings::values.hide_mouse);
+    }
 
     qt_config->endGroup();
 }
@@ -639,36 +742,11 @@ void Config::ReadUIValues() {
 void Config::ReadUIGameListValues() {
     qt_config->beginGroup(QStringLiteral("GameList"));
 
-    auto icon_size = UISettings::GameListIconSize{
-        ReadSetting(QStringLiteral("iconSize"),
-                    static_cast<int>(UISettings::GameListIconSize::LargeIcon))
-            .toInt()};
-    if (icon_size < UISettings::GameListIconSize::NoIcon ||
-        icon_size > UISettings::GameListIconSize::LargeIcon) {
-        icon_size = UISettings::GameListIconSize::LargeIcon;
-    }
-    UISettings::values.game_list_icon_size = icon_size;
-
-    UISettings::GameListText row_1 = UISettings::GameListText{
-        ReadSetting(QStringLiteral("row1"), static_cast<int>(UISettings::GameListText::TitleName))
-            .toInt()};
-    if (row_1 <= UISettings::GameListText::NoText || row_1 >= UISettings::GameListText::ListEnd) {
-        row_1 = UISettings::GameListText::TitleName;
-    }
-    UISettings::values.game_list_row_1 = row_1;
-
-    UISettings::GameListText row_2 = UISettings::GameListText{
-        ReadSetting(QStringLiteral("row2"), static_cast<int>(UISettings::GameListText::FileName))
-            .toInt()};
-    if (row_2 < UISettings::GameListText::NoText || row_2 >= UISettings::GameListText::ListEnd) {
-        row_2 = UISettings::GameListText::FileName;
-    }
-    UISettings::values.game_list_row_2 = row_2;
-
-    UISettings::values.game_list_hide_no_icon =
-        ReadSetting(QStringLiteral("hideNoIcon"), false).toBool();
-    UISettings::values.game_list_single_line_mode =
-        ReadSetting(QStringLiteral("singleLineMode"), false).toBool();
+    ReadBasicSetting(UISettings::values.game_list_icon_size);
+    ReadBasicSetting(UISettings::values.game_list_row_1);
+    ReadBasicSetting(UISettings::values.game_list_row_2);
+    ReadBasicSetting(UISettings::values.game_list_hide_no_icon);
+    ReadBasicSetting(UISettings::values.game_list_single_line_mode);
 
     qt_config->endGroup();
 }
@@ -684,8 +762,7 @@ void Config::ReadUILayoutValues() {
         ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();
     UISettings::values.microprofile_geometry =
         ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();
-    UISettings::values.microprofile_visible =
-        ReadSetting(QStringLiteral("microProfileDialogVisible"), false).toBool();
+    ReadBasicSetting(UISettings::values.microprofile_visible);
 
     qt_config->endGroup();
 }
@@ -693,10 +770,8 @@ void Config::ReadUILayoutValues() {
 void Config::ReadUpdaterValues() {
     qt_config->beginGroup(QStringLiteral("Updater"));
 
-    UISettings::values.check_for_update_on_start =
-        ReadSetting(QStringLiteral("check_for_update_on_start"), true).toBool();
-    UISettings::values.update_on_close =
-        ReadSetting(QStringLiteral("update_on_close"), false).toBool();
+    ReadBasicSetting(UISettings::values.check_for_update_on_start);
+    ReadBasicSetting(UISettings::values.update_on_close);
 
     qt_config->endGroup();
 }
@@ -719,40 +794,38 @@ void Config::ReadWebServiceValues() {
 }
 
 void Config::SaveValues() {
-    SaveControlValues();
+    if (global) {
+        SaveControlValues();
+        SaveCameraValues();
+        SaveDataStorageValues();
+        SaveMiscellaneousValues();
+        SaveDebuggingValues();
+        SaveWebServiceValues();
+        SaveVideoDumpingValues();
+        SaveUtilityValues();
+    }
+
+    SaveUIValues();
     SaveCoreValues();
     SaveRendererValues();
     SaveLayoutValues();
     SaveAudioValues();
-    SaveCameraValues();
-    SaveDataStorageValues();
     SaveSystemValues();
-    SaveMiscellaneousValues();
-    SaveDebuggingValues();
-    SaveWebServiceValues();
-    SaveVideoDumpingValues();
-    SaveUIValues();
-    SaveUtilityValues();
 }
 
 void Config::SaveAudioValues() {
     qt_config->beginGroup(QStringLiteral("Audio"));
 
-    WriteSetting(QStringLiteral("enable_dsp_lle"), Settings::values.enable_dsp_lle, false);
-    WriteSetting(QStringLiteral("enable_dsp_lle_multithread"),
-                 Settings::values.enable_dsp_lle_multithread, false);
-    WriteSetting(QStringLiteral("output_engine"), QString::fromStdString(Settings::values.sink_id),
-                 QStringLiteral("auto"));
-    WriteSetting(QStringLiteral("enable_audio_stretching"),
-                 Settings::values.enable_audio_stretching, true);
-    WriteSetting(QStringLiteral("output_device"),
-                 QString::fromStdString(Settings::values.audio_device_id), QStringLiteral("auto"));
-    WriteSetting(QStringLiteral("volume"), Settings::values.volume, 1.0f);
-    WriteSetting(QStringLiteral("mic_input_device"),
-                 QString::fromStdString(Settings::values.mic_input_device),
-                 QString::fromUtf8(Frontend::Mic::default_device_name));
-    WriteSetting(QStringLiteral("mic_input_type"),
-                 static_cast<int>(Settings::values.mic_input_type), 0);
+    WriteGlobalSetting(Settings::values.audio_emulation);
+    WriteGlobalSetting(Settings::values.enable_audio_stretching);
+    WriteGlobalSetting(Settings::values.volume);
+
+    if (global) {
+        WriteBasicSetting(Settings::values.sink_id);
+        WriteBasicSetting(Settings::values.audio_device_id);
+        WriteBasicSetting(Settings::values.mic_input_device);
+        WriteBasicSetting(Settings::values.mic_input_type);
+    }
 
     qt_config->endGroup();
 }
@@ -849,9 +922,9 @@ void Config::SaveControlValues() {
 void Config::SaveUtilityValues() {
     qt_config->beginGroup(QStringLiteral("Utility"));
 
-    WriteSetting(QStringLiteral("dump_textures"), Settings::values.dump_textures, false);
-    WriteSetting(QStringLiteral("custom_textures"), Settings::values.custom_textures, false);
-    WriteSetting(QStringLiteral("preload_textures"), Settings::values.preload_textures, false);
+    WriteBasicSetting(Settings::values.dump_textures);
+    WriteBasicSetting(Settings::values.custom_textures);
+    WriteBasicSetting(Settings::values.preload_textures);
 
     qt_config->endGroup();
 }
@@ -859,9 +932,11 @@ void Config::SaveUtilityValues() {
 void Config::SaveCoreValues() {
     qt_config->beginGroup(QStringLiteral("Core"));
 
-    WriteSetting(QStringLiteral("use_cpu_jit"), Settings::values.use_cpu_jit, true);
-    WriteSetting(QStringLiteral("cpu_clock_percentage"), Settings::values.cpu_clock_percentage,
-                 100);
+    WriteGlobalSetting(Settings::values.cpu_clock_percentage);
+
+    if (global) {
+        WriteBasicSetting(Settings::values.use_cpu_jit);
+    }
 
     qt_config->endGroup();
 }
@@ -869,8 +944,8 @@ void Config::SaveCoreValues() {
 void Config::SaveDataStorageValues() {
     qt_config->beginGroup(QStringLiteral("Data Storage"));
 
-    WriteSetting(QStringLiteral("use_virtual_sd"), Settings::values.use_virtual_sd, true);
-    WriteSetting(QStringLiteral("use_custom_storage"), Settings::values.use_custom_storage, false);
+    WriteBasicSetting(Settings::values.use_virtual_sd);
+    WriteBasicSetting(Settings::values.use_custom_storage);
     WriteSetting(QStringLiteral("nand_directory"),
                  QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
                  QStringLiteral(""));
@@ -886,8 +961,8 @@ void Config::SaveDebuggingValues() {
 
     // Intentionally not using the QT default setting as this is intended to be changed in the ini
     qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
-    WriteSetting(QStringLiteral("use_gdbstub"), Settings::values.use_gdbstub, false);
-    WriteSetting(QStringLiteral("gdbstub_port"), Settings::values.gdbstub_port, 24689);
+    WriteBasicSetting(Settings::values.use_gdbstub);
+    WriteBasicSetting(Settings::values.gdbstub_port);
 
     qt_config->beginGroup(QStringLiteral("LLE"));
     for (const auto& service_module : Settings::values.lle_modules) {
@@ -901,29 +976,30 @@ void Config::SaveDebuggingValues() {
 void Config::SaveLayoutValues() {
     qt_config->beginGroup(QStringLiteral("Layout"));
 
-    WriteSetting(QStringLiteral("render_3d"), static_cast<int>(Settings::values.render_3d), 0);
-    WriteSetting(QStringLiteral("factor_3d"), Settings::values.factor_3d.load(), 0);
-    WriteSetting(QStringLiteral("mono_render_left_eye"), Settings::values.mono_render_left_eye,
-                 false);
+    WriteGlobalSetting(Settings::values.render_3d);
+    WriteGlobalSetting(Settings::values.factor_3d);
     WriteSetting(QStringLiteral("pp_shader_name"),
-                 QString::fromStdString(Settings::values.pp_shader_name),
-                 (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph)
+                 QString::fromStdString(Settings::values.pp_shader_name.GetValue()),
+                 (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph)
                      ? QStringLiteral("dubois (builtin)")
                      : QStringLiteral("none (builtin)"));
-    WriteSetting(QStringLiteral("filter_mode"), Settings::values.filter_mode, true);
-    WriteSetting(QStringLiteral("layout_option"), static_cast<int>(Settings::values.layout_option));
-    WriteSetting(QStringLiteral("swap_screen"), Settings::values.swap_screen, false);
-    WriteSetting(QStringLiteral("upright_screen"), Settings::values.upright_screen, false);
-    WriteSetting(QStringLiteral("custom_layout"), Settings::values.custom_layout, false);
-    WriteSetting(QStringLiteral("custom_top_left"), Settings::values.custom_top_left, 0);
-    WriteSetting(QStringLiteral("custom_top_top"), Settings::values.custom_top_top, 0);
-    WriteSetting(QStringLiteral("custom_top_right"), Settings::values.custom_top_right, 400);
-    WriteSetting(QStringLiteral("custom_top_bottom"), Settings::values.custom_top_bottom, 240);
-    WriteSetting(QStringLiteral("custom_bottom_left"), Settings::values.custom_bottom_left, 40);
-    WriteSetting(QStringLiteral("custom_bottom_top"), Settings::values.custom_bottom_top, 240);
-    WriteSetting(QStringLiteral("custom_bottom_right"), Settings::values.custom_bottom_right, 360);
-    WriteSetting(QStringLiteral("custom_bottom_bottom"), Settings::values.custom_bottom_bottom,
-                 480);
+    WriteGlobalSetting(Settings::values.filter_mode);
+    WriteGlobalSetting(Settings::values.layout_option);
+    WriteGlobalSetting(Settings::values.swap_screen);
+    WriteGlobalSetting(Settings::values.upright_screen);
+
+    if (global) {
+        WriteBasicSetting(Settings::values.mono_render_left_eye);
+        WriteBasicSetting(Settings::values.custom_layout);
+        WriteBasicSetting(Settings::values.custom_top_left);
+        WriteBasicSetting(Settings::values.custom_top_top);
+        WriteBasicSetting(Settings::values.custom_top_right);
+        WriteBasicSetting(Settings::values.custom_top_bottom);
+        WriteBasicSetting(Settings::values.custom_bottom_left);
+        WriteBasicSetting(Settings::values.custom_bottom_top);
+        WriteBasicSetting(Settings::values.custom_bottom_right);
+        WriteBasicSetting(Settings::values.custom_bottom_bottom);
+    }
 
     qt_config->endGroup();
 }
@@ -931,8 +1007,7 @@ void Config::SaveLayoutValues() {
 void Config::SaveMiscellaneousValues() {
     qt_config->beginGroup(QStringLiteral("Miscellaneous"));
 
-    WriteSetting(QStringLiteral("log_filter"), QString::fromStdString(Settings::values.log_filter),
-                 QStringLiteral("*:Info"));
+    WriteBasicSetting(Settings::values.log_filter);
 
     qt_config->endGroup();
 }
@@ -974,23 +1049,25 @@ void Config::SaveMultiplayerValues() {
 void Config::SavePathValues() {
     qt_config->beginGroup(QStringLiteral("Paths"));
 
-    WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
-    WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
-    WriteSetting(QStringLiteral("movieRecordPath"), UISettings::values.movie_record_path);
-    WriteSetting(QStringLiteral("moviePlaybackPath"), UISettings::values.movie_playback_path);
-    WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
-    WriteSetting(QStringLiteral("videoDumpingPath"), UISettings::values.video_dumping_path);
-    qt_config->beginWriteArray(QStringLiteral("gamedirs"));
-    for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
-        qt_config->setArrayIndex(i);
-        const auto& game_dir = UISettings::values.game_dirs[i];
-        WriteSetting(QStringLiteral("path"), game_dir.path);
-        WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false);
-        WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true);
+    WriteGlobalSetting(UISettings::values.screenshot_path);
+    if (global) {
+        WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
+        WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
+        WriteSetting(QStringLiteral("movieRecordPath"), UISettings::values.movie_record_path);
+        WriteSetting(QStringLiteral("moviePlaybackPath"), UISettings::values.movie_playback_path);
+        WriteSetting(QStringLiteral("videoDumpingPath"), UISettings::values.video_dumping_path);
+        qt_config->beginWriteArray(QStringLiteral("gamedirs"));
+        for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
+            qt_config->setArrayIndex(i);
+            const auto& game_dir = UISettings::values.game_dirs[i];
+            WriteSetting(QStringLiteral("path"), game_dir.path);
+            WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false);
+            WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true);
+        }
+        qt_config->endArray();
+        WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
+        WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
     }
-    qt_config->endArray();
-    WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
-    WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
 
     qt_config->endGroup();
 }
@@ -998,34 +1075,29 @@ void Config::SavePathValues() {
 void Config::SaveRendererValues() {
     qt_config->beginGroup(QStringLiteral("Renderer"));
 
-    WriteSetting(QStringLiteral("use_hw_renderer"), Settings::values.use_hw_renderer, true);
-    WriteSetting(QStringLiteral("use_hw_shader"), Settings::values.use_hw_shader, true);
+    WriteGlobalSetting(Settings::values.use_hw_renderer);
+    WriteGlobalSetting(Settings::values.use_hw_shader);
 #ifdef __APPLE__
     // Hardware shader is broken on macos thanks to poor drivers.
     // TODO: enable this for none Intel GPUs
-    WriteSetting(QStringLiteral("use_separable_shader"), Settings::values.separable_shader, false);
+    WriteGlobalSetting(Settings::values.separable_shader);
 #endif
-    WriteSetting(QStringLiteral("shaders_accurate_mul"), Settings::values.shaders_accurate_mul,
-                 true);
-    WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit, true);
-    WriteSetting(QStringLiteral("use_disk_shader_cache"), Settings::values.use_disk_shader_cache,
-                 true);
-    WriteSetting(QStringLiteral("use_vsync_new"), Settings::values.use_vsync_new, true);
-    WriteSetting(QStringLiteral("resolution_factor"), Settings::values.resolution_factor, 1);
-    WriteSetting(QStringLiteral("frame_limit"), Settings::values.frame_limit, 100);
-    WriteSetting(QStringLiteral("use_frame_limit_alternate"),
-                 Settings::values.use_frame_limit_alternate, false);
-    WriteSetting(QStringLiteral("frame_limit_alternate"), Settings::values.frame_limit_alternate,
-                 200);
+    WriteGlobalSetting(Settings::values.shaders_accurate_mul);
+    WriteGlobalSetting(Settings::values.use_disk_shader_cache);
+    WriteGlobalSetting(Settings::values.use_vsync_new);
+    WriteGlobalSetting(Settings::values.resolution_factor);
+    WriteGlobalSetting(Settings::values.frame_limit);
 
-    // Cast to double because Qt's written float values are not human-readable
-    WriteSetting(QStringLiteral("bg_red"), static_cast<double>(Settings::values.bg_red), 0.0);
-    WriteSetting(QStringLiteral("bg_green"), static_cast<double>(Settings::values.bg_green), 0.0);
-    WriteSetting(QStringLiteral("bg_blue"), static_cast<double>(Settings::values.bg_blue), 0.0);
+    WriteGlobalSetting(Settings::values.bg_red);
+    WriteGlobalSetting(Settings::values.bg_green);
+    WriteGlobalSetting(Settings::values.bg_blue);
 
-    WriteSetting(QStringLiteral("texture_filter_name"),
-                 QString::fromStdString(Settings::values.texture_filter_name),
-                 QStringLiteral("none"));
+    WriteGlobalSetting(Settings::values.texture_filter_name);
+
+    if (global) {
+        WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit.GetValue(),
+                     true);
+    }
 
     qt_config->endGroup();
 }
@@ -1052,15 +1124,14 @@ void Config::SaveShortcutValues() {
 void Config::SaveSystemValues() {
     qt_config->beginGroup(QStringLiteral("System"));
 
-    WriteSetting(QStringLiteral("is_new_3ds"), Settings::values.is_new_3ds, true);
-    WriteSetting(QStringLiteral("region_value"), Settings::values.region_value,
-                 Settings::REGION_VALUE_AUTO_SELECT);
-    WriteSetting(QStringLiteral("init_clock"), static_cast<u32>(Settings::values.init_clock),
-                 static_cast<u32>(Settings::InitClock::SystemTime));
-    WriteSetting(QStringLiteral("init_time"),
-                 static_cast<unsigned long long>(Settings::values.init_time), 946681277ULL);
-    WriteSetting(QStringLiteral("init_time_offset"),
-                 static_cast<long long>(Settings::values.init_time_offset), 0LL);
+    WriteGlobalSetting(Settings::values.is_new_3ds);
+    WriteGlobalSetting(Settings::values.region_value);
+
+    if (global) {
+        WriteBasicSetting(Settings::values.init_clock);
+        WriteBasicSetting(Settings::values.init_time);
+        WriteBasicSetting(Settings::values.init_time_offset);
+    }
 
     qt_config->endGroup();
 }
@@ -1095,32 +1166,32 @@ void Config::SaveVideoDumpingValues() {
 void Config::SaveUIValues() {
     qt_config->beginGroup(QStringLiteral("UI"));
 
-    WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
-                 QString::fromUtf8(UISettings::themes[0].second));
-    WriteSetting(QStringLiteral("enable_discord_presence"),
-                 UISettings::values.enable_discord_presence, true);
-    WriteSetting(QStringLiteral("screenshot_resolution_factor"),
-                 UISettings::values.screenshot_resolution_factor, 0);
-
-    SaveUpdaterValues();
-    SaveUILayoutValues();
-    SaveUIGameListValues();
     SavePathValues();
-    SaveShortcutValues();
-    SaveMultiplayerValues();
 
-    WriteSetting(QStringLiteral("singleWindowMode"), UISettings::values.single_window_mode, true);
-    WriteSetting(QStringLiteral("fullscreen"), UISettings::values.fullscreen, false);
-    WriteSetting(QStringLiteral("displayTitleBars"), UISettings::values.display_titlebar, true);
-    WriteSetting(QStringLiteral("showFilterBar"), UISettings::values.show_filter_bar, true);
-    WriteSetting(QStringLiteral("showStatusBar"), UISettings::values.show_status_bar, true);
-    WriteSetting(QStringLiteral("confirmClose"), UISettings::values.confirm_before_closing, true);
-    WriteSetting(QStringLiteral("firstStart"), UISettings::values.first_start, true);
-    WriteSetting(QStringLiteral("calloutFlags"), UISettings::values.callout_flags, 0);
-    WriteSetting(QStringLiteral("showConsole"), UISettings::values.show_console, false);
-    WriteSetting(QStringLiteral("pauseWhenInBackground"),
-                 UISettings::values.pause_when_in_background, false);
-    WriteSetting(QStringLiteral("hideInactiveMouse"), UISettings::values.hide_mouse, false);
+    if (global) {
+        WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
+                     QString::fromUtf8(UISettings::themes[0].second));
+        WriteBasicSetting(UISettings::values.enable_discord_presence);
+        WriteBasicSetting(UISettings::values.screenshot_resolution_factor);
+
+        SaveUpdaterValues();
+        SaveUILayoutValues();
+        SaveUIGameListValues();
+        SaveShortcutValues();
+        SaveMultiplayerValues();
+
+        WriteBasicSetting(UISettings::values.single_window_mode);
+        WriteBasicSetting(UISettings::values.fullscreen);
+        WriteBasicSetting(UISettings::values.display_titlebar);
+        WriteBasicSetting(UISettings::values.show_filter_bar);
+        WriteBasicSetting(UISettings::values.show_status_bar);
+        WriteBasicSetting(UISettings::values.confirm_before_closing);
+        WriteBasicSetting(UISettings::values.first_start);
+        WriteBasicSetting(UISettings::values.callout_flags);
+        WriteBasicSetting(UISettings::values.show_console);
+        WriteBasicSetting(UISettings::values.pause_when_in_background);
+        WriteBasicSetting(UISettings::values.hide_mouse);
+    }
 
     qt_config->endGroup();
 }
@@ -1128,13 +1199,11 @@ void Config::SaveUIValues() {
 void Config::SaveUIGameListValues() {
     qt_config->beginGroup(QStringLiteral("GameList"));
 
-    WriteSetting(QStringLiteral("iconSize"),
-                 static_cast<int>(UISettings::values.game_list_icon_size), 2);
-    WriteSetting(QStringLiteral("row1"), static_cast<int>(UISettings::values.game_list_row_1), 2);
-    WriteSetting(QStringLiteral("row2"), static_cast<int>(UISettings::values.game_list_row_2), 0);
-    WriteSetting(QStringLiteral("hideNoIcon"), UISettings::values.game_list_hide_no_icon, false);
-    WriteSetting(QStringLiteral("singleLineMode"), UISettings::values.game_list_single_line_mode,
-                 false);
+    WriteBasicSetting(UISettings::values.game_list_icon_size);
+    WriteBasicSetting(UISettings::values.game_list_row_1);
+    WriteBasicSetting(UISettings::values.game_list_row_2);
+    WriteBasicSetting(UISettings::values.game_list_hide_no_icon);
+    WriteBasicSetting(UISettings::values.game_list_single_line_mode);
 
     qt_config->endGroup();
 }
@@ -1148,8 +1217,7 @@ void Config::SaveUILayoutValues() {
     WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state);
     WriteSetting(QStringLiteral("microProfileDialogGeometry"),
                  UISettings::values.microprofile_geometry);
-    WriteSetting(QStringLiteral("microProfileDialogVisible"),
-                 UISettings::values.microprofile_visible, false);
+    WriteBasicSetting(UISettings::values.microprofile_visible);
 
     qt_config->endGroup();
 }
@@ -1157,9 +1225,8 @@ void Config::SaveUILayoutValues() {
 void Config::SaveUpdaterValues() {
     qt_config->beginGroup(QStringLiteral("Updater"));
 
-    WriteSetting(QStringLiteral("check_for_update_on_start"),
-                 UISettings::values.check_for_update_on_start, true);
-    WriteSetting(QStringLiteral("update_on_close"), UISettings::values.update_on_close, false);
+    WriteBasicSetting(UISettings::values.check_for_update_on_start);
+    WriteBasicSetting(UISettings::values.update_on_close);
 
     qt_config->endGroup();
 }
@@ -1193,6 +1260,15 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value)
     return result;
 }
 
+template <typename Type>
+void Config::ReadSettingGlobal(Type& setting, const QString& name,
+                               const QVariant& default_value) const {
+    const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool();
+    if (global || !use_global) {
+        setting = ReadSetting(name, default_value).value<Type>();
+    }
+}
+
 void Config::WriteSetting(const QString& name, const QVariant& value) {
     qt_config->setValue(name, value);
 }
@@ -1203,11 +1279,21 @@ void Config::WriteSetting(const QString& name, const QVariant& value,
     qt_config->setValue(name, value);
 }
 
+void Config::WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value,
+                          bool use_global) {
+    if (!global) {
+        qt_config->setValue(name + QStringLiteral("/use_global"), use_global);
+    }
+    if (global || !use_global) {
+        qt_config->setValue(name + QStringLiteral("/default"), value == default_value);
+        qt_config->setValue(name, value);
+    }
+}
+
 void Config::Reload() {
     ReadValues();
     // To apply default value changes
     SaveValues();
-    Settings::Apply();
 }
 
 void Config::Save() {
diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h
index 8b1cf8193..8a8659de4 100644
--- a/src/citra_qt/configuration/config.h
+++ b/src/citra_qt/configuration/config.h
@@ -8,13 +8,16 @@
 #include <memory>
 #include <string>
 #include <QVariant>
-#include "core/settings.h"
+#include "common/settings.h"
 
 class QSettings;
 
 class Config {
 public:
-    Config();
+    enum class ConfigType : u32 { GlobalConfig, PerGameConfig };
+
+    explicit Config(const std::string& config_name = "qt-config",
+                    ConfigType config_type = ConfigType::GlobalConfig);
     ~Config();
 
     void Reload();
@@ -24,6 +27,8 @@ public:
     static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
 
 private:
+    void Initialize(const std::string& config_name);
+
     void ReadValues();
     void ReadAudioValues();
     void ReadCameraValues();
@@ -68,11 +73,78 @@ private:
     void SaveWebServiceValues();
     void SaveVideoDumpingValues();
 
+    /**
+     * Reads a setting from the qt_config.
+     *
+     * @param name The setting's identifier
+     * @param default_value The value to use when the setting is not already present in the config
+     */
     QVariant ReadSetting(const QString& name) const;
     QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
+
+    /**
+     * Only reads a setting from the qt_config if the current config is a global config, or if the
+     * current config is a custom config and the setting is overriding the global setting. Otherwise
+     * it does nothing.
+     *
+     * @param setting The variable to be modified
+     * @param name The setting's identifier
+     * @param default_value The value to use when the setting is not already present in the config
+     */
+    template <typename Type>
+    void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const;
+
+    /**
+     * Writes a setting to the qt_config.
+     *
+     * @param name The setting's idetentifier
+     * @param value Value of the setting
+     * @param default_value Default of the setting if not present in qt_config
+     * @param use_global Specifies if the custom or global config should be in use, for custom
+     * configs
+     */
     void WriteSetting(const QString& name, const QVariant& value);
     void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
+    void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value,
+                      bool use_global);
 
+    /**
+     * Reads a value from the qt_config and applies it to the setting, using its label and default
+     * value. If the config is a custom config, this will also read the global state of the setting
+     * and apply that information to it.
+     *
+     * @param The setting
+     */
+    template <typename Type, bool ranged>
+    void ReadGlobalSetting(Settings::SwitchableSetting<Type, ranged>& setting);
+
+    /**
+     * Sets a value to the qt_config using the setting's label and default value. If the config is a
+     * custom config, it will apply the global state, and the custom value if needed.
+     *
+     * @param The setting
+     */
+    template <typename Type, bool ranged>
+    void WriteGlobalSetting(const Settings::SwitchableSetting<Type, ranged>& setting);
+
+    /**
+     * Reads a value from the qt_config using the setting's label and default value and applies the
+     * value to the setting.
+     *
+     * @param The setting
+     */
+    template <typename Type, bool ranged>
+    void ReadBasicSetting(Settings::Setting<Type, ranged>& setting);
+
+    /** Sets a value from the setting in the qt_config using the setting's label and default value.
+     *
+     * @param The setting
+     */
+    template <typename Type, bool ranged>
+    void WriteBasicSetting(const Settings::Setting<Type, ranged>& setting);
+
+    ConfigType type;
     std::unique_ptr<QSettings> qt_config;
     std::string qt_config_loc;
+    bool global;
 };
diff --git a/src/citra_qt/configuration/configuration_shared.cpp b/src/citra_qt/configuration/configuration_shared.cpp
new file mode 100644
index 000000000..e5039256a
--- /dev/null
+++ b/src/citra_qt/configuration/configuration_shared.cpp
@@ -0,0 +1,95 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QCheckBox>
+#include <QObject>
+#include <QString>
+#include "citra_qt/configuration/configuration_shared.h"
+#include "citra_qt/configuration/configure_per_game.h"
+#include "common/settings.h"
+
+void ConfigurationShared::ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting,
+                                              const QCheckBox* checkbox,
+                                              const CheckState& tracker) {
+    if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
+        setting->SetValue(checkbox->checkState());
+    } else if (!Settings::IsConfiguringGlobal()) {
+        if (tracker == CheckState::Global) {
+            setting->SetGlobal(true);
+        } else {
+            setting->SetGlobal(false);
+            setting->SetValue(checkbox->checkState());
+        }
+    }
+}
+
+void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox,
+                                            const Settings::SwitchableSetting<bool>* setting) {
+    if (setting->UsingGlobal()) {
+        checkbox->setCheckState(Qt::PartiallyChecked);
+    } else {
+        checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked);
+    }
+}
+
+void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
+    if (highlighted) {
+        widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }")
+                                  .arg(widget->objectName()));
+    } else {
+        widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }")
+                                  .arg(widget->objectName()));
+    }
+    widget->show();
+}
+
+void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox,
+                                             const Settings::SwitchableSetting<bool>& setting,
+                                             CheckState& tracker) {
+    if (setting.UsingGlobal()) {
+        tracker = CheckState::Global;
+    } else {
+        tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off;
+    }
+    SetHighlight(checkbox, tracker != CheckState::Global);
+    QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] {
+        tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
+                                          static_cast<int>(CheckState::Count));
+        if (tracker == CheckState::Global) {
+            checkbox->setChecked(setting.GetValue(true));
+        }
+        SetHighlight(checkbox, tracker != CheckState::Global);
+    });
+}
+
+void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state,
+                                             bool global_state, CheckState& tracker) {
+    if (global) {
+        tracker = CheckState::Global;
+    } else {
+        tracker = (state == global_state) ? CheckState::On : CheckState::Off;
+    }
+    SetHighlight(checkbox, tracker != CheckState::Global);
+    QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] {
+        tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
+                                          static_cast<int>(CheckState::Count));
+        if (tracker == CheckState::Global) {
+            checkbox->setChecked(global_state);
+        }
+        SetHighlight(checkbox, tracker != CheckState::Global);
+    });
+}
+
+void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) {
+    InsertGlobalItem(combobox, global);
+    QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target,
+                     [target](int index) { SetHighlight(target, index != 0); });
+}
+
+void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) {
+    const QString use_global_text =
+        ConfigurePerGame::tr("Use global configuration (%1)").arg(combobox->itemText(global_index));
+    combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text);
+    combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX);
+}
diff --git a/src/citra_qt/configuration/configuration_shared.h b/src/citra_qt/configuration/configuration_shared.h
new file mode 100644
index 000000000..63a77edd3
--- /dev/null
+++ b/src/citra_qt/configuration/configuration_shared.h
@@ -0,0 +1,98 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QCheckBox>
+#include <QComboBox>
+#include "common/settings.h"
+
+namespace ConfigurationShared {
+
+constexpr int USE_GLOBAL_INDEX =
+    0; ///< The index of the "Use global configuration" option in checkboxes
+constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1;
+constexpr int USE_GLOBAL_OFFSET = 2;
+
+/// CheckBoxes require a tracker for their state since we emulate a tristate CheckBox
+enum class CheckState {
+    Off,    ///< Checkbox overrides to off/false
+    On,     ///< Checkbox overrides to on/true
+    Global, ///< Checkbox defers to the global state
+    Count,  ///< Simply the number of states, not a valid checkbox state
+};
+
+/**
+ * @brief ApplyPerGameSetting given a setting and a Qt UI element, properly applies a Setting
+ * taking into account the global/per-game check state. This is used for configuring checkboxes
+ * @param setting
+ * @param checkbox
+ * @param tracker
+ */
+void ApplyPerGameSetting(Settings::SwitchableSetting<bool>* setting, const QCheckBox* checkbox,
+                         const CheckState& tracker);
+
+/**
+ * @brief ApplyPerGameSetting given a setting and a Qt UI element, properly applies a Setting
+ * taking into account the global/per-game check state. This is used for both combo boxes
+ * as well as any other widget that is accompanied by a combo box in per-game settings.
+ * @param setting The setting class that stores the desired option
+ * @param combobox The Qt combo box that stores the value/per-game status
+ * @param transform A function that accepts the combo box index and transforms it to the
+ * desired settings value. When used with sliders/edit the user can ignore the input value
+ * and set a custom value this making this function useful for these widgets as well
+ */
+template <typename Type, bool ranged>
+void ApplyPerGameSetting(Settings::SwitchableSetting<Type, ranged>* setting,
+                         const QComboBox* combobox, auto transform) {
+    if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
+        setting->SetValue(static_cast<Type>(transform(combobox->currentIndex())));
+    } else if (!Settings::IsConfiguringGlobal()) {
+        if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
+            setting->SetGlobal(true);
+        } else {
+            setting->SetGlobal(false);
+            setting->SetValue(static_cast<Type>(
+                transform(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET)));
+        }
+    }
+}
+
+/// Simpler version of ApplyPerGameSetting without a transform parameter
+template <typename Type, bool ranged>
+void ApplyPerGameSetting(Settings::SwitchableSetting<Type, ranged>* setting,
+                         const QComboBox* combobox) {
+    const auto transform = [](s32 index) { return index; };
+    return ApplyPerGameSetting(setting, combobox, transform);
+}
+
+/// Sets a Qt UI element given a Settings::Setting
+void SetPerGameSetting(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>* setting);
+
+template <typename Type, bool ranged>
+void SetPerGameSetting(QComboBox* combobox,
+                       const Settings::SwitchableSetting<Type, ranged>* setting) {
+    combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
+                                                     : static_cast<int>(setting->GetValue()) +
+                                                           ConfigurationShared::USE_GLOBAL_OFFSET);
+}
+
+/// Given a Qt widget sets the background color to indicate whether the setting
+/// is per-game overriden (highlighted) or global (non-highlighted)
+void SetHighlight(QWidget* widget, bool highlighted);
+
+/// Sets up a QCheckBox like a tristate one, given a Setting
+void SetColoredTristate(QCheckBox* checkbox, const Settings::SwitchableSetting<bool>& setting,
+                        CheckState& tracker);
+void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state,
+                        CheckState& tracker);
+
+/// Sets up coloring of a QWidget `target` based on the state of a QComboBox, and calls
+/// InsertGlobalItem
+void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global);
+
+/// Adds the "Use Global Configuration" selection and separator to the beginning of a QComboBox
+void InsertGlobalItem(QComboBox* combobox, int global_index);
+
+} // namespace ConfigurationShared
diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp
index 7e9dba159..af42da8ce 100644
--- a/src/citra_qt/configuration/configure_audio.cpp
+++ b/src/citra_qt/configuration/configure_audio.cpp
@@ -9,10 +9,11 @@
 #endif
 #include "audio_core/sink.h"
 #include "audio_core/sink_details.h"
+#include "citra_qt/configuration/configuration_shared.h"
 #include "citra_qt/configuration/configure_audio.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/frontend/mic.h"
-#include "core/settings.h"
 #include "ui_configure_audio.h"
 
 #if defined(__APPLE__)
@@ -31,25 +32,30 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
         ui->output_sink_combo_box->addItem(QString::fromUtf8(id));
     }
 
-    ui->emulation_combo_box->addItem(tr("HLE (fast)"));
-    ui->emulation_combo_box->addItem(tr("LLE (accurate)"));
-    ui->emulation_combo_box->addItem(tr("LLE multi-core"));
-    ui->emulation_combo_box->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+    const bool is_running = Core::System::GetInstance().IsPoweredOn();
+    ui->emulation_combo_box->setEnabled(!is_running);
 
     connect(ui->volume_slider, &QSlider::valueChanged, this,
             &ConfigureAudio::SetVolumeIndicatorText);
 
     ui->input_device_combo_box->clear();
     ui->input_device_combo_box->addItem(tr("Default"));
+
 #ifdef HAVE_CUBEB
     for (const auto& device : AudioCore::ListCubebInputDevices()) {
         ui->input_device_combo_box->addItem(QString::fromStdString(device));
     }
 #endif
+
     connect(ui->input_type_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
             &ConfigureAudio::UpdateAudioInputDevices);
 
+    ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
+    ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal());
+
+    SetupPerGameUI();
     SetConfiguration();
+
     connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
             &ConfigureAudio::UpdateAudioOutputDevices);
 }
@@ -61,27 +67,35 @@ void ConfigureAudio::SetConfiguration() {
 
     // The device list cannot be pre-populated (nor listed) until the output sink is known.
     UpdateAudioOutputDevices(ui->output_sink_combo_box->currentIndex());
-
     SetAudioDeviceFromDeviceID();
 
-    ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
-    ui->volume_slider->setValue(
-        static_cast<int>(Settings::values.volume * ui->volume_slider->maximum()));
+    ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue());
+
+    const s32 volume =
+        static_cast<s32>(Settings::values.volume.GetValue() * ui->volume_slider->maximum());
+    ui->volume_slider->setValue(volume);
     SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
 
-    int selection;
-    if (Settings::values.enable_dsp_lle) {
-        if (Settings::values.enable_dsp_lle_multithread) {
-            selection = 2;
+    if (!Settings::IsConfiguringGlobal()) {
+        if (Settings::values.volume.UsingGlobal()) {
+            ui->volume_combo_box->setCurrentIndex(0);
+            ui->volume_slider->setEnabled(false);
         } else {
-            selection = 1;
+            ui->volume_combo_box->setCurrentIndex(1);
+            ui->volume_slider->setEnabled(true);
         }
+        ConfigurationShared::SetHighlight(ui->volume_layout,
+                                          !Settings::values.volume.UsingGlobal());
+        ConfigurationShared::SetHighlight(ui->widget_emulation,
+                                          !Settings::values.audio_emulation.UsingGlobal());
+        ConfigurationShared::SetPerGameSetting(ui->emulation_combo_box,
+                                               &Settings::values.audio_emulation);
     } else {
-        selection = 0;
+        s32 selection = static_cast<s32>(Settings::values.audio_emulation.GetValue());
+        ui->emulation_combo_box->setCurrentIndex(selection);
     }
-    ui->emulation_combo_box->setCurrentIndex(selection);
 
-    int index = static_cast<int>(Settings::values.mic_input_type);
+    s32 index = static_cast<s32>(Settings::values.mic_input_type.GetValue());
     ui->input_type_combo_box->setCurrentIndex(index);
 
     UpdateAudioInputDevices(index);
@@ -90,7 +104,7 @@ void ConfigureAudio::SetConfiguration() {
 void ConfigureAudio::SetOutputSinkFromSinkID() {
     int new_sink_index = 0;
 
-    const QString sink_id = QString::fromStdString(Settings::values.sink_id);
+    const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue());
     for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
         if (ui->output_sink_combo_box->itemText(index) == sink_id) {
             new_sink_index = index;
@@ -104,7 +118,7 @@ void ConfigureAudio::SetOutputSinkFromSinkID() {
 void ConfigureAudio::SetAudioDeviceFromDeviceID() {
     int new_device_index = -1;
 
-    const QString device_id = QString::fromStdString(Settings::values.audio_device_id);
+    const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue());
     for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
         if (ui->audio_device_combo_box->itemText(index) == device_id) {
             new_device_index = index;
@@ -120,24 +134,31 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
 }
 
 void ConfigureAudio::ApplyConfiguration() {
-    Settings::values.sink_id =
-        ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
-            .toStdString();
-    Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
-    Settings::values.audio_device_id =
-        ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
-            .toStdString();
-    Settings::values.volume =
-        static_cast<float>(ui->volume_slider->sliderPosition()) / ui->volume_slider->maximum();
-    Settings::values.enable_dsp_lle = ui->emulation_combo_box->currentIndex() != 0;
-    Settings::values.enable_dsp_lle_multithread = ui->emulation_combo_box->currentIndex() == 2;
-    Settings::values.mic_input_type =
-        static_cast<Settings::MicInputType>(ui->input_type_combo_box->currentIndex());
+    ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching,
+                                             ui->toggle_audio_stretching, audio_stretching);
+    ConfigurationShared::ApplyPerGameSetting(&Settings::values.audio_emulation,
+                                             ui->emulation_combo_box);
+    ConfigurationShared::ApplyPerGameSetting(
+        &Settings::values.volume, ui->volume_combo_box, [this](s32) {
+            return static_cast<float>(ui->volume_slider->value()) / ui->volume_slider->maximum();
+        });
 
-    if (ui->input_device_combo_box->currentIndex() == DEFAULT_INPUT_DEVICE_INDEX) {
-        Settings::values.mic_input_device = Frontend::Mic::default_device_name;
-    } else {
-        Settings::values.mic_input_device = ui->input_device_combo_box->currentText().toStdString();
+    if (Settings::IsConfiguringGlobal()) {
+        Settings::values.sink_id =
+            ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
+                .toStdString();
+        Settings::values.audio_device_id =
+            ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
+                .toStdString();
+        Settings::values.mic_input_type =
+            static_cast<Settings::MicInputType>(ui->input_type_combo_box->currentIndex());
+
+        if (ui->input_device_combo_box->currentIndex() == DEFAULT_INPUT_DEVICE_INDEX) {
+            Settings::values.mic_input_device = Frontend::Mic::default_device_name;
+        } else {
+            Settings::values.mic_input_device =
+                ui->input_device_combo_box->currentText().toStdString();
+        }
     }
 }
 
@@ -157,12 +178,41 @@ void ConfigureAudio::UpdateAudioInputDevices(int index) {
         AppleAuthorization::CheckAuthorizationForMicrophone();
     }
 #endif
-    if (Settings::values.mic_input_device != Frontend::Mic::default_device_name) {
+    if (Settings::values.mic_input_device.GetValue() != Frontend::Mic::default_device_name) {
         ui->input_device_combo_box->setCurrentText(
-            QString::fromStdString(Settings::values.mic_input_device));
+            QString::fromStdString(Settings::values.mic_input_device.GetValue()));
     }
 }
 
 void ConfigureAudio::RetranslateUI() {
     ui->retranslateUi(this);
 }
+
+void ConfigureAudio::SetupPerGameUI() {
+    if (Settings::IsConfiguringGlobal()) {
+        ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
+        return;
+    }
+
+    ui->output_sink_combo_box->setVisible(false);
+    ui->output_sink_label->setVisible(false);
+    ui->audio_device_combo_box->setVisible(false);
+    ui->audio_device_label->setVisible(false);
+    ui->input_type_label->setVisible(false);
+    ui->input_type_combo_box->setVisible(false);
+    ui->input_device_label->setVisible(false);
+    ui->input_device_combo_box->setVisible(false);
+    ui->microphone_layout->setVisible(false);
+
+    connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
+        ui->volume_slider->setEnabled(index == 1);
+        ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
+    });
+
+    ConfigurationShared::SetColoredComboBox(
+        ui->emulation_combo_box, ui->widget_emulation,
+        static_cast<u32>(Settings::values.audio_emulation.GetValue(true)));
+
+    ConfigurationShared::SetColoredTristate(
+        ui->toggle_audio_stretching, Settings::values.enable_audio_stretching, audio_stretching);
+}
diff --git a/src/citra_qt/configuration/configure_audio.h b/src/citra_qt/configuration/configure_audio.h
index cdaf73819..514c7945c 100644
--- a/src/citra_qt/configuration/configure_audio.h
+++ b/src/citra_qt/configuration/configure_audio.h
@@ -11,6 +11,10 @@ namespace Ui {
 class ConfigureAudio;
 }
 
+namespace ConfigurationShared {
+enum class CheckState;
+}
+
 class ConfigureAudio : public QWidget {
     Q_OBJECT
 
@@ -30,5 +34,8 @@ private:
     void SetAudioDeviceFromDeviceID();
     void SetVolumeIndicatorText(int percentage);
 
+    void SetupPerGameUI();
+
+    ConfigurationShared::CheckState audio_stretching;
     std::unique_ptr<Ui::ConfigureAudio> ui;
 };
diff --git a/src/citra_qt/configuration/configure_audio.ui b/src/citra_qt/configuration/configure_audio.ui
index 0eaaf39a2..3a1a28dd3 100644
--- a/src/citra_qt/configuration/configure_audio.ui
+++ b/src/citra_qt/configuration/configure_audio.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>329</width>
-    <height>344</height>
+    <width>696</width>
+    <height>527</height>
    </rect>
   </property>
   <layout class="QVBoxLayout">
@@ -18,26 +18,53 @@
      </property>
      <layout class="QVBoxLayout">
       <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_emulation">
-        <property name="bottomMargin">
-         <number>0</number>
-        </property>
-        <item>
-         <widget class="QLabel" name="label_emulation">
-          <property name="text">
-           <string>Emulation:</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QComboBox" name="emulation_combo_box"/>
-        </item>
-       </layout>
+      <widget class="QWidget" name="widget_emulation" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout_emulation">
+            <property name="leftMargin">
+            <number>0</number>
+            </property>
+            <property name="topMargin">
+            <number>0</number>
+            </property>
+            <property name="rightMargin">
+            <number>0</number>
+            </property>
+            <property name="bottomMargin">
+            <number>0</number>
+            </property>
+            <item>
+            <widget class="QLabel" name="label_emulation">
+            <property name="text">
+            <string>Emulation:</string>
+            </property>
+            </widget>
+            </item>
+            <item>
+            <widget class="QComboBox" name="emulation_combo_box">
+            <item>
+            <property name="text">
+                <string>HLE (fast)</string>
+            </property>
+            </item>
+            <item>
+            <property name="text">
+                <string>LLE (accurate)</string>
+            </property>
+            </item>
+            <item>
+            <property name="text">
+                <string>LLE multi-core</string>
+            </property>
+            </item>
+            </widget>
+            </item>
+        </layout>
+       </widget>
       </item>
       <item>
-       <layout class="QHBoxLayout">
+       <layout class="QHBoxLayout" name="output_engine_layout">
         <item>
-         <widget class="QLabel" name="label1">
+         <widget class="QLabel" name="output_sink_label">
           <property name="text">
            <string>Output Engine</string>
           </property>
@@ -59,9 +86,9 @@
        </widget>
       </item>
       <item>
-       <layout class="QHBoxLayout">
+       <layout class="QHBoxLayout" name="audio_device_layout">
         <item>
-         <widget class="QLabel" name="label2">
+         <widget class="QLabel" name="audio_device_label">
           <property name="text">
            <string>Audio Device</string>
           </property>
@@ -73,72 +100,97 @@
        </layout>
       </item>
       <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_2">
-        <property name="topMargin">
-         <number>0</number>
-        </property>
-        <item>
-         <widget class="QLabel" name="label">
-          <property name="text">
-           <string>Volume:</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <spacer name="horizontalSpacer">
-          <property name="orientation">
-           <enum>Qt::Horizontal</enum>
-          </property>
-          <property name="sizeHint" stdset="0">
-           <size>
-            <width>40</width>
-            <height>20</height>
-           </size>
-          </property>
-         </spacer>
-        </item>
-        <item>
-         <widget class="QSlider" name="volume_slider">
-          <property name="sizePolicy">
-           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-            <horstretch>0</horstretch>
-            <verstretch>0</verstretch>
-           </sizepolicy>
-          </property>
-          <property name="maximum">
-           <number>100</number>
-          </property>
-          <property name="pageStep">
-           <number>10</number>
-          </property>
-          <property name="orientation">
-           <enum>Qt::Horizontal</enum>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QLabel" name="volume_indicator">
-          <property name="minimumSize">
-           <size>
-            <width>32</width>
-            <height>0</height>
-           </size>
-          </property>
-          <property name="text">
-           <string>0 %</string>
-          </property>
-          <property name="alignment">
-           <set>Qt::AlignCenter</set>
-          </property>
-         </widget>
-        </item>
-       </layout>
+       <widget class="QWidget" name="volume_layout" native="true">
+        <layout class="QHBoxLayout" name="horizontalLayout_2">
+         <property name="leftMargin">
+          <number>0</number>
+         </property>
+         <property name="topMargin">
+          <number>0</number>
+         </property>
+         <property name="rightMargin">
+          <number>0</number>
+         </property>
+         <property name="bottomMargin">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QComboBox" name="volume_combo_box">
+           <item>
+            <property name="text">
+             <string>Use global volume</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>Set volume:</string>
+            </property>
+           </item>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="volume_label">
+           <property name="text">
+            <string>Volume:</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>30</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QSlider" name="volume_slider">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="maximum">
+            <number>100</number>
+           </property>
+           <property name="pageStep">
+            <number>5</number>
+           </property>
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="volume_indicator">
+           <property name="minimumSize">
+            <size>
+             <width>32</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="text">
+            <string>0 %</string>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignCenter</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>
-    <widget class="QGroupBox" name="groupBox_2">
+    <widget class="QGroupBox" name="microphone_layout">
      <property name="title">
       <string>Microphone</string>
      </property>
@@ -146,7 +198,7 @@
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout">
         <item>
-         <widget class="QLabel" name="label_2">
+         <widget class="QLabel" name="input_type_label">
           <property name="text">
            <string>Input Type</string>
           </property>
@@ -176,7 +228,7 @@
       <item>
        <layout class="QHBoxLayout" name="horizontalLayout_3">
         <item>
-         <widget class="QLabel" name="label_3">
+         <widget class="QLabel" name="input_device_label">
           <property name="text">
            <string>Input Device</string>
           </property>
diff --git a/src/citra_qt/configuration/configure_camera.cpp b/src/citra_qt/configuration/configure_camera.cpp
index 1eb0c2604..79f21bb74 100644
--- a/src/citra_qt/configuration/configure_camera.cpp
+++ b/src/citra_qt/configuration/configure_camera.cpp
@@ -9,10 +9,10 @@
 #include <QMessageBox>
 #include <QWidget>
 #include "citra_qt/configuration/configure_camera.h"
+#include "common/settings.h"
 #include "core/frontend/camera/factory.h"
 #include "core/frontend/camera/interface.h"
 #include "core/hle/service/cam/cam.h"
-#include "core/settings.h"
 #include "ui_configure_camera.h"
 
 #if defined(__APPLE__)
diff --git a/src/citra_qt/configuration/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp
index 798734e12..accc61b65 100644
--- a/src/citra_qt/configuration/configure_debug.cpp
+++ b/src/citra_qt/configuration/configure_debug.cpp
@@ -8,11 +8,9 @@
 #include "citra_qt/debugger/console.h"
 #include "citra_qt/uisettings.h"
 #include "common/file_util.h"
-#include "common/logging/backend.h"
-#include "common/logging/filter.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/core.h"
-#include "core/settings.h"
 #include "ui_configure_debug.h"
 
 ConfigureDebug::ConfigureDebug(QWidget* parent)
@@ -32,13 +30,13 @@ ConfigureDebug::ConfigureDebug(QWidget* parent)
 ConfigureDebug::~ConfigureDebug() = default;
 
 void ConfigureDebug::SetConfiguration() {
-    ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub);
-    ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub);
-    ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port);
+    ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue());
+    ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue());
+    ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue());
     ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn());
-    ui->toggle_console->setChecked(UISettings::values.show_console);
-    ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
-    ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
+    ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
+    ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
+    ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit.GetValue());
 }
 
 void ConfigureDebug::ApplyConfiguration() {
@@ -48,7 +46,7 @@ void ConfigureDebug::ApplyConfiguration() {
     Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
     Debugger::ToggleConsole();
     Log::Filter filter;
-    filter.ParseFilterString(Settings::values.log_filter);
+    filter.ParseFilterString(Settings::values.log_filter.GetValue());
     Log::SetGlobalFilter(filter);
     Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
 }
diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp
index 66eb3ec83..80670f2af 100644
--- a/src/citra_qt/configuration/configure_dialog.cpp
+++ b/src/citra_qt/configuration/configure_dialog.cpp
@@ -7,11 +7,13 @@
 #include "citra_qt/configuration/config.h"
 #include "citra_qt/configuration/configure_dialog.h"
 #include "citra_qt/hotkeys.h"
-#include "core/settings.h"
+#include "common/settings.h"
 #include "ui_configure.h"
 
 ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, bool enable_web_config)
     : QDialog(parent), ui(std::make_unique<Ui::ConfigureDialog>()), registry(registry) {
+    Settings::SetConfiguringGlobal(true);
+
     ui->setupUi(this);
     ui->hotkeysTab->Populate(registry);
     ui->webTab->SetWebServiceConfigEnabled(enable_web_config);
diff --git a/src/citra_qt/configuration/configure_enhancements.cpp b/src/citra_qt/configuration/configure_enhancements.cpp
index 0efb86fba..23f3b62d5 100644
--- a/src/citra_qt/configuration/configure_enhancements.cpp
+++ b/src/citra_qt/configuration/configure_enhancements.cpp
@@ -4,8 +4,8 @@
 
 #include <QColorDialog>
 #include "citra_qt/configuration/configure_enhancements.h"
+#include "common/settings.h"
 #include "core/core.h"
-#include "core/settings.h"
 #include "ui_configure_enhancements.h"
 #include "video_core/renderer_opengl/post_processing_opengl.h"
 #include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
@@ -21,7 +21,7 @@ ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
 
     ui->layoutBox->setEnabled(!Settings::values.custom_layout);
 
-    ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer);
+    ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer.GetValue());
 
     connect(ui->render_3d_combobox,
             static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
@@ -50,27 +50,30 @@ ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
 }
 
 void ConfigureEnhancements::SetConfiguration() {
-    ui->resolution_factor_combobox->setCurrentIndex(Settings::values.resolution_factor);
-    ui->render_3d_combobox->setCurrentIndex(static_cast<int>(Settings::values.render_3d));
-    ui->factor_3d->setValue(Settings::values.factor_3d);
-    ui->mono_render_left_eye->setChecked(Settings::values.mono_render_left_eye);
-    updateShaders(Settings::values.render_3d);
-    ui->toggle_linear_filter->setChecked(Settings::values.filter_mode);
+    ui->resolution_factor_combobox->setCurrentIndex(Settings::values.resolution_factor.GetValue());
+    ui->render_3d_combobox->setCurrentIndex(
+        static_cast<int>(Settings::values.render_3d.GetValue()));
+    ui->factor_3d->setValue(Settings::values.factor_3d.GetValue());
+    ui->mono_render_left_eye->setChecked(Settings::values.mono_render_left_eye.GetValue());
+    updateShaders(Settings::values.render_3d.GetValue());
+    ui->toggle_linear_filter->setChecked(Settings::values.filter_mode.GetValue());
     int tex_filter_idx = ui->texture_filter_combobox->findText(
-        QString::fromStdString(Settings::values.texture_filter_name));
+        QString::fromStdString(Settings::values.texture_filter_name.GetValue()));
     if (tex_filter_idx == -1) {
         ui->texture_filter_combobox->setCurrentIndex(0);
     } else {
         ui->texture_filter_combobox->setCurrentIndex(tex_filter_idx);
     }
-    ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option));
-    ui->swap_screen->setChecked(Settings::values.swap_screen);
-    ui->upright_screen->setChecked(Settings::values.upright_screen);
-    ui->toggle_dump_textures->setChecked(Settings::values.dump_textures);
-    ui->toggle_custom_textures->setChecked(Settings::values.custom_textures);
-    ui->toggle_preload_textures->setChecked(Settings::values.preload_textures);
-    bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
-                                Settings::values.bg_blue);
+    ui->layout_combobox->setCurrentIndex(
+        static_cast<int>(Settings::values.layout_option.GetValue()));
+    ui->swap_screen->setChecked(Settings::values.swap_screen.GetValue());
+    ui->upright_screen->setChecked(Settings::values.upright_screen.GetValue());
+    ui->toggle_dump_textures->setChecked(Settings::values.dump_textures.GetValue());
+    ui->toggle_custom_textures->setChecked(Settings::values.custom_textures.GetValue());
+    ui->toggle_preload_textures->setChecked(Settings::values.preload_textures.GetValue());
+    bg_color =
+        QColor::fromRgbF(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
+                         Settings::values.bg_blue.GetValue());
     QPixmap pixmap(ui->bg_button->size());
     pixmap.fill(bg_color);
     const QIcon color_icon(pixmap);
@@ -93,7 +96,7 @@ void ConfigureEnhancements::updateShaders(Settings::StereoRenderOption stereo_op
     for (const auto& shader : OpenGL::GetPostProcessingShaderList(
              stereo_option == Settings::StereoRenderOption::Anaglyph)) {
         ui->shader_combobox->addItem(QString::fromStdString(shader));
-        if (Settings::values.pp_shader_name == shader)
+        if (Settings::values.pp_shader_name.GetValue() == shader)
             ui->shader_combobox->setCurrentIndex(ui->shader_combobox->count() - 1);
     }
 }
diff --git a/src/citra_qt/configuration/configure_enhancements.h b/src/citra_qt/configuration/configure_enhancements.h
index 3541ccf3d..59f9ba1ad 100644
--- a/src/citra_qt/configuration/configure_enhancements.h
+++ b/src/citra_qt/configuration/configure_enhancements.h
@@ -6,9 +6,10 @@
 
 #include <memory>
 #include <QWidget>
+#include "common/common_types.h"
 
 namespace Settings {
-enum class StereoRenderOption;
+enum class StereoRenderOption : u32;
 }
 
 namespace Ui {
diff --git a/src/citra_qt/configuration/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp
index 8fd404c80..daaa44821 100644
--- a/src/citra_qt/configuration/configure_general.cpp
+++ b/src/citra_qt/configuration/configure_general.cpp
@@ -6,10 +6,11 @@
 #include <QFileDialog>
 #include <QMessageBox>
 #include <QUrl>
+#include "citra_qt/configuration/configuration_shared.h"
 #include "citra_qt/configuration/configure_general.h"
 #include "citra_qt/uisettings.h"
-#include "core/core.h"
-#include "core/settings.h"
+#include "common/file_util.h"
+#include "common/settings.h"
 #include "ui_configure_general.h"
 
 // The QSlider doesn't have an easy way to set a custom step amount,
@@ -31,14 +32,17 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
     // Set a minimum width for the label to prevent the slider from changing size.
     // This scales across DPIs, and is acceptable for uncapitalized strings.
     ui->emulation_speed_display_label->setMinimumWidth(tr("unthrottled").size() * 6);
+    ui->emulation_speed_combo->setVisible(!Settings::IsConfiguringGlobal());
+    ui->screenshot_combo->setVisible(!Settings::IsConfiguringGlobal());
 
+    SetupPerGameUI();
     SetConfiguration();
 
     ui->updateBox->setVisible(UISettings::values.updater_found);
     connect(ui->button_reset_defaults, &QPushButton::clicked, this,
             &ConfigureGeneral::ResetDefaults);
 
-    connect(ui->frame_limit, &QSlider::valueChanged, [&](int value) {
+    connect(ui->frame_limit, &QSlider::valueChanged, this, [&](int value) {
         if (value == ui->frame_limit->maximum()) {
             ui->emulation_speed_display_label->setText(tr("unthrottled"));
         } else {
@@ -49,17 +53,6 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
         }
     });
 
-    connect(ui->frame_limit_alternate, &QSlider::valueChanged, [&](int value) {
-        if (value == ui->frame_limit_alternate->maximum()) {
-            ui->emulation_speed_alternate_display_label->setText(tr("unthrottled"));
-        } else {
-            ui->emulation_speed_alternate_display_label->setText(
-                QStringLiteral("%1%")
-                    .arg(SliderToSettings(value))
-                    .rightJustified(tr("unthrottled").size()));
-        }
-    });
-
     connect(ui->change_screenshot_dir, &QToolButton::clicked, this, [this] {
         const QString dir_path = QFileDialog::getExistingDirectory(
             this, tr("Select Screenshot Directory"), ui->screenshot_dir_path->text(),
@@ -73,20 +66,21 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
 ConfigureGeneral::~ConfigureGeneral() = default;
 
 void ConfigureGeneral::SetConfiguration() {
-    ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
-    ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
-    ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse);
+    if (Settings::IsConfiguringGlobal()) {
+        ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue());
+        ui->toggle_background_pause->setChecked(
+            UISettings::values.pause_when_in_background.GetValue());
+        ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse.GetValue());
 
-    ui->toggle_update_check->setChecked(UISettings::values.check_for_update_on_start);
-    ui->toggle_auto_update->setChecked(UISettings::values.update_on_close);
+        ui->toggle_update_check->setChecked(
+            UISettings::values.check_for_update_on_start.GetValue());
+        ui->toggle_auto_update->setChecked(UISettings::values.update_on_close.GetValue());
+    }
 
-    // The first item is "auto-select" with actual value -1, so plus one here will do the trick
-    ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
-
-    if (Settings::values.frame_limit == 0) {
+    if (Settings::values.frame_limit.GetValue() == 0) {
         ui->frame_limit->setValue(ui->frame_limit->maximum());
     } else {
-        ui->frame_limit->setValue(SettingsToSlider(Settings::values.frame_limit));
+        ui->frame_limit->setValue(SettingsToSlider(Settings::values.frame_limit.GetValue()));
     }
     if (ui->frame_limit->value() == ui->frame_limit->maximum()) {
         ui->emulation_speed_display_label->setText(tr("unthrottled"));
@@ -97,32 +91,48 @@ void ConfigureGeneral::SetConfiguration() {
                 .rightJustified(tr("unthrottled").size()));
     }
 
-    ui->toggle_alternate_speed->setChecked(Settings::values.use_frame_limit_alternate);
-
-    if (Settings::values.frame_limit_alternate == 0) {
-        ui->frame_limit_alternate->setValue(ui->frame_limit_alternate->maximum());
+    if (!Settings::IsConfiguringGlobal()) {
+        if (Settings::values.frame_limit.UsingGlobal()) {
+            ui->emulation_speed_combo->setCurrentIndex(0);
+            ui->frame_limit->setEnabled(false);
+        } else {
+            ui->emulation_speed_combo->setCurrentIndex(1);
+            ui->frame_limit->setEnabled(true);
+        }
+        if (UISettings::values.screenshot_path.UsingGlobal()) {
+            ui->screenshot_combo->setCurrentIndex(0);
+            ui->screenshot_dir_path->setEnabled(false);
+            ui->change_screenshot_dir->setEnabled(false);
+        } else {
+            ui->screenshot_combo->setCurrentIndex(1);
+            ui->screenshot_dir_path->setEnabled(true);
+            ui->change_screenshot_dir->setEnabled(true);
+        }
+        ConfigurationShared::SetHighlight(ui->widget_screenshot,
+                                          !UISettings::values.screenshot_path.UsingGlobal());
+        ConfigurationShared::SetHighlight(ui->emulation_speed_layout,
+                                          !Settings::values.frame_limit.UsingGlobal());
+        ConfigurationShared::SetHighlight(ui->widget_region,
+                                          !Settings::values.region_value.UsingGlobal());
+        const bool is_region_global = Settings::values.region_value.UsingGlobal();
+        ui->region_combobox->setCurrentIndex(
+            is_region_global ? ConfigurationShared::USE_GLOBAL_INDEX
+                             : static_cast<int>(Settings::values.region_value.GetValue()) +
+                                   ConfigurationShared::USE_GLOBAL_OFFSET + 1);
     } else {
-        ui->frame_limit_alternate->setValue(
-            SettingsToSlider(Settings::values.frame_limit_alternate));
-    }
-    if (ui->frame_limit_alternate->value() == ui->frame_limit_alternate->maximum()) {
-        ui->emulation_speed_alternate_display_label->setText(tr("unthrottled"));
-    } else {
-        ui->emulation_speed_alternate_display_label->setText(
-            QStringLiteral("%1%")
-                .arg(SliderToSettings(ui->frame_limit_alternate->value()))
-                .rightJustified(tr("unthrottled").size()));
+        // The first item is "auto-select" with actual value -1, so plus one here will do the trick
+        ui->region_combobox->setCurrentIndex(Settings::values.region_value.GetValue() + 1);
     }
 
-    QString screenshot_path = UISettings::values.screenshot_path;
-    if (screenshot_path.isEmpty()) {
-        screenshot_path =
-            QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir));
-        screenshot_path.append(QStringLiteral("screenshots/"));
-        FileUtil::CreateFullPath(screenshot_path.toStdString());
+    UISettings::values.screenshot_path.SetGlobal(ui->screenshot_combo->currentIndex() ==
+                                                 ConfigurationShared::USE_GLOBAL_INDEX);
+    std::string screenshot_path = UISettings::values.screenshot_path.GetValue();
+    if (screenshot_path.empty()) {
+        screenshot_path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir) + "screenshots/";
+        FileUtil::CreateFullPath(screenshot_path);
         UISettings::values.screenshot_path = screenshot_path;
     }
-    ui->screenshot_dir_path->setText(screenshot_path);
+    ui->screenshot_dir_path->setText(QString::fromStdString(screenshot_path));
 }
 
 void ConfigureGeneral::ResetDefaults() {
@@ -139,31 +149,57 @@ void ConfigureGeneral::ResetDefaults() {
 }
 
 void ConfigureGeneral::ApplyConfiguration() {
-    UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
-    UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
-    UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
+    ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_value, ui->region_combobox,
+                                             [](s32 index) { return index - 1; });
 
-    UISettings::values.check_for_update_on_start = ui->toggle_update_check->isChecked();
-    UISettings::values.update_on_close = ui->toggle_auto_update->isChecked();
+    ConfigurationShared::ApplyPerGameSetting(
+        &Settings::values.frame_limit, ui->emulation_speed_combo, [this](s32) {
+            const bool is_maximum = ui->frame_limit->value() == ui->frame_limit->maximum();
+            return is_maximum ? 0 : SliderToSettings(ui->frame_limit->value());
+        });
 
-    UISettings::values.screenshot_path = ui->screenshot_dir_path->text();
+    ConfigurationShared::ApplyPerGameSetting(
+        &UISettings::values.screenshot_path, ui->screenshot_combo,
+        [this](s32) { return ui->screenshot_dir_path->text().toStdString(); });
 
-    Settings::values.region_value = ui->region_combobox->currentIndex() - 1;
+    if (Settings::IsConfiguringGlobal()) {
+        UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
+        UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
+        UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
 
-    if (ui->frame_limit->value() == ui->frame_limit->maximum()) {
-        Settings::values.frame_limit = 0;
-    } else {
-        Settings::values.frame_limit = SliderToSettings(ui->frame_limit->value());
-    }
-    Settings::values.use_frame_limit_alternate = ui->toggle_alternate_speed->isChecked();
-    if (ui->frame_limit_alternate->value() == ui->frame_limit_alternate->maximum()) {
-        Settings::values.frame_limit_alternate = 0;
-    } else {
-        Settings::values.frame_limit_alternate =
-            SliderToSettings(ui->frame_limit_alternate->value());
+        UISettings::values.check_for_update_on_start = ui->toggle_update_check->isChecked();
+        UISettings::values.update_on_close = ui->toggle_auto_update->isChecked();
     }
 }
 
 void ConfigureGeneral::RetranslateUI() {
     ui->retranslateUi(this);
 }
+
+void ConfigureGeneral::SetupPerGameUI() {
+    if (Settings::IsConfiguringGlobal()) {
+        ui->region_combobox->setEnabled(Settings::values.region_value.UsingGlobal());
+        ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal());
+        return;
+    }
+
+    connect(ui->emulation_speed_combo, qOverload<int>(&QComboBox::activated), this,
+            [this](int index) {
+                ui->frame_limit->setEnabled(index == 1);
+                ConfigurationShared::SetHighlight(ui->emulation_speed_layout, index == 1);
+            });
+
+    connect(ui->screenshot_combo, qOverload<int>(&QComboBox::activated), this, [this](int index) {
+        ui->screenshot_dir_path->setEnabled(index == 1);
+        ui->change_screenshot_dir->setEnabled(index == 1);
+        ConfigurationShared::SetHighlight(ui->widget_screenshot, index == 1);
+    });
+
+    ui->general_group->setVisible(false);
+    ui->updateBox->setVisible(false);
+    ui->button_reset_defaults->setVisible(false);
+
+    ConfigurationShared::SetColoredComboBox(
+        ui->region_combobox, ui->widget_region,
+        static_cast<u32>(Settings::values.region_value.GetValue(true) + 1));
+}
diff --git a/src/citra_qt/configuration/configure_general.h b/src/citra_qt/configuration/configure_general.h
index d3bae474a..3c6cef219 100644
--- a/src/citra_qt/configuration/configure_general.h
+++ b/src/citra_qt/configuration/configure_general.h
@@ -25,6 +25,8 @@ public:
     void RetranslateUI();
     void SetConfiguration();
 
+    void SetupPerGameUI();
+
 private:
     std::unique_ptr<Ui::ConfigureGeneral> ui;
 };
diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui
index 5cb817e1d..4452d69e4 100644
--- a/src/citra_qt/configuration/configure_general.ui
+++ b/src/citra_qt/configuration/configure_general.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>408</width>
-    <height>436</height>
+    <width>524</width>
+    <height>578</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -17,7 +17,7 @@
    <item>
     <layout class="QVBoxLayout" name="verticalLayout">
      <item>
-      <widget class="QGroupBox" name="groupBox">
+      <widget class="QGroupBox" name="general_group">
        <property name="title">
         <string>General</string>
        </property>
@@ -70,144 +70,152 @@
       </widget>
      </item>
      <item>
-      <widget class="QGroupBox" name="groupBox_4">
+      <widget class="QGroupBox" name="emulation_group">
        <property name="title">
         <string>Emulation</string>
        </property>
-       <layout class="QGridLayout" name="gridLayout">
-        <item row="0" column="2">
-         <widget class="QComboBox" name="region_combobox">
-          <item>
-           <property name="text">
-            <string>Auto-select</string>
+       <layout class="QVBoxLayout" name="verticalLayout_2">
+        <item>
+         <widget class="QWidget" name="widget_region" native="true">
+          <layout class="QHBoxLayout" name="region_layout">
+           <property name="leftMargin">
+            <number>0</number>
            </property>
-          </item>
-          <item>
-           <property name="text">
-            <string notr="true">JPN</string>
+           <property name="topMargin">
+            <number>0</number>
            </property>
-          </item>
-          <item>
-           <property name="text">
-            <string notr="true">USA</string>
+           <property name="rightMargin">
+            <number>0</number>
            </property>
-          </item>
-          <item>
-           <property name="text">
-            <string notr="true">EUR</string>
+           <property name="bottomMargin">
+            <number>0</number>
            </property>
-          </item>
-          <item>
-           <property name="text">
-            <string notr="true">AUS</string>
-           </property>
-          </item>
-          <item>
-           <property name="text">
-            <string notr="true">CHN</string>
-           </property>
-          </item>
-          <item>
-           <property name="text">
-            <string notr="true">KOR</string>
-           </property>
-          </item>
-          <item>
-           <property name="text">
-            <string notr="true">TWN</string>
-           </property>
-          </item>
+           <item>
+            <widget class="QLabel" name="region_label">
+             <property name="text">
+              <string>Region:</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QComboBox" name="region_combobox">
+             <item>
+              <property name="text">
+               <string>Auto-select</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">JPN</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">USA</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">EUR</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">AUS</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">CHN</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">KOR</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">TWN</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+          </layout>
          </widget>
         </item>
-        <item row="1" column="0">
-         <widget class="QLabel" name="label_emulation_speed">
-          <property name="text">
-           <string>Emulation Speed:</string>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="0">
-         <widget class="QCheckBox" name="toggle_alternate_speed">
-          <property name="text">
-           <string>Use Alternate Speed:</string>
-          </property>
-         </widget>
-        </item>
-        <item row="1" column="2">
-         <widget class="QSlider" name="frame_limit">
-          <property name="minimum">
-           <number>0</number>
-          </property>
-          <property name="maximum">
-           <number>199</number>
-          </property>
-          <property name="singleStep">
-           <number>5</number>
-          </property>
-          <property name="pageStep">
-           <number>15</number>
-          </property>
-          <property name="value">
-           <number>19</number>
-          </property>
-          <property name="orientation">
-           <enum>Qt::Horizontal</enum>
-          </property>
-          <property name="tickPosition">
-           <enum>QSlider::TicksBelow</enum>
-          </property>
-         </widget>
-        </item>
-        <item row="1" column="1">
-         <widget class="QLabel" name="emulation_speed_display_label">
-          <property name="text">
-           <string/>
-          </property>
-          <property name="alignment">
-           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-          </property>
-         </widget>
-        </item>
-        <item row="0" column="0">
-         <widget class="QLabel" name="label">
-          <property name="text">
-           <string>Region:</string>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="2">
-         <widget class="QSlider" name="frame_limit_alternate">
-          <property name="minimum">
-           <number>0</number>
-          </property>
-          <property name="maximum">
-           <number>199</number>
-          </property>
-          <property name="singleStep">
-           <number>5</number>
-          </property>
-          <property name="pageStep">
-           <number>15</number>
-          </property>
-          <property name="value">
-           <number>39</number>
-          </property>
-          <property name="orientation">
-           <enum>Qt::Horizontal</enum>
-          </property>
-          <property name="tickPosition">
-           <enum>QSlider::TicksBelow</enum>
-          </property>
-         </widget>
-        </item>
-        <item row="2" column="1">
-         <widget class="QLabel" name="emulation_speed_alternate_display_label">
-          <property name="text">
-           <string/>
-          </property>
-          <property name="alignment">
-           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-          </property>
+        <item>
+         <widget class="QWidget" name="emulation_speed_layout" native="true">
+          <layout class="QHBoxLayout" name="emulation_speed_layout_inner">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <item>
+            <widget class="QComboBox" name="emulation_speed_combo">
+             <item>
+              <property name="text">
+               <string>Use global emulation speed</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>Set emulation speed:</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLabel" name="label_emulation_speed">
+             <property name="text">
+              <string>Emulation Speed:</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QSlider" name="frame_limit">
+             <property name="minimum">
+              <number>0</number>
+             </property>
+             <property name="maximum">
+              <number>199</number>
+             </property>
+             <property name="singleStep">
+              <number>5</number>
+             </property>
+             <property name="pageStep">
+              <number>15</number>
+             </property>
+             <property name="value">
+              <number>19</number>
+             </property>
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="tickPosition">
+              <enum>QSlider::TicksBelow</enum>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLabel" name="emulation_speed_display_label">
+             <property name="text">
+              <string/>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             </property>
+            </widget>
+           </item>
+          </layout>
          </widget>
         </item>
        </layout>
@@ -218,23 +226,57 @@
        <property name="title">
         <string>Screenshots</string>
        </property>
-       <layout class="QHBoxLayout" name="horizontalLayout_2">
-        <item>
-         <widget class="QLabel" name="label_2">
-          <property name="text">
-           <string>Save Screenshots To</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QLineEdit" name="screenshot_dir_path">
-         </widget>
-        </item>
-        <item>
-         <widget class="QToolButton" name="change_screenshot_dir">
-          <property name="text">
-           <string>...</string>
-          </property>
+       <layout class="QGridLayout" name="gridLayout">
+        <property name="verticalSpacing">
+         <number>0</number>
+        </property>
+        <item row="0" column="1">
+         <widget class="QWidget" name="widget_screenshot" native="true">
+          <layout class="QHBoxLayout" name="screenshot_layout">
+           <property name="leftMargin">
+            <number>0</number>
+           </property>
+           <property name="topMargin">
+            <number>0</number>
+           </property>
+           <property name="rightMargin">
+            <number>0</number>
+           </property>
+           <property name="bottomMargin">
+            <number>0</number>
+           </property>
+           <item>
+            <widget class="QComboBox" name="screenshot_combo">
+             <item>
+              <property name="text">
+               <string>Use global screenshot path</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>Set screenshot path:</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLabel" name="screenshot_dir_label">
+             <property name="text">
+              <string>Save Screenshots To</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLineEdit" name="screenshot_dir_path"/>
+           </item>
+           <item>
+            <widget class="QToolButton" name="change_screenshot_dir">
+             <property name="text">
+              <string>...</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
          </widget>
         </item>
        </layout>
@@ -270,12 +312,6 @@
   <tabstop>toggle_hide_mouse</tabstop>
   <tabstop>toggle_update_check</tabstop>
   <tabstop>toggle_auto_update</tabstop>
-  <tabstop>region_combobox</tabstop>
-  <tabstop>frame_limit</tabstop>
-  <tabstop>toggle_alternate_speed</tabstop>
-  <tabstop>frame_limit_alternate</tabstop>
-  <tabstop>screenshot_dir_path</tabstop>
-  <tabstop>change_screenshot_dir</tabstop>
   <tabstop>button_reset_defaults</tabstop>
  </tabstops>
  <resources/>
diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp
index 75602a8b4..58c281490 100644
--- a/src/citra_qt/configuration/configure_graphics.cpp
+++ b/src/citra_qt/configuration/configure_graphics.cpp
@@ -6,22 +6,25 @@
 #ifdef __APPLE__
 #include <QMessageBox>
 #endif
+#include "citra_qt/configuration/configuration_shared.h"
 #include "citra_qt/configuration/configure_graphics.h"
+#include "common/settings.h"
 #include "core/core.h"
-#include "core/settings.h"
 #include "ui_configure_graphics.h"
-#include "video_core/renderer_opengl/post_processing_opengl.h"
 
 ConfigureGraphics::ConfigureGraphics(QWidget* parent)
     : QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) {
     ui->setupUi(this);
+
+    SetupPerGameUI();
     SetConfiguration();
 
-    ui->hw_renderer_group->setEnabled(ui->toggle_hw_renderer->isChecked());
+    ui->hw_renderer_group->setEnabled(ui->hw_renderer_group->isEnabled() &&
+                                      ui->toggle_hw_renderer->isChecked());
     ui->toggle_vsync_new->setEnabled(!Core::System::GetInstance().IsPoweredOn());
 
     connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] {
-        auto checked = ui->toggle_hw_renderer->isChecked();
+        const bool checked = ui->toggle_hw_renderer->isChecked();
         ui->hw_renderer_group->setEnabled(checked);
         ui->toggle_disk_shader_cache->setEnabled(checked && ui->toggle_hw_shader->isChecked());
     });
@@ -31,7 +34,7 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
                                              ui->toggle_hw_shader->isChecked());
 
     connect(ui->toggle_hw_shader, &QCheckBox::toggled, this, [this] {
-        auto checked = ui->toggle_hw_shader->isChecked();
+        const bool checked = ui->toggle_hw_shader->isChecked();
         ui->hw_shader_group->setEnabled(checked);
         ui->toggle_disk_shader_cache->setEnabled(checked);
     });
@@ -62,25 +65,67 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
 ConfigureGraphics::~ConfigureGraphics() = default;
 
 void ConfigureGraphics::SetConfiguration() {
-    ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer);
-    ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader);
-    ui->toggle_separable_shader->setChecked(Settings::values.separable_shader);
-    ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul);
-    ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit);
-    ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
-    ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new);
+    ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer.GetValue());
+    ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader.GetValue());
+    ui->toggle_separable_shader->setChecked(Settings::values.separable_shader.GetValue());
+    ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul.GetValue());
+    ui->toggle_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
+    ui->toggle_vsync_new->setChecked(Settings::values.use_vsync_new.GetValue());
+
+    if (Settings::IsConfiguringGlobal()) {
+        ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit.GetValue());
+    }
 }
 
 void ConfigureGraphics::ApplyConfiguration() {
-    Settings::values.use_hw_renderer = ui->toggle_hw_renderer->isChecked();
-    Settings::values.use_hw_shader = ui->toggle_hw_shader->isChecked();
-    Settings::values.separable_shader = ui->toggle_separable_shader->isChecked();
-    Settings::values.shaders_accurate_mul = ui->toggle_accurate_mul->isChecked();
-    Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
-    Settings::values.use_disk_shader_cache = ui->toggle_disk_shader_cache->isChecked();
-    Settings::values.use_vsync_new = ui->toggle_vsync_new->isChecked();
+    ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_hw_renderer,
+                                             ui->toggle_hw_renderer, use_hw_renderer);
+    ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_hw_shader, ui->toggle_hw_shader,
+                                             use_hw_shader);
+    ConfigurationShared::ApplyPerGameSetting(&Settings::values.separable_shader,
+                                             ui->toggle_separable_shader, separable_shader);
+    ConfigurationShared::ApplyPerGameSetting(&Settings::values.shaders_accurate_mul,
+                                             ui->toggle_accurate_mul, shaders_accurate_mul);
+    ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache,
+                                             ui->toggle_disk_shader_cache, use_disk_shader_cache);
+    ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync_new, ui->toggle_vsync_new,
+                                             use_vsync_new);
+
+    if (Settings::IsConfiguringGlobal()) {
+        Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
+    }
 }
 
 void ConfigureGraphics::RetranslateUI() {
     ui->retranslateUi(this);
 }
+
+void ConfigureGraphics::SetupPerGameUI() {
+    // Block the global settings if a game is currently running that overrides them
+    if (Settings::IsConfiguringGlobal()) {
+        ui->toggle_hw_renderer->setEnabled(Settings::values.use_hw_renderer.UsingGlobal());
+        ui->toggle_hw_shader->setEnabled(Settings::values.use_hw_shader.UsingGlobal());
+        ui->toggle_separable_shader->setEnabled(Settings::values.separable_shader.UsingGlobal());
+        ui->toggle_accurate_mul->setEnabled(Settings::values.shaders_accurate_mul.UsingGlobal());
+        ui->toggle_disk_shader_cache->setEnabled(
+            Settings::values.use_disk_shader_cache.UsingGlobal());
+        ui->toggle_vsync_new->setEnabled(Settings::values.use_vsync_new.UsingGlobal());
+        return;
+    }
+
+    ui->toggle_shader_jit->setVisible(false);
+
+    ConfigurationShared::SetColoredTristate(ui->toggle_hw_renderer,
+                                            Settings::values.use_hw_renderer, use_hw_renderer);
+    ConfigurationShared::SetColoredTristate(ui->toggle_hw_shader, Settings::values.use_hw_shader,
+                                            use_hw_shader);
+    ConfigurationShared::SetColoredTristate(ui->toggle_separable_shader,
+                                            Settings::values.separable_shader, separable_shader);
+    ConfigurationShared::SetColoredTristate(
+        ui->toggle_accurate_mul, Settings::values.shaders_accurate_mul, shaders_accurate_mul);
+    ConfigurationShared::SetColoredTristate(ui->toggle_disk_shader_cache,
+                                            Settings::values.use_disk_shader_cache,
+                                            use_disk_shader_cache);
+    ConfigurationShared::SetColoredTristate(ui->toggle_vsync_new, Settings::values.use_vsync_new,
+                                            use_vsync_new);
+}
diff --git a/src/citra_qt/configuration/configure_graphics.h b/src/citra_qt/configuration/configure_graphics.h
index 9ed932090..2517e1553 100644
--- a/src/citra_qt/configuration/configure_graphics.h
+++ b/src/citra_qt/configuration/configure_graphics.h
@@ -11,6 +11,10 @@ namespace Ui {
 class ConfigureGraphics;
 }
 
+namespace ConfigurationShared {
+enum class CheckState;
+}
+
 class ConfigureGraphics : public QWidget {
     Q_OBJECT
 
@@ -24,6 +28,14 @@ public:
 
     void UpdateBackgroundColorButton(const QColor& color);
 
+    void SetupPerGameUI();
+
+    ConfigurationShared::CheckState use_hw_renderer;
+    ConfigurationShared::CheckState use_hw_shader;
+    ConfigurationShared::CheckState separable_shader;
+    ConfigurationShared::CheckState shaders_accurate_mul;
+    ConfigurationShared::CheckState use_disk_shader_cache;
+    ConfigurationShared::CheckState use_vsync_new;
     std::unique_ptr<Ui::ConfigureGraphics> ui;
     QColor bg_color;
 };
diff --git a/src/citra_qt/configuration/configure_hotkeys.cpp b/src/citra_qt/configuration/configure_hotkeys.cpp
index 1479283f0..287e7baa4 100644
--- a/src/citra_qt/configuration/configure_hotkeys.cpp
+++ b/src/citra_qt/configuration/configure_hotkeys.cpp
@@ -7,7 +7,7 @@
 #include "citra_qt/configuration/configure_hotkeys.h"
 #include "citra_qt/hotkeys.h"
 #include "citra_qt/util/sequence_dialog/sequence_dialog.h"
-#include "core/settings.h"
+#include "common/settings.h"
 #include "ui_configure_hotkeys.h"
 
 ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h
index a08510fb1..b6867a39c 100644
--- a/src/citra_qt/configuration/configure_input.h
+++ b/src/citra_qt/configuration/configure_input.h
@@ -13,7 +13,7 @@
 #include <QKeySequence>
 #include <QWidget>
 #include "common/param_package.h"
-#include "core/settings.h"
+#include "common/settings.h"
 #include "input_common/main.h"
 
 class QKeyEvent;
diff --git a/src/citra_qt/configuration/configure_motion_touch.h b/src/citra_qt/configuration/configure_motion_touch.h
index 2aefd393e..3b10f752a 100644
--- a/src/citra_qt/configuration/configure_motion_touch.h
+++ b/src/citra_qt/configuration/configure_motion_touch.h
@@ -7,7 +7,7 @@
 #include <memory>
 #include <QDialog>
 #include "common/param_package.h"
-#include "core/settings.h"
+#include "common/settings.h"
 #include "input_common/main.h"
 #include "input_common/udp/udp.h"
 
diff --git a/src/citra_qt/configuration/configure_per_game.cpp b/src/citra_qt/configuration/configure_per_game.cpp
new file mode 100644
index 000000000..63421550c
--- /dev/null
+++ b/src/citra_qt/configuration/configure_per_game.cpp
@@ -0,0 +1,128 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <utility>
+#include <vector>
+#include <QPushButton>
+#include <QString>
+#include <fmt/format.h>
+#include "citra_qt/configuration/config.h"
+#include "citra_qt/configuration/configure_audio.h"
+#include "citra_qt/configuration/configure_general.h"
+#include "citra_qt/configuration/configure_graphics.h"
+#include "citra_qt/configuration/configure_per_game.h"
+#include "citra_qt/configuration/configure_system.h"
+#include "citra_qt/util/util.h"
+#include "core/core.h"
+#include "core/loader/loader.h"
+#include "core/loader/smdh.h"
+#include "ui_configure_per_game.h"
+
+ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name,
+                                   Core::System& system_)
+    : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()),
+      filename{file_name.toStdString()}, title_id{title_id_}, system{system_} {
+    const auto config_file_name = title_id == 0 ? filename : fmt::format("{:016X}", title_id);
+    game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
+
+    audio_tab = std::make_unique<ConfigureAudio>(this);
+    general_tab = std::make_unique<ConfigureGeneral>(this);
+    graphics_tab = std::make_unique<ConfigureGraphics>(this);
+    system_tab = std::make_unique<ConfigureSystem>(this);
+
+    ui->setupUi(this);
+
+    ui->tabWidget->addTab(general_tab.get(), tr("General"));
+    ui->tabWidget->addTab(system_tab.get(), tr("System"));
+    ui->tabWidget->addTab(graphics_tab.get(), tr("Graphics"));
+    ui->tabWidget->addTab(audio_tab.get(), tr("Audio"));
+
+    setFocusPolicy(Qt::ClickFocus);
+    setWindowTitle(tr("Properties"));
+    // remove Help question mark button from the title bar
+    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+    scene = new QGraphicsScene;
+    ui->icon_view->setScene(scene);
+
+    if (system.IsPoweredOn()) {
+        QPushButton* apply_button = ui->buttonBox->addButton(QDialogButtonBox::Apply);
+        connect(apply_button, &QAbstractButton::clicked, this,
+                &ConfigurePerGame::HandleApplyButtonClicked);
+    }
+
+    LoadConfiguration();
+}
+
+ConfigurePerGame::~ConfigurePerGame() = default;
+
+void ConfigurePerGame::ApplyConfiguration() {
+    general_tab->ApplyConfiguration();
+    system_tab->ApplyConfiguration();
+    graphics_tab->ApplyConfiguration();
+    audio_tab->ApplyConfiguration();
+
+    Settings::LogSettings();
+
+    game_config->Save();
+}
+
+void ConfigurePerGame::changeEvent(QEvent* event) {
+    if (event->type() == QEvent::LanguageChange) {
+        RetranslateUI();
+    }
+
+    QDialog::changeEvent(event);
+}
+
+void ConfigurePerGame::RetranslateUI() {
+    ui->retranslateUi(this);
+}
+
+void ConfigurePerGame::HandleApplyButtonClicked() {
+    ApplyConfiguration();
+}
+
+static QPixmap GetQPixmapFromSMDH(std::vector<u8>& smdh_data) {
+    Loader::SMDH smdh;
+    memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
+
+    bool large = true;
+    std::vector<u16> icon_data = smdh.GetIcon(large);
+    const uchar* data = reinterpret_cast<const uchar*>(icon_data.data());
+    int size = large ? 48 : 24;
+    QImage icon(data, size, size, QImage::Format::Format_RGB16);
+    return QPixmap::fromImage(icon);
+}
+
+void ConfigurePerGame::LoadConfiguration() {
+    if (filename.empty()) {
+        return;
+    }
+
+    ui->display_title_id->setText(
+        QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper());
+
+    const auto loader = Loader::GetLoader(filename);
+
+    std::string title;
+    if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
+        ui->display_name->setText(QString::fromStdString(title));
+
+    std::vector<u8> bytes;
+    if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
+        scene->clear();
+
+        QPixmap map = GetQPixmapFromSMDH(bytes);
+        scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
+                                    Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+    }
+
+    ui->display_filepath->setText(QString::fromStdString(filename));
+
+    ui->display_format->setText(
+        QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
+
+    const auto valueText = ReadableByteSize(FileUtil::GetSize(filename));
+    ui->display_size->setText(valueText);
+}
diff --git a/src/citra_qt/configuration/configure_per_game.h b/src/citra_qt/configuration/configure_per_game.h
new file mode 100644
index 000000000..9b1b14306
--- /dev/null
+++ b/src/citra_qt/configuration/configure_per_game.h
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <QDialog>
+#include <QList>
+#include "citra_qt/configuration/config.h"
+
+namespace Core {
+class System;
+}
+
+class ConfigureAudio;
+class ConfigureGeneral;
+class ConfigureGraphics;
+class ConfigureSystem;
+
+class QGraphicsScene;
+class QStandardItem;
+class QStandardItemModel;
+class QTreeView;
+class QVBoxLayout;
+
+namespace Ui {
+class ConfigurePerGame;
+}
+
+class ConfigurePerGame : public QDialog {
+    Q_OBJECT
+
+public:
+    // Cannot use std::filesystem::path due to https://bugreports.qt.io/browse/QTBUG-73263
+    explicit ConfigurePerGame(QWidget* parent, u64 title_id_, const QString& file_name,
+                              Core::System& system_);
+    ~ConfigurePerGame() override;
+
+    /// Loads all button configurations to settings file
+    void LoadConfiguration();
+
+    /// Save all button configurations to settings file
+    void ApplyConfiguration();
+
+private:
+    void changeEvent(QEvent* event) override;
+    void RetranslateUI();
+
+    void HandleApplyButtonClicked();
+
+    std::unique_ptr<Ui::ConfigurePerGame> ui;
+    std::string filename;
+    u64 title_id;
+
+    QGraphicsScene* scene;
+
+    std::unique_ptr<Config> game_config;
+
+    Core::System& system;
+
+    std::unique_ptr<ConfigureAudio> audio_tab;
+    std::unique_ptr<ConfigureGeneral> general_tab;
+    std::unique_ptr<ConfigureGraphics> graphics_tab;
+    std::unique_ptr<ConfigureSystem> system_tab;
+};
diff --git a/src/citra_qt/configuration/configure_per_game.ui b/src/citra_qt/configuration/configure_per_game.ui
new file mode 100644
index 000000000..a193afd65
--- /dev/null
+++ b/src/citra_qt/configuration/configure_per_game.ui
@@ -0,0 +1,264 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigurePerGame</class>
+ <widget class="QDialog" name="ConfigurePerGame">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>900</width>
+    <height>661</height>
+   </rect>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>900</width>
+    <height>0</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_3">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QGroupBox" name="groupBox">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="title">
+        <string>Info</string>
+       </property>
+       <layout class="QVBoxLayout" name="verticalLayout">
+        <item>
+         <layout class="QGridLayout" name="gridLayout">
+          <item row="0" column="0">
+           <widget class="QGraphicsView" name="icon_view">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>128</width>
+              <height>128</height>
+             </size>
+            </property>
+            <property name="maximumSize">
+             <size>
+              <width>128</width>
+              <height>128</height>
+             </size>
+            </property>
+            <property name="verticalScrollBarPolicy">
+             <enum>Qt::ScrollBarAlwaysOff</enum>
+            </property>
+            <property name="horizontalScrollBarPolicy">
+             <enum>Qt::ScrollBarAlwaysOff</enum>
+            </property>
+            <property name="interactive">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <layout class="QGridLayout" name="gridLayout_2">
+          <item row="4" column="0">
+           <widget class="QLabel" name="label_6">
+            <property name="text">
+             <string>Size</string>
+            </property>
+           </widget>
+          </item>
+          <item row="5" column="1">
+           <widget class="QLineEdit" name="display_filepath">
+            <property name="enabled">
+             <bool>true</bool>
+            </property>
+            <property name="minimumSize">
+             <size>
+              <width>160</width>
+              <height>0</height>
+             </size>
+            </property>
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="0">
+           <widget class="QLabel" name="label_5">
+            <property name="text">
+             <string>Format</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="label">
+            <property name="text">
+             <string>Name</string>
+            </property>
+           </widget>
+          </item>
+          <item row="4" column="1">
+           <widget class="QLineEdit" name="display_size">
+            <property name="enabled">
+             <bool>true</bool>
+            </property>
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="5" column="0">
+           <widget class="QLabel" name="label_7">
+            <property name="text">
+             <string>Filepath</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="label_4">
+            <property name="text">
+             <string>Title ID</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="QLineEdit" name="display_title_id">
+            <property name="enabled">
+             <bool>true</bool>
+            </property>
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QLineEdit" name="display_name">
+            <property name="enabled">
+             <bool>true</bool>
+            </property>
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="1">
+           <widget class="QLineEdit" name="display_format">
+            <property name="enabled">
+             <bool>true</bool>
+            </property>
+            <property name="readOnly">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <spacer name="verticalSpacer">
+          <property name="orientation">
+           <enum>Qt::Vertical</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>20</width>
+            <height>40</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <layout class="QVBoxLayout" name="VerticalLayout">
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_2"/>
+       </item>
+       <item>
+        <widget class="QTabWidget" name="tabWidget">
+         <property name="enabled">
+          <bool>true</bool>
+         </property>
+         <property name="currentIndex">
+          <number>-1</number>
+         </property>
+         <property name="usesScrollButtons">
+          <bool>true</bool>
+         </property>
+         <property name="documentMode">
+          <bool>false</bool>
+         </property>
+         <property name="tabsClosable">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ConfigurePerGame</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ConfigurePerGame</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/src/citra_qt/configuration/configure_storage.cpp b/src/citra_qt/configuration/configure_storage.cpp
index 043254350..694395ead 100644
--- a/src/citra_qt/configuration/configure_storage.cpp
+++ b/src/citra_qt/configuration/configure_storage.cpp
@@ -6,8 +6,8 @@
 #include <QFileDialog>
 #include <QUrl>
 #include "citra_qt/configuration/configure_storage.h"
+#include "common/settings.h"
 #include "core/core.h"
-#include "core/settings.h"
 #include "ui_configure_storage.h"
 
 ConfigureStorage::ConfigureStorage(QWidget* parent)
@@ -60,7 +60,7 @@ ConfigureStorage::ConfigureStorage(QWidget* parent)
 ConfigureStorage::~ConfigureStorage() = default;
 
 void ConfigureStorage::SetConfiguration() {
-    ui->nand_group->setVisible(Settings::values.use_custom_storage);
+    ui->nand_group->setVisible(Settings::values.use_custom_storage.GetValue());
     QString nand_path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
     ui->nand_dir_path->setText(nand_path);
     ui->open_nand_dir->setEnabled(!nand_path.isEmpty());
@@ -71,8 +71,8 @@ void ConfigureStorage::SetConfiguration() {
     ui->sdmc_dir_path->setText(sdmc_path);
     ui->open_sdmc_dir->setEnabled(!sdmc_path.isEmpty());
 
-    ui->toggle_virtual_sd->setChecked(Settings::values.use_virtual_sd);
-    ui->toggle_custom_storage->setChecked(Settings::values.use_custom_storage);
+    ui->toggle_virtual_sd->setChecked(Settings::values.use_virtual_sd.GetValue());
+    ui->toggle_custom_storage->setChecked(Settings::values.use_custom_storage.GetValue());
 
     ui->storage_group->setEnabled(!Core::System::GetInstance().IsPoweredOn());
 }
diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp
index 11b4fe77b..cfde68c2d 100644
--- a/src/citra_qt/configuration/configure_system.cpp
+++ b/src/citra_qt/configuration/configure_system.cpp
@@ -4,12 +4,12 @@
 
 #include <cstring>
 #include <QMessageBox>
+#include "citra_qt/configuration/configuration_shared.h"
 #include "citra_qt/configuration/configure_system.h"
-#include "citra_qt/uisettings.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/hle/service/cfg/cfg.h"
 #include "core/hle/service/ptm/ptm.h"
-#include "core/settings.h"
 #include "ui_configure_system.h"
 
 static const std::array<int, 12> days_in_month = {{
@@ -249,10 +249,14 @@ ConfigureSystem::ConfigureSystem(QWidget* parent)
     // This scales across DPIs. (This value should be enough for "xxx%")
     ui->clock_display_label->setMinimumWidth(40);
 
-    connect(ui->slider_clock_speed, &QSlider::valueChanged, [&](int value) {
+    connect(ui->slider_clock_speed, &QSlider::valueChanged, this, [&](int value) {
         ui->clock_display_label->setText(QStringLiteral("%1%").arg(SliderToSettings(value)));
     });
 
+    ui->clock_speed_label->setVisible(Settings::IsConfiguringGlobal());
+    ui->clock_speed_combo->setVisible(!Settings::IsConfiguringGlobal());
+
+    SetupPerGameUI();
     ConfigureTime();
 }
 
@@ -261,12 +265,12 @@ ConfigureSystem::~ConfigureSystem() = default;
 void ConfigureSystem::SetConfiguration() {
     enabled = !Core::System::GetInstance().IsPoweredOn();
 
-    ui->combo_init_clock->setCurrentIndex(static_cast<u8>(Settings::values.init_clock));
+    ui->combo_init_clock->setCurrentIndex(static_cast<u8>(Settings::values.init_clock.GetValue()));
     QDateTime date_time;
-    date_time.setTime_t(Settings::values.init_time);
+    date_time.setTime_t(Settings::values.init_time.GetValue());
     ui->edit_init_time->setDateTime(date_time);
 
-    long long init_time_offset = Settings::values.init_time_offset;
+    long long init_time_offset = Settings::values.init_time_offset.GetValue();
     long long days_offset = init_time_offset / 86400;
     ui->edit_init_time_offset_days->setValue(days_offset);
 
@@ -287,20 +291,29 @@ void ConfigureSystem::SetConfiguration() {
         ui->label_disable_info->hide();
     }
 
-    ui->slider_clock_speed->setValue(SettingsToSlider(Settings::values.cpu_clock_percentage));
-    ui->clock_display_label->setText(
-        QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage));
+    if (!Settings::IsConfiguringGlobal()) {
+        if (Settings::values.cpu_clock_percentage.UsingGlobal()) {
+            ui->clock_speed_combo->setCurrentIndex(0);
+            ui->slider_clock_speed->setEnabled(false);
+        } else {
+            ui->clock_speed_combo->setCurrentIndex(1);
+            ui->slider_clock_speed->setEnabled(true);
+        }
+        ConfigurationShared::SetHighlight(ui->clock_speed_widget,
+                                          !Settings::values.cpu_clock_percentage.UsingGlobal());
+    }
 
-    ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds);
+    ui->slider_clock_speed->setValue(
+        SettingsToSlider(Settings::values.cpu_clock_percentage.GetValue()));
+    ui->clock_display_label->setText(
+        QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue()));
+    ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds.GetValue());
 }
 
 void ConfigureSystem::ReadSystemSettings() {
     // set username
     username = cfg->GetUsername();
-    // TODO(wwylele): Use this when we move to Qt 5.5
-    // ui->edit_username->setText(QString::fromStdU16String(username));
-    ui->edit_username->setText(
-        QString::fromUtf16(reinterpret_cast<const ushort*>(username.data())));
+    ui->edit_username->setText(QString::fromStdU16String(username));
 
     // set birthday
     std::tie(birthmonth, birthday) = cfg->GetBirthday();
@@ -337,32 +350,29 @@ void ConfigureSystem::ApplyConfiguration() {
         bool modified = false;
 
         // apply username
-        // TODO(wwylele): Use this when we move to Qt 5.5
-        // std::u16string new_username = ui->edit_username->text().toStdU16String();
-        std::u16string new_username(
-            reinterpret_cast<const char16_t*>(ui->edit_username->text().utf16()));
+        std::u16string new_username = ui->edit_username->text().toStdU16String();
         if (new_username != username) {
             cfg->SetUsername(new_username);
             modified = true;
         }
 
         // apply birthday
-        int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
-        int new_birthday = ui->combo_birthday->currentIndex() + 1;
+        s32 new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
+        s32 new_birthday = ui->combo_birthday->currentIndex() + 1;
         if (birthmonth != new_birthmonth || birthday != new_birthday) {
             cfg->SetBirthday(new_birthmonth, new_birthday);
             modified = true;
         }
 
         // apply language
-        int new_language = ui->combo_language->currentIndex();
+        s32 new_language = ui->combo_language->currentIndex();
         if (language_index != new_language) {
             cfg->SetSystemLanguage(static_cast<Service::CFG::SystemLanguage>(new_language));
             modified = true;
         }
 
         // apply sound
-        int new_sound = ui->combo_sound->currentIndex();
+        s32 new_sound = ui->combo_sound->currentIndex();
         if (sound_index != new_sound) {
             cfg->SetSoundOutputMode(static_cast<Service::CFG::SoundOutputMode>(new_sound));
             modified = true;
@@ -386,6 +396,9 @@ void ConfigureSystem::ApplyConfiguration() {
             cfg->UpdateConfigNANDSavegame();
         }
 
+        ConfigurationShared::ApplyPerGameSetting(&Settings::values.is_new_3ds, ui->toggle_new_3ds,
+                                                 is_new_3ds);
+
         Settings::values.init_clock =
             static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex());
         Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t();
@@ -398,11 +411,11 @@ void ConfigureSystem::ApplyConfiguration() {
         }
 
         Settings::values.init_time_offset = time_offset_days + time_offset_time;
-        Settings::values.is_new_3ds = ui->toggle_new_3ds->isChecked();
     }
 
-    Settings::values.cpu_clock_percentage = SliderToSettings(ui->slider_clock_speed->value());
-    Settings::Apply();
+    ConfigurationShared::ApplyPerGameSetting(
+        &Settings::values.cpu_clock_percentage, ui->clock_speed_combo,
+        [this](s32) { return SliderToSettings(ui->slider_clock_speed->value()); });
 }
 
 void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
@@ -410,10 +423,10 @@ void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
         return;
 
     // store current day selection
-    int birthday_index = ui->combo_birthday->currentIndex();
+    s32 birthday_index = ui->combo_birthday->currentIndex();
 
     // get number of days in the new selected month
-    int days = days_in_month[birthmonth_index];
+    s32 days = days_in_month[birthmonth_index];
 
     // if the selected day is out of range,
     // reset it to 1st
@@ -422,7 +435,7 @@ void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
 
     // update the day combo box
     ui->combo_birthday->clear();
-    for (int i = 1; i <= days; ++i) {
+    for (s32 i = 1; i <= days; ++i) {
         ui->combo_birthday->addItem(QString::number(i));
     }
 
@@ -442,15 +455,16 @@ void ConfigureSystem::ConfigureTime() {
 }
 
 void ConfigureSystem::UpdateInitTime(int init_clock) {
+    const bool is_global = Settings::IsConfiguringGlobal();
     const bool is_fixed_time =
         static_cast<Settings::InitClock>(init_clock) == Settings::InitClock::FixedTime;
 
-    ui->label_init_time->setVisible(is_fixed_time);
-    ui->edit_init_time->setVisible(is_fixed_time);
+    ui->label_init_time->setVisible(is_fixed_time && is_global);
+    ui->edit_init_time->setVisible(is_fixed_time && is_global);
 
-    ui->label_init_time_offset->setVisible(!is_fixed_time);
-    ui->edit_init_time_offset_days->setVisible(!is_fixed_time);
-    ui->edit_init_time_offset_time->setVisible(!is_fixed_time);
+    ui->label_init_time_offset->setVisible(!is_fixed_time && is_global);
+    ui->edit_init_time_offset_days->setVisible(!is_fixed_time && is_global);
+    ui->edit_init_time_offset_time->setVisible(!is_fixed_time && is_global);
 }
 
 void ConfigureSystem::RefreshConsoleID() {
@@ -475,3 +489,43 @@ void ConfigureSystem::RefreshConsoleID() {
 void ConfigureSystem::RetranslateUI() {
     ui->retranslateUi(this);
 }
+
+void ConfigureSystem::SetupPerGameUI() {
+    // Block the global settings if a game is currently running that overrides them
+    if (Settings::IsConfiguringGlobal()) {
+        ui->toggle_new_3ds->setEnabled(Settings::values.is_new_3ds.UsingGlobal());
+        ui->slider_clock_speed->setEnabled(Settings::values.cpu_clock_percentage.UsingGlobal());
+        return;
+    }
+
+    // Hide most settings for now, we can implement them later
+    ui->label_username->setVisible(false);
+    ui->label_birthday->setVisible(false);
+    ui->label_init_clock->setVisible(false);
+    ui->label_init_time->setVisible(false);
+    ui->label_console_id->setVisible(false);
+    ui->label_sound->setVisible(false);
+    ui->label_language->setVisible(false);
+    ui->label_country->setVisible(false);
+    ui->label_play_coins->setVisible(false);
+    ui->edit_username->setVisible(false);
+    ui->spinBox_play_coins->setVisible(false);
+    ui->combo_birthday->setVisible(false);
+    ui->combo_birthmonth->setVisible(false);
+    ui->combo_init_clock->setVisible(false);
+    ui->combo_sound->setVisible(false);
+    ui->combo_language->setVisible(false);
+    ui->combo_country->setVisible(false);
+    ui->label_init_time_offset->setVisible(false);
+    ui->edit_init_time_offset_days->setVisible(false);
+    ui->edit_init_time_offset_time->setVisible(false);
+    ui->button_regenerate_console_id->setVisible(false);
+
+    connect(ui->clock_speed_combo, qOverload<int>(&QComboBox::activated), this, [this](int index) {
+        ui->slider_clock_speed->setEnabled(index == 1);
+        ConfigurationShared::SetHighlight(ui->clock_speed_widget, index == 1);
+    });
+
+    ConfigurationShared::SetColoredTristate(ui->toggle_new_3ds, Settings::values.is_new_3ds,
+                                            is_new_3ds);
+}
diff --git a/src/citra_qt/configuration/configure_system.h b/src/citra_qt/configuration/configure_system.h
index 99becf54a..007bed199 100644
--- a/src/citra_qt/configuration/configure_system.h
+++ b/src/citra_qt/configuration/configure_system.h
@@ -12,6 +12,10 @@ namespace Ui {
 class ConfigureSystem;
 }
 
+namespace ConfigurationShared {
+enum class CheckState;
+}
+
 namespace Service {
 namespace CFG {
 class Module;
@@ -37,6 +41,9 @@ private:
     void UpdateInitTime(int init_clock);
     void RefreshConsoleID();
 
+    void SetupPerGameUI();
+
+    ConfigurationShared::CheckState is_new_3ds;
     std::unique_ptr<Ui::ConfigureSystem> ui;
     bool enabled = false;
 
diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui
index 37337f537..b2bb618b3 100644
--- a/src/citra_qt/configuration/configure_system.ui
+++ b/src/citra_qt/configuration/configure_system.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>520</width>
-    <height>564</height>
+    <width>525</width>
+    <height>619</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -344,54 +344,77 @@
       </widget>
      </item>
      <item>
-      <widget class="QGroupBox" name="groupBox">
+      <widget class="QGroupBox" name="group_advanced">
        <property name="title">
         <string>Advanced</string>
        </property>
        <layout class="QGridLayout" name="gridLayout_2">
         <item row="0" column="0">
-         <widget class="QLabel" name="label_3">
-          <property name="text">
-           <string>CPU Clock Speed</string>
-          </property>
-         </widget>
-        </item>
-        <item row="0" column="2">
-         <widget class="QSlider" name="slider_clock_speed">
-          <property name="toolTip">
-           <string>&lt;html&gt;&lt;body&gt;Changes the emulated CPU clock frequency.&lt;br&gt;Underclocking can increase performance but may cause the game to freeze.&lt;br&gt;Overclocking may reduce in game lag but also might cause freezes&lt;/body&gt;&lt;/html&gt;</string>
-          </property>
-          <property name="minimum">
-           <number>0</number>
-          </property>
-          <property name="maximum">
-           <number>79</number>
-          </property>
-          <property name="singleStep">
-           <number>5</number>
-          </property>
-          <property name="pageStep">
-           <number>15</number>
-          </property>
-          <property name="value">
-           <number>25</number>
-          </property>
-          <property name="orientation">
-           <enum>Qt::Horizontal</enum>
-          </property>
-          <property name="tickPosition">
-           <enum>QSlider::TicksBelow</enum>
-          </property>
-         </widget>
-        </item>
-        <item row="0" column="1">
-         <widget class="QLabel" name="clock_display_label">
-          <property name="text">
-           <string/>
-          </property>
-          <property name="alignment">
-           <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
-          </property>
+         <widget class="QWidget" name="clock_speed_widget" native="true">
+          <layout class="QHBoxLayout" name="clock_speed_layout">
+           <property name="spacing">
+            <number>7</number>
+           </property>
+           <item>
+            <widget class="QComboBox" name="clock_speed_combo">
+             <item>
+              <property name="text">
+               <string>Use global clock speed</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>Set clock speed:</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLabel" name="clock_speed_label">
+             <property name="text">
+              <string>CPU Clock Speed</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QSlider" name="slider_clock_speed">
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;body&gt;Changes the emulated CPU clock frequency.&lt;br&gt;Underclocking can increase performance but may cause the game to freeze.&lt;br&gt;Overclocking may reduce in game lag but also might cause freezes&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="minimum">
+              <number>0</number>
+             </property>
+             <property name="maximum">
+              <number>79</number>
+             </property>
+             <property name="singleStep">
+              <number>5</number>
+             </property>
+             <property name="pageStep">
+              <number>15</number>
+             </property>
+             <property name="value">
+              <number>25</number>
+             </property>
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="tickPosition">
+              <enum>QSlider::TicksBelow</enum>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QLabel" name="clock_display_label">
+             <property name="text">
+              <string/>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+             </property>
+            </widget>
+           </item>
+          </layout>
          </widget>
         </item>
        </layout>
@@ -449,7 +472,6 @@
   <tabstop>edit_init_time</tabstop>
   <tabstop>spinBox_play_coins</tabstop>
   <tabstop>button_regenerate_console_id</tabstop>
-  <tabstop>slider_clock_speed</tabstop>
  </tabstops>
  <resources/>
  <connections/>
diff --git a/src/citra_qt/configuration/configure_touch_from_button.h b/src/citra_qt/configuration/configure_touch_from_button.h
index a2fa02dc1..bd95a4945 100644
--- a/src/citra_qt/configuration/configure_touch_from_button.h
+++ b/src/citra_qt/configuration/configure_touch_from_button.h
@@ -9,7 +9,7 @@
 #include <optional>
 #include <vector>
 #include <QDialog>
-#include "core/settings.h"
+#include "common/settings.h"
 
 class QItemSelection;
 class QModelIndex;
diff --git a/src/citra_qt/configuration/configure_ui.cpp b/src/citra_qt/configuration/configure_ui.cpp
index a3785247f..b3aad532c 100644
--- a/src/citra_qt/configuration/configure_ui.cpp
+++ b/src/citra_qt/configuration/configure_ui.cpp
@@ -46,12 +46,14 @@ void ConfigureUi::SetConfiguration() {
     ui->language_combobox->setCurrentIndex(
         ui->language_combobox->findData(UISettings::values.language));
     ui->icon_size_combobox->setCurrentIndex(
-        static_cast<int>(UISettings::values.game_list_icon_size));
-    ui->row_1_text_combobox->setCurrentIndex(static_cast<int>(UISettings::values.game_list_row_1));
-    ui->row_2_text_combobox->setCurrentIndex(static_cast<int>(UISettings::values.game_list_row_2) +
-                                             1);
-    ui->toggle_hide_no_icon->setChecked(UISettings::values.game_list_hide_no_icon);
-    ui->toggle_single_line_mode->setChecked(UISettings::values.game_list_single_line_mode);
+        static_cast<int>(UISettings::values.game_list_icon_size.GetValue()));
+    ui->row_1_text_combobox->setCurrentIndex(
+        static_cast<int>(UISettings::values.game_list_row_1.GetValue()));
+    ui->row_2_text_combobox->setCurrentIndex(
+        static_cast<int>(UISettings::values.game_list_row_2.GetValue()) + 1);
+    ui->toggle_hide_no_icon->setChecked(UISettings::values.game_list_hide_no_icon.GetValue());
+    ui->toggle_single_line_mode->setChecked(
+        UISettings::values.game_list_single_line_mode.GetValue());
 }
 
 void ConfigureUi::ApplyConfiguration() {
diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp
index e97a22720..b9a15b6ec 100644
--- a/src/citra_qt/configuration/configure_web.cpp
+++ b/src/citra_qt/configuration/configure_web.cpp
@@ -87,7 +87,7 @@ void ConfigureWeb::SetConfiguration() {
         tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
     user_verified = true;
 
-    ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence);
+    ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence.GetValue());
 }
 
 void ConfigureWeb::ApplyConfiguration() {
diff --git a/src/citra_qt/debugger/console.cpp b/src/citra_qt/debugger/console.cpp
index 95ce9b0c7..eecf86048 100644
--- a/src/citra_qt/debugger/console.cpp
+++ b/src/citra_qt/debugger/console.cpp
@@ -15,10 +15,10 @@
 namespace Debugger {
 void ToggleConsole() {
     static bool console_shown = false;
-    if (console_shown == UISettings::values.show_console) {
+    if (console_shown == UISettings::values.show_console.GetValue()) {
         return;
     } else {
-        console_shown = UISettings::values.show_console;
+        console_shown = UISettings::values.show_console.GetValue();
     }
 
 #ifdef _WIN32
diff --git a/src/citra_qt/debugger/lle_service_modules.cpp b/src/citra_qt/debugger/lle_service_modules.cpp
index dca3ce638..508381326 100644
--- a/src/citra_qt/debugger/lle_service_modules.cpp
+++ b/src/citra_qt/debugger/lle_service_modules.cpp
@@ -7,7 +7,7 @@
 #include <QLayout>
 #include <QScrollArea>
 #include "citra_qt/debugger/lle_service_modules.h"
-#include "core/settings.h"
+#include "common/settings.h"
 
 LLEServiceModulesWidget::LLEServiceModulesWidget(QWidget* parent)
     : QDockWidget(tr("Toggle LLE Service Modules"), parent) {
diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp
index d46d99758..5892ce48c 100644
--- a/src/citra_qt/debugger/wait_tree.cpp
+++ b/src/citra_qt/debugger/wait_tree.cpp
@@ -7,13 +7,13 @@
 #include "citra_qt/uisettings.h"
 #include "citra_qt/util/util.h"
 #include "common/assert.h"
+#include "common/settings.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/kernel/mutex.h"
 #include "core/hle/kernel/semaphore.h"
 #include "core/hle/kernel/thread.h"
 #include "core/hle/kernel/timer.h"
 #include "core/hle/kernel/wait_object.h"
-#include "core/settings.h"
 
 namespace {
 
diff --git a/src/citra_qt/dumping/dumping_dialog.cpp b/src/citra_qt/dumping/dumping_dialog.cpp
index 70d165c01..bbe69f245 100644
--- a/src/citra_qt/dumping/dumping_dialog.cpp
+++ b/src/citra_qt/dumping/dumping_dialog.cpp
@@ -7,7 +7,7 @@
 #include "citra_qt/dumping/dumping_dialog.h"
 #include "citra_qt/dumping/options_dialog.h"
 #include "citra_qt/uisettings.h"
-#include "core/settings.h"
+#include "common/settings.h"
 #include "ui_dumping_dialog.h"
 
 DumpingDialog::DumpingDialog(QWidget* parent)
diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp
index 43c6eaa78..4a3771bf8 100644
--- a/src/citra_qt/game_list.cpp
+++ b/src/citra_qt/game_list.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <QApplication>
+#include <QDir>
 #include <QFileInfo>
 #include <QFileSystemWatcher>
 #include <QHBoxLayout>
@@ -28,9 +29,11 @@
 #include "citra_qt/main.h"
 #include "citra_qt/uisettings.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/file_sys/archive_extsavedata.h"
 #include "core/file_sys/archive_source_sd_savedata.h"
 #include "core/hle/service/fs/archive.h"
+#include "qcursor.h"
 
 GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
     : QObject(parent), gamelist{gamelist} {}
@@ -462,9 +465,20 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
     default:
         break;
     }
+
     context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
 }
 
+void ForEachOpenGLCacheFile(u64 program_id, auto func) {
+    for (const std::string_view cache_type : {"separable", "conventional"}) {
+        const std::string path = fmt::format("{}opengl/precompiled/{}/{:016X}.bin",
+                                             FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir),
+                                             cache_type, program_id);
+        QFile file{QString::fromStdString(path)};
+        func(file);
+    }
+}
+
 void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 program_id,
                             u64 extdata_id) {
     QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
@@ -475,19 +489,32 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
     QAction* open_texture_load_location =
         context_menu.addAction(tr("Open Custom Texture Location"));
     QAction* open_mods_location = context_menu.addAction(tr("Open Mods Location"));
+    QAction* open_dlc_location = context_menu.addAction(tr("Open DLC Data Location"));
+    QMenu* shader_menu = context_menu.addMenu(tr("Disk Shader Cache"));
     QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
     QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
+    context_menu.addSeparator();
+    QAction* properties = context_menu.addAction(tr("Properties"));
+
+    QAction* open_shader_cache_location = shader_menu->addAction(tr("Open Shader Cache Location"));
+    shader_menu->addSeparator();
+    QAction* delete_opengl_disk_shader_cache =
+        shader_menu->addAction(tr("Delete OpenGL Shader Cache"));
 
     const bool is_application =
         0x0004000000000000 <= program_id && program_id <= 0x00040000FFFFFFFF;
 
+    bool opengl_cache_exists = false;
+    ForEachOpenGLCacheFile(
+        program_id, [&opengl_cache_exists](QFile& file) { opengl_cache_exists |= file.exists(); });
+
     std::string sdmc_dir = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
-    open_save_location->setVisible(
+    open_save_location->setEnabled(
         is_application && FileUtil::Exists(FileSys::ArchiveSource_SDSaveData::GetSaveDataPathFor(
                               sdmc_dir, program_id)));
 
     if (extdata_id) {
-        open_extdata_location->setVisible(
+        open_extdata_location->setEnabled(
             is_application &&
             FileUtil::Exists(FileSys::GetExtDataPathFromId(sdmc_dir, extdata_id)));
     } else {
@@ -495,18 +522,20 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
     }
 
     auto media_type = Service::AM::GetTitleMediaType(program_id);
-    open_application_location->setVisible(path.toStdString() ==
+    open_application_location->setEnabled(path.toStdString() ==
                                           Service::AM::GetTitleContentPath(media_type, program_id));
-    open_update_location->setVisible(
+    open_update_location->setEnabled(
         is_application && FileUtil::Exists(Service::AM::GetTitlePath(Service::FS::MediaType::SDMC,
                                                                      program_id + 0xe00000000) +
                                            "content/"));
     auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
 
-    open_texture_dump_location->setVisible(is_application);
-    open_texture_load_location->setVisible(is_application);
-    open_mods_location->setVisible(is_application);
-    dump_romfs->setVisible(is_application);
+    open_texture_dump_location->setEnabled(is_application);
+    open_texture_load_location->setEnabled(is_application);
+    open_mods_location->setEnabled(is_application);
+    open_dlc_location->setEnabled(is_application);
+    dump_romfs->setEnabled(is_application);
+    delete_opengl_disk_shader_cache->setEnabled(opengl_cache_exists);
 
     navigate_to_gamedb_entry->setVisible(it != compatibility_list.end());
 
@@ -543,11 +572,32 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra
             emit OpenFolderRequested(program_id, GameListOpenTarget::MODS);
         }
     });
+    connect(open_dlc_location, &QAction::triggered, this, [this, program_id] {
+        const u64 trimmed_id = program_id & 0xFFFFFFF;
+        const std::string dlc_path =
+            fmt::format("{}Nintendo 3DS/00000000000000000000000000000000/"
+                        "00000000000000000000000000000000/title/0004008c/{:08x}/content/",
+                        FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), trimmed_id);
+        fmt::print("DLC path {}\n", dlc_path);
+        if (FileUtil::CreateFullPath(dlc_path)) {
+            emit OpenFolderRequested(trimmed_id, GameListOpenTarget::DLC_DATA);
+        }
+    });
     connect(dump_romfs, &QAction::triggered, this,
             [this, path, program_id] { emit DumpRomFSRequested(path, program_id); });
     connect(navigate_to_gamedb_entry, &QAction::triggered, this, [this, program_id]() {
         emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
     });
+    connect(properties, &QAction::triggered, this,
+            [this, path]() { emit OpenPerGameGeneralRequested(path); });
+    connect(open_shader_cache_location, &QAction::triggered, this, [this, program_id] {
+        if (FileUtil::CreateFullPath(FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir))) {
+            emit OpenFolderRequested(program_id, GameListOpenTarget::SHADER_CACHE);
+        }
+    });
+    connect(delete_opengl_disk_shader_cache, &QAction::triggered, this, [program_id] {
+        ForEachOpenGLCacheFile(program_id, [](QFile& file) { file.remove(); });
+    });
 };
 
 void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h
index bf20cc9b8..fced8f9e3 100644
--- a/src/citra_qt/game_list.h
+++ b/src/citra_qt/game_list.h
@@ -37,6 +37,8 @@ enum class GameListOpenTarget {
     TEXTURE_DUMP = 4,
     TEXTURE_LOAD = 5,
     MODS = 6,
+    DLC_DATA = 7,
+    SHADER_CACHE = 8
 };
 
 class GameList : public QWidget {
@@ -82,6 +84,7 @@ signals:
     void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
     void NavigateToGamedbEntryRequested(u64 program_id,
                                         const CompatibilityList& compatibility_list);
+    void OpenPerGameGeneralRequested(const QString file);
     void DumpRomFSRequested(QString game_path, u64 program_id);
     void OpenDirectory(const QString& directory);
     void AddDirectory();
diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h
index 75f1b90fa..19d04053d 100644
--- a/src/citra_qt/game_list_p.h
+++ b/src/citra_qt/game_list_p.h
@@ -160,17 +160,19 @@ public:
         setData(qulonglong(program_id), ProgramIdRole);
         setData(qulonglong(extdata_id), ExtdataIdRole);
 
-        if (UISettings::values.game_list_icon_size == UISettings::GameListIconSize::NoIcon) {
+        if (UISettings::values.game_list_icon_size.GetValue() ==
+            UISettings::GameListIconSize::NoIcon) {
             // Do not display icons
             setData(QPixmap(), Qt::DecorationRole);
         }
 
-        bool large =
-            UISettings::values.game_list_icon_size == UISettings::GameListIconSize::LargeIcon;
+        bool large = UISettings::values.game_list_icon_size.GetValue() ==
+                     UISettings::GameListIconSize::LargeIcon;
 
         if (!Loader::IsValidSMDH(smdh_data)) {
             // SMDH is not valid, set a default icon
-            if (UISettings::values.game_list_icon_size != UISettings::GameListIconSize::NoIcon)
+            if (UISettings::values.game_list_icon_size.GetValue() !=
+                UISettings::GameListIconSize::NoIcon)
                 setData(GetDefaultIcon(large), Qt::DecorationRole);
             return;
         }
@@ -179,7 +181,8 @@ public:
         memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
 
         // Get icon from SMDH
-        if (UISettings::values.game_list_icon_size != UISettings::GameListIconSize::NoIcon) {
+        if (UISettings::values.game_list_icon_size.GetValue() !=
+            UISettings::GameListIconSize::NoIcon) {
             setData(GetQPixmapFromSMDH(smdh, large), Qt::DecorationRole);
         }
 
@@ -211,16 +214,17 @@ public:
                  QString::fromStdString(fmt::format("{:016X}", data(ProgramIdRole).toULongLong()))},
             };
 
-            const QString& row1 = display_texts.at(UISettings::values.game_list_row_1).simplified();
+            const QString& row1 =
+                display_texts.at(UISettings::values.game_list_row_1.GetValue()).simplified();
 
             if (role == SortRole)
                 return row1.toLower();
 
             QString row2;
-            auto row_2_id = UISettings::values.game_list_row_2;
+            const auto row_2_id = UISettings::values.game_list_row_2.GetValue();
             if (row_2_id != UISettings::GameListText::NoText) {
                 if (!row1.isEmpty()) {
-                    row2 = UISettings::values.game_list_single_line_mode
+                    row2 = UISettings::values.game_list_single_line_mode.GetValue()
                                ? QStringLiteral("     ")
                                : QStringLiteral("\n     ");
                 }
@@ -355,7 +359,7 @@ public:
         UISettings::GameDir* game_dir = &directory;
         setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole);
 
-        const int icon_size = IconSizes.at(UISettings::values.game_list_icon_size);
+        const int icon_size = IconSizes.at(UISettings::values.game_list_icon_size.GetValue());
         switch (dir_type) {
         case GameListItemType::InstalledDir:
             setData(QIcon::fromTheme(QStringLiteral("sd_card")).pixmap(icon_size),
@@ -398,7 +402,7 @@ public:
     explicit GameListAddDir() {
         setData(type(), TypeRole);
 
-        int icon_size = IconSizes.at(UISettings::values.game_list_icon_size);
+        int icon_size = IconSizes.at(UISettings::values.game_list_icon_size.GetValue());
         setData(QIcon::fromTheme(QStringLiteral("plus")).pixmap(icon_size), Qt::DecorationRole);
         setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole);
     }
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 225694969..40767a49a 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <clocale>
+#include <filesystem>
 #include <fstream>
 #include <memory>
 #include <thread>
@@ -34,6 +35,7 @@
 #include "citra_qt/compatibility_list.h"
 #include "citra_qt/configuration/config.h"
 #include "citra_qt/configuration/configure_dialog.h"
+#include "citra_qt/configuration/configure_per_game.h"
 #include "citra_qt/debugger/console.h"
 #include "citra_qt/debugger/graphics/graphics.h"
 #include "citra_qt/debugger/graphics/graphics_breakpoints.h"
@@ -70,9 +72,11 @@
 #include "common/microprofile.h"
 #include "common/scm_rev.h"
 #include "common/scope_exit.h"
+#include "common/string_util.h"
 #ifdef ARCHITECTURE_x86_64
 #include "common/x64/cpu_detect.h"
 #endif
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/dumping/backend.h"
 #include "core/file_sys/archive_extsavedata.h"
@@ -85,7 +89,6 @@
 #include "core/loader/loader.h"
 #include "core/movie.h"
 #include "core/savestate.h"
-#include "core/settings.h"
 #include "game_list_p.h"
 #include "input_common/main.h"
 #include "network/network_settings.h"
@@ -124,11 +127,13 @@ enum class CalloutFlag : uint32_t {
 };
 
 void GMainWindow::ShowTelemetryCallout() {
-    if (UISettings::values.callout_flags & static_cast<uint32_t>(CalloutFlag::Telemetry)) {
+    if (UISettings::values.callout_flags.GetValue() &
+        static_cast<uint32_t>(CalloutFlag::Telemetry)) {
         return;
     }
 
-    UISettings::values.callout_flags |= static_cast<uint32_t>(CalloutFlag::Telemetry);
+    UISettings::values.callout_flags =
+        UISettings::values.callout_flags.GetValue() | static_cast<uint32_t>(CalloutFlag::Telemetry);
     const QString telemetry_message =
         tr("<a href='https://citra-emu.org/entry/telemetry-and-why-thats-a-good-thing/'>Anonymous "
            "data is collected</a> to help improve Citra. "
@@ -143,7 +148,7 @@ const int GMainWindow::max_recent_files_item;
 
 static void InitializeLogging() {
     Log::Filter log_filter;
-    log_filter.ParseFilterString(Settings::values.log_filter);
+    log_filter.ParseFilterString(Settings::values.log_filter.GetValue());
     Log::SetGlobalFilter(log_filter);
 
     const std::string& log_dir = FileUtil::GetUserPath(FileUtil::UserPath::LogDir);
@@ -175,7 +180,7 @@ GMainWindow::GMainWindow()
     default_theme_paths = QIcon::themeSearchPaths();
     UpdateUITheme();
 
-    SetDiscordEnabled(UISettings::values.enable_discord_presence);
+    SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
     discord_rpc->Update();
 
     Network::Init();
@@ -556,8 +561,7 @@ void GMainWindow::InitializeHotkeys() {
             });
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Alternate Speed"), this),
             &QShortcut::activated, this, [&] {
-                Settings::values.use_frame_limit_alternate =
-                    !Settings::values.use_frame_limit_alternate;
+                Settings::values.frame_limit.SetGlobal(!Settings::values.frame_limit.UsingGlobal());
                 UpdateStatusBar();
             });
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Texture Dumping"), this),
@@ -568,42 +572,25 @@ void GMainWindow::InitializeHotkeys() {
     static constexpr u16 SPEED_LIMIT_STEP = 5;
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Increase Speed Limit"), this),
             &QShortcut::activated, this, [&] {
-                if (Settings::values.use_frame_limit_alternate) {
-                    if (Settings::values.frame_limit_alternate == 0) {
-                        return;
-                    }
-                    if (Settings::values.frame_limit_alternate < 995 - SPEED_LIMIT_STEP) {
-                        Settings::values.frame_limit_alternate += SPEED_LIMIT_STEP;
-                    } else {
-                        Settings::values.frame_limit_alternate = 0;
-                    }
+                if (Settings::values.frame_limit.GetValue() == 0) {
+                    return;
+                }
+                if (Settings::values.frame_limit.GetValue() < 995 - SPEED_LIMIT_STEP) {
+                    Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() +
+                                                          SPEED_LIMIT_STEP);
                 } else {
-                    if (Settings::values.frame_limit == 0) {
-                        return;
-                    }
-                    if (Settings::values.frame_limit < 995 - SPEED_LIMIT_STEP) {
-                        Settings::values.frame_limit += SPEED_LIMIT_STEP;
-                    } else {
-                        Settings::values.frame_limit = 0;
-                    }
+                    Settings::values.frame_limit = 0;
                 }
                 UpdateStatusBar();
             });
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Decrease Speed Limit"), this),
             &QShortcut::activated, this, [&] {
-                if (Settings::values.use_frame_limit_alternate) {
-                    if (Settings::values.frame_limit_alternate == 0) {
-                        Settings::values.frame_limit_alternate = 995;
-                    } else if (Settings::values.frame_limit_alternate > SPEED_LIMIT_STEP) {
-                        Settings::values.frame_limit_alternate -= SPEED_LIMIT_STEP;
-                    }
-                } else {
-                    if (Settings::values.frame_limit == 0) {
-                        Settings::values.frame_limit = 995;
-                    } else if (Settings::values.frame_limit > SPEED_LIMIT_STEP) {
-                        Settings::values.frame_limit -= SPEED_LIMIT_STEP;
-                        UpdateStatusBar();
-                    }
+                if (Settings::values.frame_limit.GetValue() == 0) {
+                    Settings::values.frame_limit = 995;
+                } else if (Settings::values.frame_limit.GetValue() > SPEED_LIMIT_STEP) {
+                    Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() -
+                                                          SPEED_LIMIT_STEP);
+                    UpdateStatusBar();
                 }
                 UpdateStatusBar();
             });
@@ -663,25 +650,26 @@ void GMainWindow::RestoreUIState() {
     render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
 #if MICROPROFILE_ENABLED
     microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry);
-    microProfileDialog->setVisible(UISettings::values.microprofile_visible);
+    microProfileDialog->setVisible(UISettings::values.microprofile_visible.GetValue());
 #endif
     ui->action_Cheats->setEnabled(false);
 
     game_list->LoadInterfaceLayout();
 
-    ui->action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode);
+    ui->action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode.GetValue());
     ToggleWindowMode();
 
-    ui->action_Fullscreen->setChecked(UISettings::values.fullscreen);
+    ui->action_Fullscreen->setChecked(UISettings::values.fullscreen.GetValue());
     SyncMenuUISettings();
 
-    ui->action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar);
+    ui->action_Display_Dock_Widget_Headers->setChecked(
+        UISettings::values.display_titlebar.GetValue());
     OnDisplayTitleBars(ui->action_Display_Dock_Widget_Headers->isChecked());
 
-    ui->action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar);
+    ui->action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar.GetValue());
     game_list->SetFilterVisible(ui->action_Show_Filter_Bar->isChecked());
 
-    ui->action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar);
+    ui->action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar.GetValue());
     statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
 }
 
@@ -717,6 +705,9 @@ void GMainWindow::ConnectWidgetEvents() {
     connect(game_list, &GameList::PopulatingCompleted, this,
             [this] { multiplayer_state->UpdateGameList(game_list->GetModel()); });
 
+    connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
+            &GMainWindow::OnGameListOpenPerGameProperties);
+
     connect(this, &GMainWindow::EmulationStarting, render_window,
             &GRenderWindow::OnEmulationStarting);
     connect(this, &GMainWindow::EmulationStopping, render_window,
@@ -752,6 +743,8 @@ void GMainWindow::ConnectMenuEvents() {
     connect(ui->action_Report_Compatibility, &QAction::triggered, this,
             &GMainWindow::OnMenuReportCompatibility);
     connect(ui->action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
+    connect(ui->action_Configure_Current_Game, &QAction::triggered, this,
+            &GMainWindow::OnConfigurePerGame);
     connect(ui->action_Cheats, &QAction::triggered, this, &GMainWindow::OnCheats);
 
     // View
@@ -1086,6 +1079,22 @@ void GMainWindow::BootGame(const QString& filename) {
         Core::Movie::GetInstance().PrepareForPlayback(movie_playback_path.toStdString());
     }
 
+    u64 title_id{0};
+    const std::string path = filename.toStdString();
+    const auto loader = Loader::GetLoader(path);
+
+    if (loader != nullptr && loader->ReadProgramId(title_id) == Loader::ResultStatus::Success) {
+        // Load per game settings
+        const std::string name{FileUtil::GetFilename(filename.toStdString())};
+        const std::string config_file_name =
+            title_id == 0 ? name : fmt::format("{:016X}", title_id);
+        Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
+        Settings::Apply();
+
+        LOG_INFO(Frontend, "Using per game config file for title id {}", config_file_name);
+        Settings::LogSettings();
+    }
+
     // Save configurations
     UpdateUISettings();
     game_list->SaveInterfaceLayout();
@@ -1239,6 +1248,7 @@ void GMainWindow::ShutdownGame() {
     ui->action_Stop->setEnabled(false);
     ui->action_Restart->setEnabled(false);
     ui->action_Cheats->setEnabled(false);
+    ui->action_Configure_Current_Game->setEnabled(false);
     ui->action_Load_Amiibo->setEnabled(false);
     ui->action_Remove_Amiibo->setEnabled(false);
     ui->action_Report_Compatibility->setEnabled(false);
@@ -1398,26 +1408,42 @@ void GMainWindow::OnGameListOpenFolder(u64 data_id, GameListOpenTarget target) {
         path = Service::AM::GetTitlePath(media_type, data_id) + "content/";
         break;
     }
-    case GameListOpenTarget::UPDATE_DATA:
+    case GameListOpenTarget::UPDATE_DATA: {
         open_target = "Update Data";
         path = Service::AM::GetTitlePath(Service::FS::MediaType::SDMC, data_id + 0xe00000000) +
                "content/";
         break;
-    case GameListOpenTarget::TEXTURE_DUMP:
+    }
+    case GameListOpenTarget::TEXTURE_DUMP: {
         open_target = "Dumped Textures";
         path = fmt::format("{}textures/{:016X}/",
                            FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), data_id);
         break;
-    case GameListOpenTarget::TEXTURE_LOAD:
+    }
+    case GameListOpenTarget::TEXTURE_LOAD: {
         open_target = "Custom Textures";
         path = fmt::format("{}textures/{:016X}/",
                            FileUtil::GetUserPath(FileUtil::UserPath::LoadDir), data_id);
         break;
-    case GameListOpenTarget::MODS:
+    }
+    case GameListOpenTarget::MODS: {
         open_target = "Mods";
         path = fmt::format("{}mods/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
                            data_id);
         break;
+    }
+    case GameListOpenTarget::DLC_DATA: {
+        open_target = "DLC Data";
+        path = fmt::format("{}Nintendo 3DS/00000000000000000000000000000000/"
+                           "00000000000000000000000000000000/title/0004008c/{:08x}/content/",
+                           FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), data_id);
+        break;
+    }
+    case GameListOpenTarget::SHADER_CACHE: {
+        open_target = "Shader Cache";
+        path = FileUtil::GetUserPath(FileUtil::UserPath::ShaderDir);
+        break;
+    }
     default:
         LOG_ERROR(Frontend, "Unexpected target {}", static_cast<int>(target));
         return;
@@ -1529,6 +1555,19 @@ void GMainWindow::OnGameListShowList(bool show) {
     game_list_placeholder->setVisible(!show);
 };
 
+void GMainWindow::OnGameListOpenPerGameProperties(const QString& file) {
+    const auto loader = Loader::GetLoader(file.toStdString());
+
+    u64 title_id{};
+    if (!loader || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
+        QMessageBox::information(this, tr("Properties"),
+                                 tr("The game properties could not be loaded."));
+        return;
+    }
+
+    OpenPerGameConfiguration(title_id, file);
+}
+
 void GMainWindow::OnMenuLoadFile() {
     const QString extensions = QStringLiteral("*.").append(
         GameList::supported_file_extensions.join(QStringLiteral(" *.")));
@@ -1654,6 +1693,7 @@ void GMainWindow::OnStartGame() {
     ui->action_Stop->setEnabled(true);
     ui->action_Restart->setEnabled(true);
     ui->action_Cheats->setEnabled(true);
+    ui->action_Configure_Current_Game->setEnabled(true);
     ui->action_Load_Amiibo->setEnabled(true);
     ui->action_Report_Compatibility->setEnabled(true);
     ui->action_Capture_Screenshot->setEnabled(true);
@@ -1676,6 +1716,7 @@ void GMainWindow::OnPauseGame() {
 
 void GMainWindow::OnStopGame() {
     ShutdownGame();
+    Settings::RestoreGlobalState(false);
 }
 
 void GMainWindow::OnLoadComplete() {
@@ -1769,7 +1810,7 @@ void GMainWindow::UpdateSecondaryWindowVisibility() {
     if (!emulation_running) {
         return;
     }
-    if (Settings::values.layout_option == Settings::LayoutOption::SeparateWindows) {
+    if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
         secondary_window->RestoreGeometry();
         secondary_window->show();
     } else {
@@ -1800,7 +1841,7 @@ void GMainWindow::ChangeScreenLayout() {
 
 void GMainWindow::ToggleScreenLayout() {
     const Settings::LayoutOption new_layout = []() {
-        switch (Settings::values.layout_option) {
+        switch (Settings::values.layout_option.GetValue()) {
         case Settings::LayoutOption::Default:
             return Settings::LayoutOption::SingleScreen;
         case Settings::LayoutOption::SingleScreen:
@@ -1812,7 +1853,8 @@ void GMainWindow::ToggleScreenLayout() {
         case Settings::LayoutOption::SeparateWindows:
             return Settings::LayoutOption::Default;
         default:
-            LOG_ERROR(Frontend, "Unknown layout option {}", Settings::values.layout_option);
+            LOG_ERROR(Frontend, "Unknown layout option {}",
+                      Settings::values.layout_option.GetValue());
             return Settings::LayoutOption::Default;
         }
     }();
@@ -1864,6 +1906,7 @@ void GMainWindow::OnLoadState() {
 }
 
 void GMainWindow::OnConfigure() {
+    Settings::SetConfiguringGlobal(true);
     ConfigureDialog configureDialog(this, hotkey_registry,
                                     !multiplayer_state->IsHostingPublicRoom());
     connect(&configureDialog, &ConfigureDialog::LanguageChanged, this,
@@ -1872,15 +1915,15 @@ void GMainWindow::OnConfigure() {
     const int old_input_profile_index = Settings::values.current_input_profile_index;
     const auto old_input_profiles = Settings::values.input_profiles;
     const auto old_touch_from_button_maps = Settings::values.touch_from_button_maps;
-    const bool old_discord_presence = UISettings::values.enable_discord_presence;
+    const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
     auto result = configureDialog.exec();
     if (result == QDialog::Accepted) {
         configureDialog.ApplyConfiguration();
         InitializeHotkeys();
         if (UISettings::values.theme != old_theme)
             UpdateUITheme();
-        if (UISettings::values.enable_discord_presence != old_discord_presence)
-            SetDiscordEnabled(UISettings::values.enable_discord_presence);
+        if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence)
+            SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
         if (!multiplayer_state->IsHostingPublicRoom())
             multiplayer_state->UpdateCredentials();
         emit UpdateThemedIcons();
@@ -2057,24 +2100,26 @@ void GMainWindow::OnSaveMovie() {
 
 void GMainWindow::OnCaptureScreenshot() {
     OnPauseGame();
-    QString path = UISettings::values.screenshot_path;
-    if (!FileUtil::IsDirectory(path.toStdString())) {
-        if (!FileUtil::CreateFullPath(path.toStdString())) {
+    std::string path = UISettings::values.screenshot_path.GetValue();
+    if (!FileUtil::IsDirectory(path)) {
+        if (!FileUtil::CreateFullPath(path)) {
             QMessageBox::information(this, tr("Invalid Screenshot Directory"),
                                      tr("Cannot create specified screenshot directory. Screenshot "
                                         "path is set back to its default value."));
-            path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir));
-            path.append(QStringLiteral("screenshots/"));
+            path = FileUtil::GetUserPath(FileUtil::UserPath::UserDir);
+            path.append("screenshots/");
             UISettings::values.screenshot_path = path;
         };
     }
-    const QString filename = game_title.remove(QRegularExpression(QStringLiteral("[\\/:?\"<>|]")));
-    const QString timestamp =
-        QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"));
-    path.append(QStringLiteral("/%1_%2.png").arg(filename, timestamp));
+    const std::string filename =
+        game_title.remove(QRegularExpression(QStringLiteral("[\\/:?\"<>|]"))).toStdString();
+    const std::string timestamp =
+        QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z")).toStdString();
+    path.append(fmt::format("/{}_{}.png", filename, timestamp));
 
     auto* const screenshot_window = secondary_window->HasFocus() ? secondary_window : render_window;
-    screenshot_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
+    screenshot_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor.GetValue(),
+                                         QString::fromStdString(path));
     OnStartGame();
 }
 
@@ -2166,22 +2211,12 @@ void GMainWindow::UpdateStatusBar() {
 
     auto results = Core::System::GetInstance().GetAndResetPerfStats();
 
-    if (Settings::values.use_frame_limit_alternate) {
-        if (Settings::values.frame_limit_alternate == 0) {
-            emu_speed_label->setText(
-                tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
-
-        } else {
-            emu_speed_label->setText(tr("Speed: %1% / %2%")
-                                         .arg(results.emulation_speed * 100.0, 0, 'f', 0)
-                                         .arg(Settings::values.frame_limit_alternate));
-        }
-    } else if (Settings::values.frame_limit == 0) {
+    if (Settings::values.frame_limit.GetValue() == 0) {
         emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
     } else {
         emu_speed_label->setText(tr("Speed: %1% / %2%")
                                      .arg(results.emulation_speed * 100.0, 0, 'f', 0)
-                                     .arg(Settings::values.frame_limit));
+                                     .arg(Settings::values.frame_limit.GetValue()));
     }
     game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0));
     emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
@@ -2192,7 +2227,7 @@ void GMainWindow::UpdateStatusBar() {
 }
 
 void GMainWindow::HideMouseCursor() {
-    if (emu_thread == nullptr || UISettings::values.hide_mouse == false) {
+    if (emu_thread == nullptr || !UISettings::values.hide_mouse.GetValue()) {
         mouse_hide_timer.stop();
         ShowMouseCursor();
         return;
@@ -2455,6 +2490,35 @@ void GMainWindow::OnLanguageChanged(const QString& locale) {
         ui->action_Start->setText(tr("Continue"));
 }
 
+void GMainWindow::OnConfigurePerGame() {
+    u64 title_id{};
+    Core::System::GetInstance().GetAppLoader().ReadProgramId(title_id);
+    OpenPerGameConfiguration(title_id, game_path);
+}
+
+void GMainWindow::OpenPerGameConfiguration(u64 title_id, const QString& file_name) {
+    Core::System& system = Core::System::GetInstance();
+
+    Settings::SetConfiguringGlobal(false);
+    ConfigurePerGame dialog(this, title_id, file_name, system);
+    const auto result = dialog.exec();
+
+    if (result != QDialog::Accepted) {
+        Settings::RestoreGlobalState(system.IsPoweredOn());
+        return;
+    } else if (result == QDialog::Accepted) {
+        dialog.ApplyConfiguration();
+    }
+
+    // Do not cause the global config to write local settings into the config file
+    const bool is_powered_on = system.IsPoweredOn();
+    Settings::RestoreGlobalState(system.IsPoweredOn());
+
+    if (!is_powered_on) {
+        config->Save();
+    }
+}
+
 void GMainWindow::OnMoviePlaybackCompleted() {
     OnPauseGame();
     QMessageBox::information(this, tr("Playback Completed"), tr("Movie playback completed."));
@@ -2489,18 +2553,19 @@ void GMainWindow::UpdateUISettings() {
 }
 
 void GMainWindow::SyncMenuUISettings() {
-    ui->action_Screen_Layout_Default->setChecked(Settings::values.layout_option ==
+    ui->action_Screen_Layout_Default->setChecked(Settings::values.layout_option.GetValue() ==
                                                  Settings::LayoutOption::Default);
-    ui->action_Screen_Layout_Single_Screen->setChecked(Settings::values.layout_option ==
+    ui->action_Screen_Layout_Single_Screen->setChecked(Settings::values.layout_option.GetValue() ==
                                                        Settings::LayoutOption::SingleScreen);
-    ui->action_Screen_Layout_Large_Screen->setChecked(Settings::values.layout_option ==
+    ui->action_Screen_Layout_Large_Screen->setChecked(Settings::values.layout_option.GetValue() ==
                                                       Settings::LayoutOption::LargeScreen);
-    ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option ==
+    ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option.GetValue() ==
                                                       Settings::LayoutOption::SideScreen);
-    ui->action_Screen_Layout_Separate_Windows->setChecked(Settings::values.layout_option ==
-                                                          Settings::LayoutOption::SeparateWindows);
-    ui->action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen);
-    ui->action_Screen_Layout_Upright_Screens->setChecked(Settings::values.upright_screen);
+    ui->action_Screen_Layout_Separate_Windows->setChecked(
+        Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
+    ui->action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen.GetValue());
+    ui->action_Screen_Layout_Upright_Screens->setChecked(
+        Settings::values.upright_screen.GetValue());
 }
 
 void GMainWindow::RetranslateStatusBar() {
@@ -2573,11 +2638,13 @@ int main(int argc, char* argv[]) {
 
     // Register frontend applets
     Frontend::RegisterDefaultApplets();
-    Core::System::GetInstance().RegisterMiiSelector(std::make_shared<QtMiiSelector>(main_window));
-    Core::System::GetInstance().RegisterSoftwareKeyboard(std::make_shared<QtKeyboard>(main_window));
+
+    Core::System& system = Core::System::GetInstance();
+    system.RegisterMiiSelector(std::make_shared<QtMiiSelector>(main_window));
+    system.RegisterSoftwareKeyboard(std::make_shared<QtKeyboard>(main_window));
 
     // Register Qt image interface
-    Core::System::GetInstance().RegisterImageInterface(std::make_shared<QtImageInterface>());
+    system.RegisterImageInterface(std::make_shared<QtImageInterface>());
 
     main_window.show();
 
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index e11f99d0d..03689cc5f 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -183,6 +183,8 @@ private slots:
     void OnGameListOpenDirectory(const QString& directory);
     void OnGameListAddDirectory();
     void OnGameListShowList(bool show);
+    void OnGameListOpenPerGameProperties(const QString& file);
+    void OnConfigurePerGame();
     void OnMenuLoadFile();
     void OnMenuInstallCIA();
     void OnUpdateProgress(std::size_t written, std::size_t total);
@@ -238,6 +240,7 @@ private:
     void InstallCIA(QStringList filepaths);
     void HideMouseCursor();
     void ShowMouseCursor();
+    void OpenPerGameConfiguration(u64 title_id, const QString& file_name);
 
     std::unique_ptr<Ui::MainWindow> ui;
 
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui
index 2c2185ed8..b0d5b0387 100644
--- a/src/citra_qt/main.ui
+++ b/src/citra_qt/main.ui
@@ -45,7 +45,7 @@
      <x>0</x>
      <y>0</y>
      <width>1081</width>
-     <height>21</height>
+     <height>25</height>
     </rect>
    </property>
    <widget class="QMenu" name="menu_File">
@@ -104,6 +104,7 @@
     <addaction name="action_Report_Compatibility"/>
     <addaction name="separator"/>
     <addaction name="action_Configure"/>
+    <addaction name="action_Configure_Current_Game"/>
     <addaction name="action_Cheats"/>
    </widget>
    <widget class="QMenu" name="menu_View">
@@ -239,20 +240,20 @@
    </property>
   </action>
   <action name="action_Save">
-    <property name="enabled">
-      <bool>false</bool>
-    </property>
-    <property name="text">
-      <string>Save</string>
-    </property>
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="text">
+    <string>Save</string>
+   </property>
   </action>
   <action name="action_Load">
-    <property name="enabled">
-      <bool>false</bool>
-    </property>
-    <property name="text">
-      <string>Load</string>
-    </property>
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="text">
+    <string>Load</string>
+   </property>
   </action>
   <action name="action_FAQ">
    <property name="text">
@@ -541,6 +542,14 @@
     <string>Open Citra Folder</string>
    </property>
   </action>
+  <action name="action_Configure_Current_Game">
+   <property name="enabled">
+    <bool>false</bool>
+   </property>
+   <property name="text">
+    <string>Configure Current Game...</string>
+   </property>
+  </action>
  </widget>
  <resources/>
  <connections/>
diff --git a/src/citra_qt/uisettings.h b/src/citra_qt/uisettings.h
index 2226c67e1..43e84ced8 100644
--- a/src/citra_qt/uisettings.h
+++ b/src/citra_qt/uisettings.h
@@ -13,7 +13,7 @@
 #include <QString>
 #include <QStringList>
 #include <QVector>
-#include "common/common_types.h"
+#include "common/settings.h"
 
 namespace UISettings {
 
@@ -40,13 +40,13 @@ struct GameDir {
     }
 };
 
-enum class GameListIconSize {
+enum class GameListIconSize : u32 {
     NoIcon,    ///< Do not display icons
     SmallIcon, ///< Display a small (24x24) icon
     LargeIcon, ///< Display a large (48x48) icon
 };
 
-enum class GameListText {
+enum class GameListText : s32 {
     NoText = -1,   ///< No text
     FileName,      ///< Display the file name of the entry
     FullPath,      ///< Display the full path of the entry
@@ -65,40 +65,41 @@ struct Values {
     QByteArray gamelist_header_state;
 
     QByteArray microprofile_geometry;
-    bool microprofile_visible;
+    Settings::Setting<bool> microprofile_visible{false, "microProfileDialogVisible"};
 
-    bool single_window_mode;
-    bool fullscreen;
-    bool display_titlebar;
-    bool show_filter_bar;
-    bool show_status_bar;
+    Settings::Setting<bool> single_window_mode{true, "singleWindowMode"};
+    Settings::Setting<bool> fullscreen{false, "fullscreen"};
+    Settings::Setting<bool> display_titlebar{true, "displayTitleBars"};
+    Settings::Setting<bool> show_filter_bar{true, "showFilterBar"};
+    Settings::Setting<bool> show_status_bar{true, "showStatusBar"};
 
-    bool confirm_before_closing;
-    bool first_start;
-    bool pause_when_in_background;
-    bool hide_mouse;
+    Settings::Setting<bool> confirm_before_closing{true, "confirmClose"};
+    Settings::Setting<bool> first_start{true, "firstStart"};
+    Settings::Setting<bool> pause_when_in_background{false, "pauseWhenInBackground"};
+    Settings::Setting<bool> hide_mouse{false, "hideInactiveMouse"};
 
     bool updater_found;
-    bool update_on_close;
-    bool check_for_update_on_start;
+    Settings::Setting<bool> update_on_close{false, "update_on_close"};
+    Settings::Setting<bool> check_for_update_on_start{true, "check_for_update_on_start"};
 
     // Discord RPC
-    bool enable_discord_presence;
+    Settings::Setting<bool> enable_discord_presence{true, "enable_discord_presence"};
 
     // Game List
-    GameListIconSize game_list_icon_size;
-    GameListText game_list_row_1;
-    GameListText game_list_row_2;
-    bool game_list_hide_no_icon;
-    bool game_list_single_line_mode;
+    Settings::Setting<GameListIconSize> game_list_icon_size{GameListIconSize::LargeIcon,
+                                                            "iconSize"};
+    Settings::Setting<GameListText> game_list_row_1{GameListText::TitleName, "row1"};
+    Settings::Setting<GameListText> game_list_row_2{GameListText::FileName, "row2"};
+    Settings::Setting<bool> game_list_hide_no_icon{false, "hideNoIcon"};
+    Settings::Setting<bool> game_list_single_line_mode{false, "singleLineMode"};
 
-    u16 screenshot_resolution_factor;
+    Settings::Setting<u16> screenshot_resolution_factor{0, "screenshot_resolution_factor"};
+    Settings::SwitchableSetting<std::string> screenshot_path{"", "screenshotPath"};
 
     QString roms_path;
     QString symbols_path;
     QString movie_record_path;
     QString movie_playback_path;
-    QString screenshot_path;
     QString video_dumping_path;
     QString game_dir_deprecated;
     bool game_dir_deprecated_deepscan;
@@ -111,7 +112,7 @@ struct Values {
     // Shortcut name <Shortcut, context>
     std::vector<Shortcut> shortcuts;
 
-    uint32_t callout_flags;
+    Settings::Setting<u32> callout_flags{0, "calloutFlags"};
 
     // multiplayer settings
     QString nickname;
@@ -127,7 +128,7 @@ struct Values {
     std::pair<std::vector<std::string>, std::vector<std::string>> ban_list;
 
     // logging
-    bool show_console;
+    Settings::Setting<bool> show_console{false, "showConsole"};
 };
 
 extern Values values;
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index e4f8b25e8..52eac11b9 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -99,6 +99,8 @@ add_library(common STATIC
     scm_rev.cpp
     scm_rev.h
     scope_exit.h
+    settings.cpp
+    settings.h
     serialization/atomic.h
     serialization/boost_discrete_interval.hpp
     serialization/boost_flat_set.h
diff --git a/src/core/settings.cpp b/src/common/settings.cpp
similarity index 54%
rename from src/core/settings.cpp
rename to src/common/settings.cpp
index b05389cc1..f6688e1e5 100644
--- a/src/core/settings.cpp
+++ b/src/common/settings.cpp
@@ -5,6 +5,7 @@
 #include <string_view>
 #include <utility>
 #include "audio_core/dsp_interface.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/hle/kernel/shared_page.h"
@@ -13,24 +14,24 @@
 #include "core/hle/service/ir/ir_rst.h"
 #include "core/hle/service/ir/ir_user.h"
 #include "core/hle/service/mic_u.h"
-#include "core/settings.h"
 #include "video_core/renderer_base.h"
 #include "video_core/video_core.h"
 
 namespace Settings {
 
 Values values = {};
+static bool configuring_global = true;
 
 void Apply() {
-    GDBStub::SetServerPort(values.gdbstub_port);
-    GDBStub::ToggleServer(values.use_gdbstub);
+    GDBStub::SetServerPort(values.gdbstub_port.GetValue());
+    GDBStub::ToggleServer(values.use_gdbstub.GetValue());
 
-    VideoCore::g_hw_renderer_enabled = values.use_hw_renderer;
-    VideoCore::g_shader_jit_enabled = values.use_shader_jit;
-    VideoCore::g_hw_shader_enabled = values.use_hw_shader;
-    VideoCore::g_separable_shader_enabled = values.separable_shader;
-    VideoCore::g_hw_shader_accurate_mul = values.shaders_accurate_mul;
-    VideoCore::g_use_disk_shader_cache = values.use_disk_shader_cache;
+    VideoCore::g_hw_renderer_enabled = values.use_hw_renderer.GetValue();
+    VideoCore::g_shader_jit_enabled = values.use_shader_jit.GetValue();
+    VideoCore::g_hw_shader_enabled = values.use_hw_shader.GetValue();
+    VideoCore::g_separable_shader_enabled = values.separable_shader.GetValue();
+    VideoCore::g_hw_shader_accurate_mul = values.shaders_accurate_mul.GetValue();
+    VideoCore::g_use_disk_shader_cache = values.use_disk_shader_cache.GetValue();
 
 #ifndef ANDROID
     if (VideoCore::g_renderer) {
@@ -45,9 +46,9 @@ void Apply() {
 
     auto& system = Core::System::GetInstance();
     if (system.IsPoweredOn()) {
-        system.CoreTiming().UpdateClockSpeed(values.cpu_clock_percentage);
-        Core::DSP().SetSink(values.sink_id, values.audio_device_id);
-        Core::DSP().EnableStretching(values.enable_audio_stretching);
+        system.CoreTiming().UpdateClockSpeed(values.cpu_clock_percentage.GetValue());
+        Core::DSP().SetSink(values.sink_id.GetValue(), values.audio_device_id.GetValue());
+        Core::DSP().EnableStretching(values.enable_audio_stretching.GetValue());
 
         auto hid = Service::HID::GetModule(system);
         if (hid) {
@@ -76,39 +77,47 @@ void LogSettings() {
         LOG_INFO(Config, "{}: {}", name, value);
     };
 
+    const auto to_string = [](AudioEmulation emulation) -> std::string_view {
+        switch (emulation) {
+        case AudioEmulation::HLE:
+            return "HLE";
+        case AudioEmulation::LLE:
+            return "LLE";
+        case AudioEmulation::LLEMultithreaded:
+            return "LLE Multithreaded";
+        }
+    };
+
     LOG_INFO(Config, "Citra Configuration:");
-    log_setting("Core_UseCpuJit", values.use_cpu_jit);
-    log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage);
-    log_setting("Renderer_UseGLES", values.use_gles);
-    log_setting("Renderer_UseHwRenderer", values.use_hw_renderer);
-    log_setting("Renderer_UseHwShader", values.use_hw_shader);
-    log_setting("Renderer_SeparableShader", values.separable_shader);
-    log_setting("Renderer_ShadersAccurateMul", values.shaders_accurate_mul);
-    log_setting("Renderer_UseShaderJit", values.use_shader_jit);
-    log_setting("Renderer_UseResolutionFactor", values.resolution_factor);
-    log_setting("Renderer_FrameLimit", values.frame_limit);
-    log_setting("Renderer_UseFrameLimitAlternate", values.use_frame_limit_alternate);
-    log_setting("Renderer_FrameLimitAlternate", values.frame_limit_alternate);
-    log_setting("Renderer_VSyncNew", values.use_vsync_new);
-    log_setting("Renderer_PostProcessingShader", values.pp_shader_name);
-    log_setting("Renderer_FilterMode", values.filter_mode);
-    log_setting("Renderer_TextureFilterName", values.texture_filter_name);
-    log_setting("Stereoscopy_Render3d", values.render_3d);
-    log_setting("Stereoscopy_Factor3d", values.factor_3d);
-    log_setting("Stereoscopy_MonoRenderLeftEye", values.mono_render_left_eye);
-    log_setting("Layout_LayoutOption", values.layout_option);
-    log_setting("Layout_SwapScreen", values.swap_screen);
-    log_setting("Layout_UprightScreen", values.upright_screen);
-    log_setting("Utility_DumpTextures", values.dump_textures);
-    log_setting("Utility_CustomTextures", values.custom_textures);
-    log_setting("Utility_UseDiskShaderCache", values.use_disk_shader_cache);
-    log_setting("Audio_EnableDspLle", values.enable_dsp_lle);
-    log_setting("Audio_EnableDspLleMultithread", values.enable_dsp_lle_multithread);
-    log_setting("Audio_OutputEngine", values.sink_id);
-    log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching);
-    log_setting("Audio_OutputDevice", values.audio_device_id);
-    log_setting("Audio_InputDeviceType", values.mic_input_type);
-    log_setting("Audio_InputDevice", values.mic_input_device);
+    log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue());
+    log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue());
+    log_setting("Renderer_UseGLES", values.use_gles.GetValue());
+    log_setting("Renderer_UseHwRenderer", values.use_hw_renderer.GetValue());
+    log_setting("Renderer_UseHwShader", values.use_hw_shader.GetValue());
+    log_setting("Renderer_SeparableShader", values.separable_shader.GetValue());
+    log_setting("Renderer_ShadersAccurateMul", values.shaders_accurate_mul.GetValue());
+    log_setting("Renderer_UseShaderJit", values.use_shader_jit.GetValue());
+    log_setting("Renderer_UseResolutionFactor", values.resolution_factor.GetValue());
+    log_setting("Renderer_FrameLimit", values.frame_limit.GetValue());
+    log_setting("Renderer_VSyncNew", values.use_vsync_new.GetValue());
+    log_setting("Renderer_PostProcessingShader", values.pp_shader_name.GetValue());
+    log_setting("Renderer_FilterMode", values.filter_mode.GetValue());
+    log_setting("Renderer_TextureFilterName", values.texture_filter_name.GetValue());
+    log_setting("Stereoscopy_Render3d", values.render_3d.GetValue());
+    log_setting("Stereoscopy_Factor3d", values.factor_3d.GetValue());
+    log_setting("Stereoscopy_MonoRenderLeftEye", values.mono_render_left_eye.GetValue());
+    log_setting("Layout_LayoutOption", values.layout_option.GetValue());
+    log_setting("Layout_SwapScreen", values.swap_screen.GetValue());
+    log_setting("Layout_UprightScreen", values.upright_screen.GetValue());
+    log_setting("Utility_DumpTextures", values.dump_textures.GetValue());
+    log_setting("Utility_CustomTextures", values.custom_textures.GetValue());
+    log_setting("Utility_UseDiskShaderCache", values.use_disk_shader_cache.GetValue());
+    log_setting("Audio_Emulation", to_string(values.audio_emulation.GetValue()));
+    log_setting("Audio_OutputEngine", values.sink_id.GetValue());
+    log_setting("Audio_EnableAudioStretching", values.enable_audio_stretching.GetValue());
+    log_setting("Audio_OutputDevice", values.audio_device_id.GetValue());
+    log_setting("Audio_InputDeviceType", values.mic_input_type.GetValue());
+    log_setting("Audio_InputDevice", values.mic_input_device.GetValue());
     using namespace Service::CAM;
     log_setting("Camera_OuterRightName", values.camera_name[OuterRightCamera]);
     log_setting("Camera_OuterRightConfig", values.camera_config[OuterRightCamera]);
@@ -119,23 +128,68 @@ void LogSettings() {
     log_setting("Camera_OuterLeftName", values.camera_name[OuterLeftCamera]);
     log_setting("Camera_OuterLeftConfig", values.camera_config[OuterLeftCamera]);
     log_setting("Camera_OuterLeftFlip", values.camera_flip[OuterLeftCamera]);
-    log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd);
-    log_setting("DataStorage_UseCustomStorage", values.use_custom_storage);
+    log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue());
+    log_setting("DataStorage_UseCustomStorage", values.use_custom_storage.GetValue());
     if (values.use_custom_storage) {
         log_setting("DataStorage_SdmcDir", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir));
         log_setting("DataStorage_NandDir", FileUtil::GetUserPath(FileUtil::UserPath::NANDDir));
     }
-    log_setting("System_IsNew3ds", values.is_new_3ds);
-    log_setting("System_RegionValue", values.region_value);
-    log_setting("Debugging_UseGdbstub", values.use_gdbstub);
-    log_setting("Debugging_GdbstubPort", values.gdbstub_port);
+    log_setting("System_IsNew3ds", values.is_new_3ds.GetValue());
+    log_setting("System_RegionValue", values.region_value.GetValue());
+    log_setting("Debugging_UseGdbstub", values.use_gdbstub.GetValue());
+    log_setting("Debugging_GdbstubPort", values.gdbstub_port.GetValue());
+}
+
+bool IsConfiguringGlobal() {
+    return configuring_global;
+}
+
+void SetConfiguringGlobal(bool is_global) {
+    configuring_global = is_global;
 }
 
 float Volume() {
     if (values.audio_muted) {
         return 0.0f;
     }
-    return values.volume;
+    return values.volume.GetValue();
+}
+
+void RestoreGlobalState(bool is_powered_on) {
+    // If a game is running, DO NOT restore the global settings state
+    if (is_powered_on) {
+        return;
+    }
+
+    // Audio
+    values.audio_emulation.SetGlobal(true);
+    values.enable_audio_stretching.SetGlobal(true);
+    values.volume.SetGlobal(true);
+
+    // Core
+    values.cpu_clock_percentage.SetGlobal(true);
+    values.is_new_3ds.SetGlobal(true);
+
+    // Renderer
+    values.use_hw_renderer.SetGlobal(true);
+    values.use_hw_shader.SetGlobal(true);
+    values.separable_shader.SetGlobal(true);
+    values.use_disk_shader_cache.SetGlobal(true);
+    values.shaders_accurate_mul.SetGlobal(true);
+    values.use_vsync_new.SetGlobal(true);
+    values.resolution_factor.SetGlobal(true);
+    values.frame_limit.SetGlobal(true);
+    values.texture_filter_name.SetGlobal(true);
+    values.layout_option.SetGlobal(true);
+    values.swap_screen.SetGlobal(true);
+    values.upright_screen.SetGlobal(true);
+    values.bg_red.SetGlobal(true);
+    values.bg_green.SetGlobal(true);
+    values.bg_blue.SetGlobal(true);
+    values.render_3d.SetGlobal(true);
+    values.factor_3d.SetGlobal(true);
+    values.filter_mode.SetGlobal(true);
+    values.pp_shader_name.SetGlobal(true);
 }
 
 void LoadProfile(int index) {
diff --git a/src/common/settings.h b/src/common/settings.h
new file mode 100644
index 000000000..38f69a97e
--- /dev/null
+++ b/src/common/settings.h
@@ -0,0 +1,538 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <atomic>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "common/common_types.h"
+#include "core/hle/service/cam/cam_params.h"
+
+namespace Settings {
+
+enum class InitClock : u32 {
+    SystemTime = 0,
+    FixedTime = 1,
+};
+
+enum class LayoutOption : u32 {
+    Default,
+    SingleScreen,
+    LargeScreen,
+    SideScreen,
+#ifndef ANDROID
+    SeparateWindows,
+#endif
+    // Similiar to default, but better for mobile devices in portrait mode. Top screen in clamped to
+    // the top of the frame, and the bottom screen is enlarged to match the top screen.
+    MobilePortrait,
+
+    // Similiar to LargeScreen, but better for mobile devices in landscape mode. The screens are
+    // clamped to the top of the frame, and the bottom screen is a bit bigger.
+    MobileLandscape,
+};
+
+enum class MicInputType : u32 {
+    None = 0,
+    Real = 1,
+    Static = 2,
+};
+
+enum class StereoRenderOption : u32 {
+    Off = 0,
+    SideBySide = 1,
+    Anaglyph = 2,
+    Interlaced = 3,
+    ReverseInterlaced = 4,
+    CardboardVR = 5
+};
+
+enum class AudioEmulation : u32 { HLE = 0, LLE = 1, LLEMultithreaded = 2 };
+
+namespace NativeButton {
+
+enum Values {
+    A,
+    B,
+    X,
+    Y,
+    Up,
+    Down,
+    Left,
+    Right,
+    L,
+    R,
+    Start,
+    Select,
+    Debug,
+    Gpio14,
+
+    ZL,
+    ZR,
+
+    Home,
+
+    NumButtons,
+};
+
+constexpr int BUTTON_HID_BEGIN = A;
+constexpr int BUTTON_IR_BEGIN = ZL;
+constexpr int BUTTON_NS_BEGIN = Home;
+
+constexpr int BUTTON_HID_END = BUTTON_IR_BEGIN;
+constexpr int BUTTON_IR_END = BUTTON_NS_BEGIN;
+constexpr int BUTTON_NS_END = NumButtons;
+
+constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
+constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN;
+constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
+
+static const std::array<const char*, NumButtons> mapping = {{
+    "button_a",
+    "button_b",
+    "button_x",
+    "button_y",
+    "button_up",
+    "button_down",
+    "button_left",
+    "button_right",
+    "button_l",
+    "button_r",
+    "button_start",
+    "button_select",
+    "button_debug",
+    "button_gpio14",
+    "button_zl",
+    "button_zr",
+    "button_home",
+}};
+
+} // namespace NativeButton
+
+namespace NativeAnalog {
+enum Values {
+    CirclePad,
+    CStick,
+    NumAnalogs,
+};
+
+constexpr std::array<const char*, NumAnalogs> mapping = {{
+    "circle_pad",
+    "c_stick",
+}};
+} // namespace NativeAnalog
+
+/** The Setting class is a simple resource manager. It defines a label and default value alongside
+ * the actual value of the setting for simpler and less-error prone use with frontend
+ * configurations. Specifying a default value and label is required. A minimum and maximum range can
+ * be specified for sanitization.
+ */
+template <typename Type, bool ranged = false>
+class Setting {
+protected:
+    Setting() = default;
+
+    /**
+     * Only sets the setting to the given initializer, leaving the other members to their default
+     * initializers.
+     *
+     * @param global_val Initial value of the setting
+     */
+    explicit Setting(const Type& val) : value{val} {}
+
+public:
+    /**
+     * Sets a default value, label, and setting value.
+     *
+     * @param default_val Intial value of the setting, and default value of the setting
+     * @param name Label for the setting
+     */
+    explicit Setting(const Type& default_val, const std::string& name) requires(!ranged)
+        : value{default_val}, default_value{default_val}, label{name} {}
+    virtual ~Setting() = default;
+
+    /**
+     * Sets a default value, minimum value, maximum value, and label.
+     *
+     * @param default_val Intial value of the setting, and default value of the setting
+     * @param min_val Sets the minimum allowed value of the setting
+     * @param max_val Sets the maximum allowed value of the setting
+     * @param name Label for the setting
+     */
+    explicit Setting(const Type& default_val, const Type& min_val, const Type& max_val,
+                     const std::string& name) requires(ranged)
+        : value{default_val},
+          default_value{default_val}, maximum{max_val}, minimum{min_val}, label{name} {}
+
+    /**
+     *  Returns a reference to the setting's value.
+     *
+     * @returns A reference to the setting
+     */
+    [[nodiscard]] virtual const Type& GetValue() const {
+        return value;
+    }
+
+    /**
+     * Sets the setting to the given value.
+     *
+     * @param val The desired value
+     */
+    virtual void SetValue(const Type& val) {
+        Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
+        std::swap(value, temp);
+    }
+
+    /**
+     * Returns the value that this setting was created with.
+     *
+     * @returns A reference to the default value
+     */
+    [[nodiscard]] const Type& GetDefault() const {
+        return default_value;
+    }
+
+    /**
+     * Returns the label this setting was created with.
+     *
+     * @returns A reference to the label
+     */
+    [[nodiscard]] const std::string& GetLabel() const {
+        return label;
+    }
+
+    /**
+     * Assigns a value to the setting.
+     *
+     * @param val The desired setting value
+     *
+     * @returns A reference to the setting
+     */
+    virtual const Type& operator=(const Type& val) {
+        Type temp{ranged ? std::clamp(val, minimum, maximum) : val};
+        std::swap(value, temp);
+        return value;
+    }
+
+    /**
+     * Returns a reference to the setting.
+     *
+     * @returns A reference to the setting
+     */
+    explicit virtual operator const Type&() const {
+        return value;
+    }
+
+protected:
+    Type value{};               ///< The setting
+    const Type default_value{}; ///< The default value
+    const Type maximum{};       ///< Maximum allowed value of the setting
+    const Type minimum{};       ///< Minimum allowed value of the setting
+    const std::string label{};  ///< The setting's label
+};
+
+/**
+ * The SwitchableSetting class is a slightly more complex version of the Setting class. This adds a
+ * custom setting to switch to when a guest application specifically requires it. The effect is that
+ * other components of the emulator can access the setting's intended value without any need for the
+ * component to ask whether the custom or global setting is needed at the moment.
+ *
+ * By default, the global setting is used.
+ */
+template <typename Type, bool ranged = false>
+class SwitchableSetting : virtual public Setting<Type, ranged> {
+public:
+    /**
+     * Sets a default value, label, and setting value.
+     *
+     * @param default_val Intial value of the setting, and default value of the setting
+     * @param name Label for the setting
+     */
+    explicit SwitchableSetting(const Type& default_val, const std::string& name) requires(!ranged)
+        : Setting<Type>{default_val, name} {}
+    virtual ~SwitchableSetting() = default;
+
+    /**
+     * Sets a default value, minimum value, maximum value, and label.
+     *
+     * @param default_val Intial value of the setting, and default value of the setting
+     * @param min_val Sets the minimum allowed value of the setting
+     * @param max_val Sets the maximum allowed value of the setting
+     * @param name Label for the setting
+     */
+    explicit SwitchableSetting(const Type& default_val, const Type& min_val, const Type& max_val,
+                               const std::string& name) requires(ranged)
+        : Setting<Type, true>{default_val, min_val, max_val, name} {}
+
+    /**
+     * Tells this setting to represent either the global or custom setting when other member
+     * functions are used.
+     *
+     * @param to_global Whether to use the global or custom setting.
+     */
+    void SetGlobal(bool to_global) {
+        use_global = to_global;
+    }
+
+    /**
+     * Returns whether this setting is using the global setting or not.
+     *
+     * @returns The global state
+     */
+    [[nodiscard]] bool UsingGlobal() const {
+        return use_global;
+    }
+
+    /**
+     * Returns either the global or custom setting depending on the values of this setting's global
+     * state or if the global value was specifically requested.
+     *
+     * @param need_global Request global value regardless of setting's state; defaults to false
+     *
+     * @returns The required value of the setting
+     */
+    [[nodiscard]] virtual const Type& GetValue() const override {
+        if (use_global) {
+            return this->value;
+        }
+        return custom;
+    }
+    [[nodiscard]] virtual const Type& GetValue(bool need_global) const {
+        if (use_global || need_global) {
+            return this->value;
+        }
+        return custom;
+    }
+
+    /**
+     * Sets the current setting value depending on the global state.
+     *
+     * @param val The new value
+     */
+    void SetValue(const Type& val) override {
+        Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
+        if (use_global) {
+            std::swap(this->value, temp);
+        } else {
+            std::swap(custom, temp);
+        }
+    }
+
+    /**
+     * Assigns the current setting value depending on the global state.
+     *
+     * @param val The new value
+     *
+     * @returns A reference to the current setting value
+     */
+    const Type& operator=(const Type& val) override {
+        Type temp{ranged ? std::clamp(val, this->minimum, this->maximum) : val};
+        if (use_global) {
+            std::swap(this->value, temp);
+            return this->value;
+        }
+        std::swap(custom, temp);
+        return custom;
+    }
+
+    /**
+     * Returns the current setting value depending on the global state.
+     *
+     * @returns A reference to the current setting value
+     */
+    virtual explicit operator const Type&() const override {
+        if (use_global) {
+            return this->value;
+        }
+        return custom;
+    }
+
+protected:
+    bool use_global{true}; ///< The setting's global state
+    Type custom{};         ///< The custom value of the setting
+};
+
+/**
+ * The InputSetting class allows for getting a reference to either the global or custom members.
+ * This is required as we cannot easily modify the values of user-defined types within containers
+ * using the SetValue() member function found in the Setting class. The primary purpose of this
+ * class is to store an array of 10 PlayerInput structs for both the global and custom setting and
+ * allows for easily accessing and modifying both settings.
+ */
+template <typename Type>
+class InputSetting final {
+public:
+    InputSetting() = default;
+    explicit InputSetting(Type val) : Setting<Type>(val) {}
+    ~InputSetting() = default;
+    void SetGlobal(bool to_global) {
+        use_global = to_global;
+    }
+    [[nodiscard]] bool UsingGlobal() const {
+        return use_global;
+    }
+    [[nodiscard]] Type& GetValue(bool need_global = false) {
+        if (use_global || need_global) {
+            return global;
+        }
+        return custom;
+    }
+
+private:
+    bool use_global{true}; ///< The setting's global state
+    Type global{};         ///< The setting
+    Type custom{};         ///< The custom setting value
+};
+
+struct InputProfile {
+    std::string name;
+    std::array<std::string, NativeButton::NumButtons> buttons;
+    std::array<std::string, NativeAnalog::NumAnalogs> analogs;
+    std::string motion_device;
+    std::string touch_device;
+    bool use_touch_from_button;
+    int touch_from_button_map_index;
+    std::string udp_input_address;
+    u16 udp_input_port;
+    u8 udp_pad_index;
+};
+
+struct TouchFromButtonMap {
+    std::string name;
+    std::vector<std::string> buttons;
+};
+
+/// A special region value indicating that citra will automatically select a region
+/// value to fit the region lockout info of the game
+static constexpr s32 REGION_VALUE_AUTO_SELECT = -1;
+
+struct Values {
+    // Controls
+    InputProfile current_input_profile;       ///< The current input profile
+    int current_input_profile_index;          ///< The current input profile index
+    std::vector<InputProfile> input_profiles; ///< The list of input profiles
+    std::vector<TouchFromButtonMap> touch_from_button_maps;
+
+    // Core
+    Setting<bool> use_cpu_jit{true, "use_cpu_jit"};
+    SwitchableSetting<s32, true> cpu_clock_percentage{100, 5, 400, "cpu_clock_percentage"};
+    SwitchableSetting<bool> is_new_3ds{true, "is_new_3ds"};
+
+    // Data Storage
+    Setting<bool> use_virtual_sd{true, "use_virtual_sd"};
+    Setting<bool> use_custom_storage{false, "use_custom_storage"};
+
+    // System
+    SwitchableSetting<s32> region_value{REGION_VALUE_AUTO_SELECT, "region_value"};
+    Setting<InitClock> init_clock{InitClock::SystemTime, "init_clock"};
+    Setting<u64> init_time{946681277ULL, "init_time"};
+    Setting<s64> init_time_offset{0, "init_time_offset"};
+
+    // Renderer
+    Setting<bool> use_gles{false, "use_gles"};
+    SwitchableSetting<bool> use_hw_renderer{true, "use_hw_renderer"};
+    SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"};
+    SwitchableSetting<bool> separable_shader{false, "use_separable_shader"};
+    SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
+    SwitchableSetting<bool> shaders_accurate_mul{true, "shaders_accurate_mul"};
+    SwitchableSetting<bool> use_vsync_new{true, "use_vsync_new"};
+    Setting<bool> use_shader_jit{true, "use_shader_jit"};
+    SwitchableSetting<u16, true> resolution_factor{1, 1, 10, "resolution_factor"};
+    SwitchableSetting<u16, true> frame_limit{100, 0, 1000, "frame_limit"};
+    SwitchableSetting<std::string> texture_filter_name{"none", "texture_filter_name"};
+
+    SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
+    SwitchableSetting<bool> swap_screen{false, "swap_screen"};
+    SwitchableSetting<bool> upright_screen{false, "upright_screen"};
+    Setting<bool> custom_layout{false, "custom_layout"};
+    Setting<u16> custom_top_left{0, "custom_top_left"};
+    Setting<u16> custom_top_top{0, "custom_top_top"};
+    Setting<u16> custom_top_right{400, "custom_top_right"};
+    Setting<u16> custom_top_bottom{240, "custom_top_bottom"};
+    Setting<u16> custom_bottom_left{40, "custom_bottom_left"};
+    Setting<u16> custom_bottom_top{240, "custom_bottom_top"};
+    Setting<u16> custom_bottom_right{360, "custom_bottom_right"};
+    Setting<u16> custom_bottom_bottom{480, "custom_bottom_bottom"};
+
+    SwitchableSetting<double> bg_red{0.f, "bg_red"};
+    SwitchableSetting<double> bg_green{0.f, "bg_green"};
+    SwitchableSetting<double> bg_blue{0.f, "bg_blue"};
+
+    SwitchableSetting<StereoRenderOption> render_3d{StereoRenderOption::Off, "render_3d"};
+    SwitchableSetting<u32> factor_3d{0, "factor_3d"};
+
+    Setting<bool> mono_render_left_eye{false, "mono_render_left_eye"};
+    Setting<s32> cardboard_screen_size{85, "cardboard_screen_size"};
+    Setting<s32> cardboard_x_shift{0, "cardboard_x_shift"};
+    Setting<s32> cardboard_y_shift{0, "cardboard_y_shift"};
+
+    SwitchableSetting<bool> filter_mode{true, "filter_mode"};
+    SwitchableSetting<std::string> pp_shader_name{"none (builtin)", "pp_shader_name"};
+
+    Setting<bool> dump_textures{false, "dump_textures"};
+    Setting<bool> custom_textures{false, "custom_textures"};
+    Setting<bool> preload_textures{false, "preload_textures"};
+
+    // Audio
+    bool audio_muted;
+    SwitchableSetting<AudioEmulation> audio_emulation{AudioEmulation::HLE, "audio_emulation"};
+    Setting<std::string> sink_id{"auto", "output_engine"};
+    SwitchableSetting<bool> enable_audio_stretching{true, "enable_audio_stretching"};
+    Setting<std::string> audio_device_id{"auto", "output_device"};
+    SwitchableSetting<float, true> volume{1.f, 0.f, 1.f, "volume"};
+    Setting<MicInputType> mic_input_type{MicInputType::None, "mic_input_type"};
+    Setting<std::string> mic_input_device{"Default", "mic_input_device"};
+
+    // Camera
+    std::array<std::string, Service::CAM::NumCameras> camera_name;
+    std::array<std::string, Service::CAM::NumCameras> camera_config;
+    std::array<int, Service::CAM::NumCameras> camera_flip;
+
+    // Debugging
+    bool record_frame_times;
+    std::unordered_map<std::string, bool> lle_modules;
+    Setting<bool> use_gdbstub{false, "use_gdbstub"};
+    Setting<u16> gdbstub_port{24689, "gdbstub_port"};
+
+    // Miscellaneous
+    Setting<std::string> log_filter{"*:Info", "log_filter"};
+
+    // Video Dumping
+    std::string output_format;
+    std::string format_options;
+
+    std::string video_encoder;
+    std::string video_encoder_options;
+    u64 video_bitrate;
+
+    std::string audio_encoder;
+    std::string audio_encoder_options;
+    u64 audio_bitrate;
+};
+
+extern Values values;
+
+bool IsConfiguringGlobal();
+void SetConfiguringGlobal(bool is_global);
+
+float Volume();
+
+void Apply();
+void LogSettings();
+
+// Restore the global state of all applicable settings in the Values struct
+void RestoreGlobalState(bool is_powered_on);
+
+// Input profiles
+void LoadProfile(int index);
+void SaveProfile(int index);
+void CreateProfile(std::string name);
+void DeleteProfile(int index);
+void RenameCurrentProfile(std::string new_name);
+
+} // namespace Settings
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index d57aef53e..fb9b78600 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -454,8 +454,6 @@ add_library(core STATIC
     rpc/udp_server.h
     savestate.cpp
     savestate.h
-    settings.cpp
-    settings.h
     telemetry_session.cpp
     telemetry_session.h
     tracer/citrace.h
diff --git a/src/core/arm/exclusive_monitor.cpp b/src/core/arm/exclusive_monitor.cpp
index 5c8a3bc20..53906eff9 100644
--- a/src/core/arm/exclusive_monitor.cpp
+++ b/src/core/arm/exclusive_monitor.cpp
@@ -4,9 +4,9 @@
 #if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
 #include "core/arm/dynarmic/arm_exclusive_monitor.h"
 #endif
+#include "common/settings.h"
 #include "core/arm/exclusive_monitor.h"
 #include "core/memory.h"
-#include "core/settings.h"
 
 namespace Core {
 
diff --git a/src/core/cheats/gateway_cheat.h b/src/core/cheats/gateway_cheat.h
index 46512fb96..14c81786b 100644
--- a/src/core/cheats/gateway_cheat.h
+++ b/src/core/cheats/gateway_cheat.h
@@ -6,6 +6,8 @@
 
 #include <atomic>
 #include <memory>
+#include <vector>
+#include "common/common_types.h"
 #include "core/cheats/cheat_base.h"
 
 namespace Cheats {
diff --git a/src/core/core.cpp b/src/core/core.cpp
index e409265dc..6d2bd6682 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -25,6 +25,7 @@
 #ifdef ENABLE_FFMPEG_VIDEO_DUMPER
 #include "core/dumping/ffmpeg_backend.h"
 #endif
+#include "common/settings.h"
 #include "core/custom_tex_cache.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/global.h"
@@ -45,7 +46,6 @@
 #include "core/loader/loader.h"
 #include "core/movie.h"
 #include "core/rpc/rpc_server.h"
-#include "core/settings.h"
 #include "network/network.h"
 #include "video_core/renderer_base.h"
 #include "video_core/video_core.h"
@@ -365,7 +365,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
 
     memory = std::make_unique<Memory::MemorySystem>();
 
-    timing = std::make_unique<Timing>(num_cores, Settings::values.cpu_clock_percentage);
+    timing = std::make_unique<Timing>(num_cores, Settings::values.cpu_clock_percentage.GetValue());
 
     kernel = std::make_unique<Kernel::KernelSystem>(
         *memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores, n3ds_mode);
@@ -395,17 +395,19 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
     kernel->SetCPUs(cpu_cores);
     kernel->SetRunningCPU(cpu_cores[0].get());
 
-    if (Settings::values.enable_dsp_lle) {
-        dsp_core = std::make_unique<AudioCore::DspLle>(*memory,
-                                                       Settings::values.enable_dsp_lle_multithread);
-    } else {
+    const auto audio_emulation = Settings::values.audio_emulation.GetValue();
+    if (audio_emulation == Settings::AudioEmulation::HLE) {
         dsp_core = std::make_unique<AudioCore::DspHle>(*memory);
+    } else {
+        const bool multithread = audio_emulation == Settings::AudioEmulation::LLEMultithreaded;
+        dsp_core = std::make_unique<AudioCore::DspLle>(*memory, multithread);
     }
 
     memory->SetDSP(*dsp_core);
 
-    dsp_core->SetSink(Settings::values.sink_id, Settings::values.audio_device_id);
-    dsp_core->EnableStretching(Settings::values.enable_audio_stretching);
+    dsp_core->SetSink(Settings::values.sink_id.GetValue(),
+                      Settings::values.audio_device_id.GetValue());
+    dsp_core->EnableStretching(Settings::values.enable_audio_stretching.GetValue());
 
     telemetry_session = std::make_unique<Core::TelemetrySession>();
 
diff --git a/src/core/dumping/ffmpeg_backend.cpp b/src/core/dumping/ffmpeg_backend.cpp
index cad321d87..5f38d02fd 100644
--- a/src/core/dumping/ffmpeg_backend.cpp
+++ b/src/core/dumping/ffmpeg_backend.cpp
@@ -7,10 +7,10 @@
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "common/param_package.h"
+#include "common/settings.h"
 #include "common/string_util.h"
 #include "core/dumping/ffmpeg_backend.h"
 #include "core/hw/gpu.h"
-#include "core/settings.h"
 #include "video_core/renderer_base.h"
 #include "video_core/video_core.h"
 
diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp
index 4c3ce6d69..81db641e4 100644
--- a/src/core/file_sys/archive_sdmc.cpp
+++ b/src/core/file_sys/archive_sdmc.cpp
@@ -7,11 +7,11 @@
 #include "common/archives.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/file_sys/archive_sdmc.h"
 #include "core/file_sys/disk_archive.h"
 #include "core/file_sys/errors.h"
 #include "core/file_sys/path_parser.h"
-#include "core/settings.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp
index 241e93f0a..8272ce789 100644
--- a/src/core/file_sys/archive_sdmcwriteonly.cpp
+++ b/src/core/file_sys/archive_sdmcwriteonly.cpp
@@ -5,11 +5,11 @@
 #include <memory>
 #include "common/archives.h"
 #include "common/file_util.h"
+#include "common/settings.h"
 #include "core/file_sys/archive_sdmcwriteonly.h"
 #include "core/file_sys/directory_backend.h"
 #include "core/file_sys/errors.h"
 #include "core/file_sys/file_backend.h"
-#include "core/settings.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 1144c049f..2222ece02 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -4,9 +4,10 @@
 
 #include <cmath>
 #include <mutex>
+#include "common/settings.h"
+#include "core/3ds.h"
 #include "core/frontend/emu_window.h"
 #include "core/frontend/input.h"
-#include "core/settings.h"
 
 namespace Frontend {
 /// We need a global touch state that is shared across the different window instances
@@ -63,14 +64,14 @@ EmuWindow::~EmuWindow() = default;
  */
 static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigned framebuffer_x,
                                 unsigned framebuffer_y) {
-    if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide) {
         return (framebuffer_y >= layout.bottom_screen.top &&
                 framebuffer_y < layout.bottom_screen.bottom &&
                 ((framebuffer_x >= layout.bottom_screen.left / 2 &&
                   framebuffer_x < layout.bottom_screen.right / 2) ||
                  (framebuffer_x >= (layout.bottom_screen.left / 2) + (layout.width / 2) &&
                   framebuffer_x < (layout.bottom_screen.right / 2) + (layout.width / 2))));
-    } else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
+    } else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
         return (framebuffer_y >= layout.bottom_screen.top &&
                 framebuffer_y < layout.bottom_screen.bottom &&
                 ((framebuffer_x >= layout.bottom_screen.left &&
@@ -88,13 +89,13 @@ static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigne
 
 std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) const {
     if (new_x >= framebuffer_layout.width / 2) {
-        if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide)
+        if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide)
             new_x -= framebuffer_layout.width / 2;
-        else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR)
+        else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR)
             new_x -=
                 (framebuffer_layout.width / 2) - (framebuffer_layout.cardboard.user_x_shift * 2);
     }
-    if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide) {
         new_x = std::max(new_x, framebuffer_layout.bottom_screen.left / 2);
         new_x = std::min(new_x, framebuffer_layout.bottom_screen.right / 2 - 1);
     } else {
@@ -122,14 +123,14 @@ bool EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
         return false;
 
     if (framebuffer_x >= framebuffer_layout.width / 2) {
-        if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide)
+        if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide)
             framebuffer_x -= framebuffer_layout.width / 2;
-        else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR)
+        else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR)
             framebuffer_x -=
                 (framebuffer_layout.width / 2) - (framebuffer_layout.cardboard.user_x_shift * 2);
     }
     std::lock_guard guard(touch_state->mutex);
-    if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::SideBySide) {
         touch_state->touch_x =
             static_cast<float>(framebuffer_x - framebuffer_layout.bottom_screen.left / 2) /
             (framebuffer_layout.bottom_screen.right / 2 -
@@ -173,55 +174,59 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height,
                                                bool is_portrait_mode) {
     Layout::FramebufferLayout layout;
     const auto layout_option = Settings::values.layout_option;
-    const auto min_size =
-        Layout::GetMinimumSizeFromLayout(layout_option, Settings::values.upright_screen);
+    const auto min_size = Layout::GetMinimumSizeFromLayout(
+        layout_option.GetValue(), Settings::values.upright_screen.GetValue());
 
-    if (Settings::values.custom_layout == true) {
+    if (Settings::values.custom_layout.GetValue() == true) {
         layout = Layout::CustomFrameLayout(width, height);
     } else {
         width = std::max(width, min_size.first);
         height = std::max(height, min_size.second);
 
         // If in portrait mode, only the MobilePortrait option really makes sense
-        const Settings::LayoutOption layout_option = is_portrait_mode
-                                                         ? Settings::LayoutOption::MobilePortrait
-                                                         : Settings::values.layout_option;
+        const Settings::LayoutOption layout_option =
+            is_portrait_mode ? Settings::LayoutOption::MobilePortrait
+                             : Settings::values.layout_option.GetValue();
 
         switch (layout_option) {
         case Settings::LayoutOption::SingleScreen:
-            layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen,
-                                               Settings::values.upright_screen);
+            layout =
+                Layout::SingleFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                          Settings::values.upright_screen.GetValue());
             break;
         case Settings::LayoutOption::LargeScreen:
-            layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen,
-                                              Settings::values.upright_screen);
+            layout =
+                Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                         Settings::values.upright_screen.GetValue());
             break;
         case Settings::LayoutOption::SideScreen:
-            layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen,
-                                             Settings::values.upright_screen);
+            layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                             Settings::values.upright_screen.GetValue());
             break;
 #ifndef ANDROID
         case Settings::LayoutOption::SeparateWindows:
             layout = Layout::SeparateWindowsLayout(width, height, is_secondary,
-                                                   Settings::values.upright_screen);
+                                                   Settings::values.upright_screen.GetValue());
             break;
 #endif
         case Settings::LayoutOption::MobilePortrait:
-            layout = Layout::MobilePortraitFrameLayout(width, height, Settings::values.swap_screen);
+            layout = Layout::MobilePortraitFrameLayout(width, height,
+                                                       Settings::values.swap_screen.GetValue());
             break;
         case Settings::LayoutOption::MobileLandscape:
-            layout = Layout::MobileLandscapeFrameLayout(width, height, Settings::values.swap_screen,
-                                                        2.25f, false);
+            layout = Layout::MobileLandscapeFrameLayout(
+                width, height, Settings::values.swap_screen.GetValue(), 2.25f, false);
             break;
         case Settings::LayoutOption::Default:
         default:
-            layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen,
-                                                Settings::values.upright_screen);
+            layout =
+                Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                           Settings::values.upright_screen.GetValue());
             break;
         }
         UpdateMinimumWindowSize(min_size);
     }
-    if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
         layout = Layout::GetCardboardSettings(layout);
     }
     NotifyFramebufferLayoutChanged(layout);
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index ad476edcc..4b3512d6a 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -5,9 +5,9 @@
 #include <cmath>
 
 #include "common/assert.h"
+#include "common/settings.h"
 #include "core/3ds.h"
 #include "core/frontend/framebuffer_layout.h"
-#include "core/settings.h"
 
 namespace Layout {
 
@@ -355,12 +355,14 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height) {
 
     FramebufferLayout res{width, height, true, true, {}, {}, !Settings::values.upright_screen};
 
-    Common::Rectangle<u32> top_screen{
-        Settings::values.custom_top_left, Settings::values.custom_top_top,
-        Settings::values.custom_top_right, Settings::values.custom_top_bottom};
-    Common::Rectangle<u32> bot_screen{
-        Settings::values.custom_bottom_left, Settings::values.custom_bottom_top,
-        Settings::values.custom_bottom_right, Settings::values.custom_bottom_bottom};
+    Common::Rectangle<u32> top_screen{Settings::values.custom_top_left.GetValue(),
+                                      Settings::values.custom_top_top.GetValue(),
+                                      Settings::values.custom_top_right.GetValue(),
+                                      Settings::values.custom_top_bottom.GetValue()};
+    Common::Rectangle<u32> bot_screen{Settings::values.custom_bottom_left.GetValue(),
+                                      Settings::values.custom_bottom_top.GetValue(),
+                                      Settings::values.custom_bottom_right.GetValue(),
+                                      Settings::values.custom_bottom_bottom.GetValue()};
 
     res.top_screen = top_screen;
     res.bottom_screen = bot_screen;
@@ -369,20 +371,21 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height) {
 
 FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary) {
     FramebufferLayout layout;
-    if (Settings::values.custom_layout == true) {
-        layout = CustomFrameLayout(
-            std::max(Settings::values.custom_top_right, Settings::values.custom_bottom_right),
-            std::max(Settings::values.custom_top_bottom, Settings::values.custom_bottom_bottom));
+    if (Settings::values.custom_layout.GetValue() == true) {
+        layout = CustomFrameLayout(std::max(Settings::values.custom_top_right.GetValue(),
+                                            Settings::values.custom_bottom_right.GetValue()),
+                                   std::max(Settings::values.custom_top_bottom.GetValue(),
+                                            Settings::values.custom_bottom_bottom.GetValue()));
     } else {
         int width, height;
-        switch (Settings::values.layout_option) {
+        switch (Settings::values.layout_option.GetValue()) {
         case Settings::LayoutOption::SingleScreen:
 #ifndef ANDROID
         case Settings::LayoutOption::SeparateWindows:
 #endif
         {
-            const bool swap_screens = is_secondary || Settings::values.swap_screen;
-            if (Settings::values.upright_screen) {
+            const bool swap_screens = is_secondary || Settings::values.swap_screen.GetValue();
+            if (Settings::values.upright_screen.GetValue()) {
                 if (swap_screens) {
                     width = Core::kScreenBottomHeight * res_scale;
                     height = Core::kScreenBottomWidth * res_scale;
@@ -399,13 +402,13 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
                     height = Core::kScreenTopHeight * res_scale;
                 }
             }
-            layout =
-                SingleFrameLayout(width, height, swap_screens, Settings::values.upright_screen);
+            layout = SingleFrameLayout(width, height, swap_screens,
+                                       Settings::values.upright_screen.GetValue());
             break;
         }
         case Settings::LayoutOption::LargeScreen:
-            if (Settings::values.upright_screen) {
-                if (Settings::values.swap_screen) {
+            if (Settings::values.upright_screen.GetValue()) {
+                if (Settings::values.swap_screen.GetValue()) {
                     width = Core::kScreenBottomHeight * res_scale;
                     height = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
                 } else {
@@ -413,7 +416,7 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
                     height = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 4) * res_scale;
                 }
             } else {
-                if (Settings::values.swap_screen) {
+                if (Settings::values.swap_screen.GetValue()) {
                     width = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
                     height = Core::kScreenBottomHeight * res_scale;
                 } else {
@@ -421,51 +424,52 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
                     height = Core::kScreenTopHeight * res_scale;
                 }
             }
-            layout = LargeFrameLayout(width, height, Settings::values.swap_screen,
-                                      Settings::values.upright_screen);
+            layout = LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                      Settings::values.upright_screen.GetValue());
             break;
         case Settings::LayoutOption::SideScreen:
-            if (Settings::values.upright_screen) {
+            if (Settings::values.upright_screen.GetValue()) {
                 width = Core::kScreenTopHeight * res_scale;
                 height = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
             } else {
                 width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
                 height = Core::kScreenTopHeight * res_scale;
             }
-            layout = SideFrameLayout(width, height, Settings::values.swap_screen,
-                                     Settings::values.upright_screen);
+            layout = SideFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                     Settings::values.upright_screen.GetValue());
             break;
         case Settings::LayoutOption::MobilePortrait:
             width = Core::kScreenTopWidth * res_scale;
             height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
-            layout = MobilePortraitFrameLayout(width, height, Settings::values.swap_screen);
+            layout =
+                MobilePortraitFrameLayout(width, height, Settings::values.swap_screen.GetValue());
             break;
         case Settings::LayoutOption::MobileLandscape:
-            if (Settings::values.swap_screen) {
+            if (Settings::values.swap_screen.GetValue()) {
                 width = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 2.25f) * res_scale;
                 height = Core::kScreenBottomHeight * res_scale;
             } else {
                 width = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 2.25f) * res_scale;
                 height = Core::kScreenTopHeight * res_scale;
             }
-            layout = MobileLandscapeFrameLayout(width, height, Settings::values.swap_screen, 2.25f,
-                                                false);
+            layout = MobileLandscapeFrameLayout(
+                width, height, Settings::values.swap_screen.GetValue(), 2.25f, false);
             break;
         case Settings::LayoutOption::Default:
         default:
-            if (Settings::values.upright_screen) {
+            if (Settings::values.upright_screen.GetValue()) {
                 width = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
                 height = Core::kScreenTopWidth * res_scale;
             } else {
                 width = Core::kScreenTopWidth * res_scale;
                 height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
             }
-            layout = DefaultFrameLayout(width, height, Settings::values.swap_screen,
-                                        Settings::values.upright_screen);
+            layout = DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
+                                        Settings::values.upright_screen.GetValue());
             break;
         }
     }
-    if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
         layout = Layout::GetCardboardSettings(layout);
     }
     return layout;
@@ -478,17 +482,17 @@ FramebufferLayout GetCardboardSettings(FramebufferLayout layout) {
     float bottom_screen_left = 0;
     float bottom_screen_top = 0;
 
-    float cardboardScreenScale = Settings::values.cardboard_screen_size / 100.0f;
+    float cardboardScreenScale = Settings::values.cardboard_screen_size.GetValue() / 100.0f;
     float top_screen_width = layout.top_screen.GetWidth() / 2.0f * cardboardScreenScale;
     float top_screen_height = layout.top_screen.GetHeight() / 2.0f * cardboardScreenScale;
     float bottom_screen_width = layout.bottom_screen.GetWidth() / 2.0f * cardboardScreenScale;
     float bottom_screen_height = layout.bottom_screen.GetHeight() / 2.0f * cardboardScreenScale;
-    bool is_swapped = Settings::values.swap_screen;
-    bool is_portrait = layout.height > layout.width;
+    const bool is_swapped = Settings::values.swap_screen.GetValue();
+    const bool is_portrait = layout.height > layout.width;
 
     float cardboardScreenWidth;
     float cardboardScreenHeight;
-    switch (Settings::values.layout_option) {
+    switch (Settings::values.layout_option.GetValue()) {
     case Settings::LayoutOption::MobileLandscape:
     case Settings::LayoutOption::SideScreen:
         // If orientation is portrait, only use MobilePortrait
@@ -524,9 +528,11 @@ FramebufferLayout GetCardboardSettings(FramebufferLayout layout) {
         break;
     }
     float cardboardMaxXShift = (layout.width / 2.0f - cardboardScreenWidth) / 2.0f;
-    float cardboardUserXShift = (Settings::values.cardboard_x_shift / 100.0f) * cardboardMaxXShift;
+    float cardboardUserXShift =
+        (Settings::values.cardboard_x_shift.GetValue() / 100.0f) * cardboardMaxXShift;
     float cardboardMaxYShift = ((float)layout.height - cardboardScreenHeight) / 2.0f;
-    float cardboardUserYShift = (Settings::values.cardboard_y_shift / 100.0f) * cardboardMaxYShift;
+    float cardboardUserYShift =
+        (Settings::values.cardboard_y_shift.GetValue() / 100.0f) * cardboardMaxYShift;
 
     // Center the screens and apply user Y shift
     newLayout.top_screen.left = top_screen_left + cardboardMaxXShift;
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 4566407f6..92b43f285 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -5,7 +5,7 @@
 #pragma once
 
 #include "common/math_util.h"
-#include "core/settings.h"
+#include "common/settings.h"
 
 namespace Layout {
 
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp
index 7928aa55a..cdef9d996 100644
--- a/src/core/hle/kernel/memory.cpp
+++ b/src/core/hle/kernel/memory.cpp
@@ -11,6 +11,7 @@
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/hle/kernel/config_mem.h"
 #include "core/hle/kernel/memory.h"
@@ -19,7 +20,6 @@
 #include "core/hle/kernel/vm_manager.h"
 #include "core/hle/result.h"
 #include "core/memory.h"
-#include "core/settings.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -52,7 +52,7 @@ enum N3DSMode : u8 {
 void KernelSystem::MemoryInit(u32 mem_type, u8 n3ds_mode) {
     ASSERT(mem_type != 1);
 
-    const bool is_new_3ds = Settings::values.is_new_3ds;
+    const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
     u32 reported_mem_type = mem_type;
     if (is_new_3ds) {
         if (n3ds_mode == MemoryMode::Mode6 || n3ds_mode == MemoryMode::Mode6_2) {
diff --git a/src/core/hle/kernel/shared_page.cpp b/src/core/hle/kernel/shared_page.cpp
index 6deebf6b1..0bc839804 100644
--- a/src/core/hle/kernel/shared_page.cpp
+++ b/src/core/hle/kernel/shared_page.cpp
@@ -6,12 +6,12 @@
 #include <cstring>
 #include "common/archives.h"
 #include "common/assert.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/hle/kernel/shared_page.h"
 #include "core/hle/service/ptm/ptm.h"
 #include "core/movie.h"
-#include "core/settings.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -37,7 +37,7 @@ static std::chrono::seconds GetInitTime() {
         return std::chrono::seconds(override_init_time);
     }
 
-    switch (Settings::values.init_clock) {
+    switch (Settings::values.init_clock.GetValue()) {
     case Settings::InitClock::SystemTime: {
         auto now = std::chrono::system_clock::now();
         // If the system time is in daylight saving, we give an additional hour to console time
@@ -47,7 +47,7 @@ static std::chrono::seconds GetInitTime() {
             now = now + std::chrono::hours(1);
 
         // add the offset
-        s64 init_time_offset = Settings::values.init_time_offset;
+        s64 init_time_offset = Settings::values.init_time_offset.GetValue();
         long long days_offset = init_time_offset / 86400;
         long long days_offset_in_seconds = days_offset * 86400; // h/m/s truncated
         unsigned long long seconds_offset =
@@ -58,9 +58,9 @@ static std::chrono::seconds GetInitTime() {
         return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
     }
     case Settings::InitClock::FixedTime:
-        return std::chrono::seconds(Settings::values.init_time);
+        return std::chrono::seconds(Settings::values.init_time.GetValue());
     default:
-        UNREACHABLE_MSG("Invalid InitClock value ({})", Settings::values.init_clock);
+        UNREACHABLE_MSG("Invalid InitClock value ({})", Settings::values.init_clock.GetValue());
     }
 }
 
@@ -85,7 +85,7 @@ Handler::Handler(Core::Timing& timing) : timing(timing) {
                                              std::bind(&Handler::UpdateTimeCallback, this, _1, _2));
     timing.ScheduleEvent(0, update_time_event, 0, 0);
 
-    float slidestate = Settings::values.factor_3d / 100.0f;
+    float slidestate = Settings::values.factor_3d.GetValue() / 100.0f;
     shared_page.sliderstate_3d = static_cast<float_le>(slidestate);
 }
 
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index bb39074a2..ce094a8fe 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -8,6 +8,7 @@
 #include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/file_sys/archive_ncch.h"
 #include "core/file_sys/file_backend.h"
@@ -28,7 +29,6 @@
 #include "core/hle/service/service.h"
 #include "core/hw/aes/ccm.h"
 #include "core/hw/aes/key.h"
-#include "core/settings.h"
 
 SERVICE_CONSTRUCT_IMPL(Service::APT::Module)
 
diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp
index abd3a7ee5..2f4725bc0 100644
--- a/src/core/hle/service/cam/cam.cpp
+++ b/src/core/hle/service/cam/cam.cpp
@@ -6,6 +6,7 @@
 #include "common/archives.h"
 #include "common/bit_set.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/frontend/camera/factory.h"
@@ -19,7 +20,6 @@
 #include "core/hle/service/cam/cam_s.h"
 #include "core/hle/service/cam/cam_u.h"
 #include "core/memory.h"
-#include "core/settings.h"
 
 SERVICE_CONSTRUCT_IMPL(Service::CAM::Module)
 
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index df1665236..0ed45156e 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -12,6 +12,7 @@
 #include "common/archives.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "common/string_util.h"
 #include "common/swap.h"
 #include "core/core.h"
@@ -25,7 +26,6 @@
 #include "core/hle/service/cfg/cfg_nor.h"
 #include "core/hle/service/cfg/cfg_s.h"
 #include "core/hle/service/cfg/cfg_u.h"
-#include "core/settings.h"
 
 SERIALIZE_EXPORT_IMPL(Service::CFG::Module)
 
@@ -189,10 +189,10 @@ void Module::Interface::GetCountryCodeID(Kernel::HLERequestContext& ctx) {
 }
 
 u32 Module::GetRegionValue() {
-    if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT)
+    if (Settings::values.region_value.GetValue() == Settings::REGION_VALUE_AUTO_SELECT)
         return preferred_region_code;
 
-    return Settings::values.region_value;
+    return Settings::values.region_value.GetValue();
 }
 
 void Module::Interface::SecureInfoGetRegion(Kernel::HLERequestContext& ctx, u16 id) {
@@ -654,7 +654,7 @@ void Module::SetPreferredRegionCodes(const std::vector<u32>& region_codes) {
     preferred_region_code = region;
     LOG_INFO(Service_CFG, "Preferred region code set to {}", preferred_region_code);
 
-    if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) {
+    if (Settings::values.region_value.GetValue() == Settings::REGION_VALUE_AUTO_SELECT) {
         if (current_language != adjusted_language) {
             LOG_WARNING(Service_CFG, "System language {} does not fit the region. Adjusted to {}",
                         current_language, adjusted_language);
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index af07e0866..8670788e3 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -9,6 +9,7 @@
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "common/scope_exit.h"
+#include "common/settings.h"
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/file_sys/errors.h"
@@ -25,7 +26,6 @@
 #include "core/hle/service/am/am.h"
 #include "core/hle/service/fs/archive.h"
 #include "core/hle/service/fs/fs_user.h"
-#include "core/settings.h"
 
 SERVICE_CONSTRUCT_IMPL(Service::FS::FS_USER)
 SERIALIZE_EXPORT_IMPL(Service::FS::FS_USER)
@@ -350,7 +350,7 @@ void FS_USER::IsSdmcDetected(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x817, 0, 0);
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     rb.Push(RESULT_SUCCESS);
-    rb.Push(Settings::values.use_virtual_sd);
+    rb.Push(Settings::values.use_virtual_sd.GetValue());
 }
 
 void FS_USER::IsSdmcWriteable(Kernel::HLERequestContext& ctx) {
@@ -358,7 +358,7 @@ void FS_USER::IsSdmcWriteable(Kernel::HLERequestContext& ctx) {
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     rb.Push(RESULT_SUCCESS);
     // If the SD isn't enabled, it can't be writeable...else, stubbed true
-    rb.Push(Settings::values.use_virtual_sd);
+    rb.Push(Settings::values.use_virtual_sd.GetValue());
     LOG_DEBUG(Service_FS, " (STUBBED)");
 }
 
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index aba6c8291..3fb9d22af 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -218,8 +218,9 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) {
 
     // TODO(xperia64): How the 3D Slider is updated by the HID module needs to be RE'd
     // and possibly moved to its own Core::Timing event.
-    mem->pad.sliderstate_3d = (Settings::values.factor_3d / 100.0f);
-    system.Kernel().GetSharedPageHandler().Set3DSlider(Settings::values.factor_3d / 100.0f);
+    mem->pad.sliderstate_3d = (Settings::values.factor_3d.GetValue() / 100.0f);
+    system.Kernel().GetSharedPageHandler().Set3DSlider(Settings::values.factor_3d.GetValue() /
+                                                       100.0f);
 
     // Reschedule recurrent event
     system.CoreTiming().ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
@@ -406,7 +407,7 @@ void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext&
 void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp{ctx, 0x17, 0, 0};
 
-    const u8 volume = static_cast<u8>(0x3F * Settings::values.volume);
+    const u8 volume = static_cast<u8>(0x3F * Settings::values.volume.GetValue());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index c0f74ced1..609cb9276 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -13,10 +13,10 @@
 #include "common/bit_field.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
+#include "common/settings.h"
 #include "core/core_timing.h"
 #include "core/frontend/input.h"
 #include "core/hle/service/service.h"
-#include "core/settings.h"
 
 namespace Core {
 class System;
diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp
index 777615d7f..5e7511033 100644
--- a/src/core/hle/service/ir/extra_hid.cpp
+++ b/src/core/hle/service/ir/extra_hid.cpp
@@ -3,12 +3,12 @@
 // Refer to the license.txt file included.
 
 #include "common/alignment.h"
+#include "common/settings.h"
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/hle/service/ir/extra_hid.h"
 #include "core/movie.h"
-#include "core/settings.h"
 
 namespace Service::IR {
 
diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp
index 7372bec5e..d5e475468 100644
--- a/src/core/hle/service/ir/ir_rst.cpp
+++ b/src/core/hle/service/ir/ir_rst.cpp
@@ -5,6 +5,7 @@
 #include <boost/serialization/base_object.hpp>
 #include <boost/serialization/shared_ptr.hpp>
 #include "common/archives.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/hle/ipc_helpers.h"
@@ -13,7 +14,6 @@
 #include "core/hle/service/hid/hid.h"
 #include "core/hle/service/ir/ir_rst.h"
 #include "core/movie.h"
-#include "core/settings.h"
 
 SERIALIZE_EXPORT_IMPL(Service::IR::IR_RST)
 SERVICE_CONSTRUCT_IMPL(Service::IR::IR_RST)
diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp
index d95bcc890..f2e41519c 100644
--- a/src/core/hle/service/mic_u.cpp
+++ b/src/core/hle/service/mic_u.cpp
@@ -5,6 +5,7 @@
 #include <boost/serialization/weak_ptr.hpp>
 #include "common/archives.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/frontend/mic.h"
 #include "core/hle/ipc.h"
@@ -14,7 +15,6 @@
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/shared_memory.h"
 #include "core/hle/service/mic_u.h"
-#include "core/settings.h"
 
 SERVICE_CONSTRUCT_IMPL(Service::MIC::MIC_U)
 SERIALIZE_EXPORT_IMPL(Service::MIC::MIC_U)
@@ -350,12 +350,12 @@ struct MIC_U::Impl {
 
     void CreateMic() {
         std::unique_ptr<Frontend::Mic::Interface> new_mic;
-        switch (Settings::values.mic_input_type) {
+        switch (Settings::values.mic_input_type.GetValue()) {
         case Settings::MicInputType::None:
             new_mic = std::make_unique<Frontend::Mic::NullMic>();
             break;
         case Settings::MicInputType::Real:
-            new_mic = Frontend::Mic::CreateRealMic(Settings::values.mic_input_device);
+            new_mic = Frontend::Mic::CreateRealMic(Settings::values.mic_input_device.GetValue());
             break;
         case Settings::MicInputType::Static:
             new_mic = std::make_unique<Frontend::Mic::StaticMic>();
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
index 9c4c09fad..8278b0c55 100644
--- a/src/core/hle/service/ptm/ptm.cpp
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -7,6 +7,7 @@
 #include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/file_sys/archive_extsavedata.h"
 #include "core/file_sys/errors.h"
@@ -17,7 +18,6 @@
 #include "core/hle/service/ptm/ptm_sets.h"
 #include "core/hle/service/ptm/ptm_sysm.h"
 #include "core/hle/service/ptm/ptm_u.h"
-#include "core/settings.h"
 
 SERIALIZE_EXPORT_IMPL(Service::PTM::Module)
 
@@ -118,7 +118,7 @@ void Module::Interface::GetSoftwareClosedFlag(Kernel::HLERequestContext& ctx) {
 }
 
 void CheckNew3DS(IPC::RequestBuilder& rb) {
-    const bool is_new_3ds = Settings::values.is_new_3ds;
+    const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
 
     rb.Push(RESULT_SUCCESS);
     rb.Push(is_new_3ds);
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 91f8d154b..8e98ee99c 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -12,6 +12,7 @@
 #include "common/atomic_ops.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
+#include "common/settings.h"
 #include "common/swap.h"
 #include "core/arm/arm_interface.h"
 #include "core/core.h"
@@ -20,7 +21,6 @@
 #include "core/hle/kernel/process.h"
 #include "core/hle/lock.h"
 #include "core/memory.h"
-#include "core/settings.h"
 #include "video_core/renderer_base.h"
 #include "video_core/video_core.h"
 
@@ -289,7 +289,7 @@ private:
     friend class boost::serialization::access;
     template <class Archive>
     void serialize(Archive& ar, const unsigned int file_version) {
-        bool save_n3ds_ram = Settings::values.is_new_3ds;
+        bool save_n3ds_ram = Settings::values.is_new_3ds.GetValue();
         ar& save_n3ds_ram;
         ar& boost::serialization::make_binary_object(vram.get(), Memory::VRAM_SIZE);
         ar& boost::serialization::make_binary_object(
diff --git a/src/core/movie.cpp b/src/core/movie.cpp
index cf74cc4ef..2348fe827 100644
--- a/src/core/movie.cpp
+++ b/src/core/movie.cpp
@@ -601,8 +601,8 @@ void Movie::PrepareForPlayback(const std::string& movie_file) {
 }
 
 void Movie::PrepareForRecording() {
-    if (Settings::values.init_clock == Settings::InitClock::SystemTime) {
-        long long init_time_offset = Settings::values.init_time_offset;
+    if (Settings::values.init_clock.GetValue() == Settings::InitClock::SystemTime) {
+        long long init_time_offset = Settings::values.init_time_offset.GetValue();
         long long days_offset = init_time_offset / 86400;
         unsigned long long seconds_offset =
             std::abs(init_time_offset) - std::abs(days_offset * 86400);
@@ -610,7 +610,7 @@ void Movie::PrepareForRecording() {
         init_time =
             Common::Timer::GetTimeSinceJan1970().count() + seconds_offset + (days_offset * 86400);
     } else {
-        init_time = Settings::values.init_time;
+        init_time = Settings::values.init_time.GetValue();
     }
 }
 
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 5b426c83b..6dffec97f 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -12,9 +12,9 @@
 #include <fmt/chrono.h>
 #include <fmt/format.h>
 #include "common/file_util.h"
+#include "common/settings.h"
 #include "core/hw/gpu.h"
 #include "core/perf_stats.h"
-#include "core/settings.h"
 
 using namespace std::chrono_literals;
 using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>;
@@ -136,14 +136,9 @@ void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
     }
 
     auto now = Clock::now();
-    double sleep_scale = Settings::values.frame_limit / 100.0;
+    double sleep_scale = Settings::values.frame_limit.GetValue() / 100.0;
 
-    if (Settings::values.use_frame_limit_alternate) {
-        if (Settings::values.frame_limit_alternate == 0) {
-            return;
-        }
-        sleep_scale = Settings::values.frame_limit_alternate / 100.0;
-    } else if (Settings::values.frame_limit == 0) {
+    if (Settings::values.frame_limit.GetValue() == 0) {
         return;
     }
 
diff --git a/src/core/settings.h b/src/core/settings.h
deleted file mode 100644
index accb39071..000000000
--- a/src/core/settings.h
+++ /dev/null
@@ -1,268 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <array>
-#include <atomic>
-#include <string>
-#include <unordered_map>
-#include <vector>
-#include "common/common_types.h"
-#include "core/hle/service/cam/cam_params.h"
-
-namespace Settings {
-
-enum class InitClock {
-    SystemTime = 0,
-    FixedTime = 1,
-};
-
-enum class LayoutOption {
-    Default,
-    SingleScreen,
-    LargeScreen,
-    SideScreen,
-#ifndef ANDROID
-    SeparateWindows,
-#endif
-    // Similiar to default, but better for mobile devices in portrait mode. Top screen in clamped to
-    // the top of the frame, and the bottom screen is enlarged to match the top screen.
-    MobilePortrait,
-
-    // Similiar to LargeScreen, but better for mobile devices in landscape mode. The screens are
-    // clamped to the top of the frame, and the bottom screen is a bit bigger.
-    MobileLandscape,
-};
-
-enum class MicInputType {
-    None,
-    Real,
-    Static,
-};
-
-enum class StereoRenderOption {
-    Off,
-    SideBySide,
-    Anaglyph,
-    Interlaced,
-    ReverseInterlaced,
-    CardboardVR
-};
-
-namespace NativeButton {
-enum Values {
-    A,
-    B,
-    X,
-    Y,
-    Up,
-    Down,
-    Left,
-    Right,
-    L,
-    R,
-    Start,
-    Select,
-    Debug,
-    Gpio14,
-
-    ZL,
-    ZR,
-
-    Home,
-
-    NumButtons,
-};
-
-constexpr int BUTTON_HID_BEGIN = A;
-constexpr int BUTTON_IR_BEGIN = ZL;
-constexpr int BUTTON_NS_BEGIN = Home;
-
-constexpr int BUTTON_HID_END = BUTTON_IR_BEGIN;
-constexpr int BUTTON_IR_END = BUTTON_NS_BEGIN;
-constexpr int BUTTON_NS_END = NumButtons;
-
-constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
-constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN;
-constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
-
-static const std::array<const char*, NumButtons> mapping = {{
-    "button_a",
-    "button_b",
-    "button_x",
-    "button_y",
-    "button_up",
-    "button_down",
-    "button_left",
-    "button_right",
-    "button_l",
-    "button_r",
-    "button_start",
-    "button_select",
-    "button_debug",
-    "button_gpio14",
-    "button_zl",
-    "button_zr",
-    "button_home",
-}};
-} // namespace NativeButton
-
-namespace NativeAnalog {
-enum Values {
-    CirclePad,
-    CStick,
-
-    NumAnalogs,
-};
-
-static const std::array<const char*, NumAnalogs> mapping = {{
-    "circle_pad",
-    "c_stick",
-}};
-} // namespace NativeAnalog
-
-struct InputProfile {
-    std::string name;
-    std::array<std::string, NativeButton::NumButtons> buttons;
-    std::array<std::string, NativeAnalog::NumAnalogs> analogs;
-    std::string motion_device;
-    std::string touch_device;
-    bool use_touch_from_button;
-    int touch_from_button_map_index;
-    std::string udp_input_address;
-    u16 udp_input_port;
-    u8 udp_pad_index;
-};
-
-struct TouchFromButtonMap {
-    std::string name;
-    std::vector<std::string> buttons;
-};
-
-struct Values {
-    // CheckNew3DS
-    bool is_new_3ds;
-
-    // Controls
-    InputProfile current_input_profile;       ///< The current input profile
-    int current_input_profile_index;          ///< The current input profile index
-    std::vector<InputProfile> input_profiles; ///< The list of input profiles
-    std::vector<TouchFromButtonMap> touch_from_button_maps;
-
-    // Core
-    bool use_cpu_jit;
-    int cpu_clock_percentage;
-
-    // Data Storage
-    bool use_virtual_sd;
-    bool use_custom_storage;
-
-    // System
-    int region_value;
-    InitClock init_clock;
-    u64 init_time;
-    s64 init_time_offset;
-
-    // Renderer
-    bool use_gles;
-    bool use_hw_renderer;
-    bool use_hw_shader;
-    bool separable_shader;
-    bool use_disk_shader_cache;
-    bool shaders_accurate_mul;
-    bool use_shader_jit;
-    u16 resolution_factor;
-    bool use_frame_limit_alternate;
-    u16 frame_limit;
-    u16 frame_limit_alternate;
-    std::string texture_filter_name;
-
-    LayoutOption layout_option;
-    bool swap_screen;
-    bool upright_screen;
-    bool custom_layout;
-    u16 custom_top_left;
-    u16 custom_top_top;
-    u16 custom_top_right;
-    u16 custom_top_bottom;
-    u16 custom_bottom_left;
-    u16 custom_bottom_top;
-    u16 custom_bottom_right;
-    u16 custom_bottom_bottom;
-
-    float bg_red;
-    float bg_green;
-    float bg_blue;
-
-    StereoRenderOption render_3d;
-    std::atomic<u8> factor_3d;
-
-    bool mono_render_left_eye;
-
-    int cardboard_screen_size;
-    int cardboard_x_shift;
-    int cardboard_y_shift;
-
-    bool filter_mode;
-    std::string pp_shader_name;
-
-    bool dump_textures;
-    bool custom_textures;
-    bool preload_textures;
-
-    bool use_vsync_new;
-
-    // Audio
-    bool audio_muted;
-    bool enable_dsp_lle;
-    bool enable_dsp_lle_multithread;
-    std::string sink_id;
-    bool enable_audio_stretching;
-    std::string audio_device_id;
-    float volume;
-    MicInputType mic_input_type;
-    std::string mic_input_device;
-
-    // Camera
-    std::array<std::string, Service::CAM::NumCameras> camera_name;
-    std::array<std::string, Service::CAM::NumCameras> camera_config;
-    std::array<int, Service::CAM::NumCameras> camera_flip;
-
-    // Debugging
-    bool record_frame_times;
-    bool use_gdbstub;
-    u16 gdbstub_port;
-    std::string log_filter;
-    std::unordered_map<std::string, bool> lle_modules;
-
-    // Video Dumping
-    std::string output_format;
-    std::string format_options;
-
-    std::string video_encoder;
-    std::string video_encoder_options;
-    u64 video_bitrate;
-
-    std::string audio_encoder;
-    std::string audio_encoder_options;
-    u64 audio_bitrate;
-} extern values;
-
-float Volume();
-
-// a special value for Values::region_value indicating that citra will automatically select a region
-// value to fit the region lockout info of the game
-static constexpr int REGION_VALUE_AUTO_SELECT = -1;
-
-void Apply();
-void LogSettings();
-
-// Input profiles
-void LoadProfile(int index);
-void SaveProfile(int index);
-void CreateProfile(std::string name);
-void DeleteProfile(int index);
-void RenameCurrentProfile(std::string new_name);
-} // namespace Settings
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index bd84f50d7..1092777b1 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -9,8 +9,8 @@
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "common/scm_rev.h"
+#include "common/settings.h"
 #include "core/core.h"
-#include "core/settings.h"
 #include "core/telemetry_session.h"
 #include "network/network_settings.h"
 
@@ -124,35 +124,37 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
     Telemetry::AppendOSInfo(field_collection);
 
     // Log user configuration information
-    AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id);
+    AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id.GetValue());
     AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching",
-             Settings::values.enable_audio_stretching);
-    AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
+             Settings::values.enable_audio_stretching.GetValue());
+    AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit",
+             Settings::values.use_cpu_jit.GetValue());
     AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
-             Settings::values.resolution_factor);
-    AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit);
-    AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimitAlternate",
-             Settings::values.use_frame_limit_alternate);
-    AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimitAlternate",
-             Settings::values.frame_limit_alternate);
+             Settings::values.resolution_factor.GetValue());
+    AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit",
+             Settings::values.frame_limit.GetValue());
     AddField(Telemetry::FieldType::UserConfig, "Renderer_UseHwRenderer",
-             Settings::values.use_hw_renderer);
+             Settings::values.use_hw_renderer.GetValue());
     AddField(Telemetry::FieldType::UserConfig, "Renderer_UseHwShader",
-             Settings::values.use_hw_shader);
+             Settings::values.use_hw_shader.GetValue());
     AddField(Telemetry::FieldType::UserConfig, "Renderer_ShadersAccurateMul",
-             Settings::values.shaders_accurate_mul);
+             Settings::values.shaders_accurate_mul.GetValue());
     AddField(Telemetry::FieldType::UserConfig, "Renderer_UseShaderJit",
-             Settings::values.use_shader_jit);
-    AddField(Telemetry::FieldType::UserConfig, "Renderer_UseVsync", Settings::values.use_vsync_new);
-    AddField(Telemetry::FieldType::UserConfig, "Renderer_FilterMode", Settings::values.filter_mode);
+             Settings::values.use_shader_jit.GetValue());
+    AddField(Telemetry::FieldType::UserConfig, "Renderer_UseVsync",
+             Settings::values.use_vsync_new.GetValue());
+    AddField(Telemetry::FieldType::UserConfig, "Renderer_FilterMode",
+             Settings::values.filter_mode.GetValue());
     AddField(Telemetry::FieldType::UserConfig, "Renderer_Render3d",
-             static_cast<int>(Settings::values.render_3d));
+             static_cast<int>(Settings::values.render_3d.GetValue()));
     AddField(Telemetry::FieldType::UserConfig, "Renderer_Factor3d",
-             Settings::values.factor_3d.load());
+             Settings::values.factor_3d.GetValue());
     AddField(Telemetry::FieldType::UserConfig, "Renderer_MonoRenderLeftEye",
-             Settings::values.mono_render_left_eye);
-    AddField(Telemetry::FieldType::UserConfig, "System_IsNew3ds", Settings::values.is_new_3ds);
-    AddField(Telemetry::FieldType::UserConfig, "System_RegionValue", Settings::values.region_value);
+             Settings::values.mono_render_left_eye.GetValue());
+    AddField(Telemetry::FieldType::UserConfig, "System_IsNew3ds",
+             Settings::values.is_new_3ds.GetValue());
+    AddField(Telemetry::FieldType::UserConfig, "System_RegionValue",
+             Settings::values.region_value.GetValue());
 }
 
 bool TelemetrySession::SubmitTestcase() {
diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h
index 113e200f9..297749c9f 100644
--- a/src/input_common/gcadapter/gc_poller.h
+++ b/src/input_common/gcadapter/gc_poller.h
@@ -5,8 +5,8 @@
 #pragma once
 
 #include <memory>
+#include "common/settings.h"
 #include "core/frontend/input.h"
-#include "core/settings.h"
 #include "input_common/gcadapter/gc_adapter.h"
 #include "input_common/main.h"
 
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
index 0be659cd0..bb6671951 100644
--- a/src/input_common/sdl/sdl_impl.h
+++ b/src/input_common/sdl/sdl_impl.h
@@ -7,8 +7,8 @@
 #include <atomic>
 #include <memory>
 #include <thread>
+#include "common/settings.h"
 #include "common/threadsafe_queue.h"
-#include "core/settings.h"
 #include "input_common/sdl/sdl.h"
 
 union SDL_Event;
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
index b213a57c1..9cd6c0204 100644
--- a/src/input_common/touch_from_button.cpp
+++ b/src/input_common/touch_from_button.cpp
@@ -2,8 +2,8 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/settings.h"
 #include "core/3ds.h"
-#include "core/settings.h"
 #include "input_common/touch_from_button.h"
 
 namespace InputCommon {
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index c1d417abb..89e114fee 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -6,8 +6,8 @@
 #include <optional>
 #include <tuple>
 #include "common/param_package.h"
+#include "common/settings.h"
 #include "core/frontend/input.h"
-#include "core/settings.h"
 #include "input_common/udp/client.h"
 #include "input_common/udp/udp.h"
 
diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.cpp b/src/video_core/rasterizer_cache/rasterizer_cache.cpp
index 5fec2bae6..394bcca00 100644
--- a/src/video_core/rasterizer_cache/rasterizer_cache.cpp
+++ b/src/video_core/rasterizer_cache/rasterizer_cache.cpp
@@ -241,8 +241,8 @@ static Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams&
 
 RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
     resolution_scale_factor = VideoCore::GetResolutionScaleFactor();
-    texture_filterer = std::make_unique<TextureFilterer>(Settings::values.texture_filter_name,
-                                                         resolution_scale_factor);
+    texture_filterer = std::make_unique<TextureFilterer>(
+        Settings::values.texture_filter_name.GetValue(), resolution_scale_factor);
     format_reinterpreter = std::make_unique<FormatReinterpreterOpenGL>();
     texture_downloader_es = std::make_unique<TextureDownloaderES>(false);
 }
@@ -592,7 +592,7 @@ SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(
         resolution_scale_factor != VideoCore::GetResolutionScaleFactor();
     const bool texture_filter_changed =
         VideoCore::g_texture_filter_update_requested.exchange(false) &&
-        texture_filterer->Reset(Settings::values.texture_filter_name,
+        texture_filterer->Reset(Settings::values.texture_filter_name.GetValue(),
                                 VideoCore::GetResolutionScaleFactor());
 
     if (resolution_scale_changed || texture_filter_changed) {
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 f2b0757d3..1fd2950a4 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -11,10 +11,10 @@
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "common/scm_rev.h"
+#include "common/settings.h"
 #include "common/zstd_compression.h"
 #include "core/core.h"
 #include "core/hle/kernel/process.h"
-#include "core/settings.h"
 #include "video_core/renderer_opengl/gl_shader_disk_cache.h"
 
 namespace OpenGL {
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index a10b970b5..1e1126345 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -5,6 +5,7 @@
 #include <queue>
 #include "common/logging/log.h"
 #include "common/microprofile.h"
+#include "common/settings.h"
 #include "core/core.h"
 #include "core/dumping/backend.h"
 #include "core/frontend/emu_window.h"
@@ -12,7 +13,6 @@
 #include "core/hw/hw.h"
 #include "core/hw/lcd.h"
 #include "core/memory.h"
-#include "core/settings.h"
 #include "core/tracer/recorder.h"
 #include "video_core/debug_utils/debug_utils.h"
 #include "video_core/rasterizer_interface.h"
@@ -381,7 +381,7 @@ void RendererOpenGL::SwapBuffers() {
     RenderToMailbox(main_layout, render_window.mailbox, false);
 
 #ifndef ANDROID
-    if (Settings::values.layout_option == Settings::LayoutOption::SeparateWindows) {
+    if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows) {
         ASSERT(secondary_window);
         const auto& secondary_layout = secondary_window->GetFramebufferLayout();
         RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
@@ -618,8 +618,8 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
  * Initializes the OpenGL state and creates persistent objects.
  */
 void RendererOpenGL::InitOpenGLObjects() {
-    glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
-                 0.0f);
+    glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
+                 Settings::values.bg_blue.GetValue(), 0.0f);
 
     filter_sampler.Create();
     ReloadSampler();
@@ -685,12 +685,13 @@ void RendererOpenGL::ReloadShader() {
     if (GLES) {
         shader_data += fragment_shader_precision_OES;
     }
-    if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph) {
-        if (Settings::values.pp_shader_name == "dubois (builtin)") {
+
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph) {
+        if (Settings::values.pp_shader_name.GetValue() == "dubois (builtin)") {
             shader_data += fragment_shader_anaglyph;
         } else {
-            std::string shader_text =
-                OpenGL::GetPostProcessingShaderCode(true, Settings::values.pp_shader_name);
+            std::string shader_text = OpenGL::GetPostProcessingShaderCode(
+                true, Settings::values.pp_shader_name.GetValue());
             if (shader_text.empty()) {
                 // Should probably provide some information that the shader couldn't load
                 shader_data += fragment_shader_anaglyph;
@@ -698,13 +699,14 @@ void RendererOpenGL::ReloadShader() {
                 shader_data += shader_text;
             }
         }
-    } else if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced ||
-               Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
-        if (Settings::values.pp_shader_name == "horizontal (builtin)") {
+    } else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
+               Settings::values.render_3d.GetValue() ==
+                   Settings::StereoRenderOption::ReverseInterlaced) {
+        if (Settings::values.pp_shader_name.GetValue() == "horizontal (builtin)") {
             shader_data += fragment_shader_interlaced;
         } else {
-            std::string shader_text =
-                OpenGL::GetPostProcessingShaderCode(false, Settings::values.pp_shader_name);
+            std::string shader_text = OpenGL::GetPostProcessingShaderCode(
+                false, Settings::values.pp_shader_name.GetValue());
             if (shader_text.empty()) {
                 // Should probably provide some information that the shader couldn't load
                 shader_data += fragment_shader_interlaced;
@@ -713,11 +715,11 @@ void RendererOpenGL::ReloadShader() {
             }
         }
     } else {
-        if (Settings::values.pp_shader_name == "none (builtin)") {
+        if (Settings::values.pp_shader_name.GetValue() == "none (builtin)") {
             shader_data += fragment_shader;
         } else {
-            std::string shader_text =
-                OpenGL::GetPostProcessingShaderCode(false, Settings::values.pp_shader_name);
+            std::string shader_text = OpenGL::GetPostProcessingShaderCode(
+                false, Settings::values.pp_shader_name.GetValue());
             if (shader_text.empty()) {
                 // Should probably provide some information that the shader couldn't load
                 shader_data += fragment_shader;
@@ -731,16 +733,17 @@ void RendererOpenGL::ReloadShader() {
     state.Apply();
     uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix");
     uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture");
-    if (Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph ||
-        Settings::values.render_3d == Settings::StereoRenderOption::Interlaced ||
-        Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph ||
+        Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
+        Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::ReverseInterlaced) {
         uniform_color_texture_r = glGetUniformLocation(shader.handle, "color_texture_r");
     }
-    if (Settings::values.render_3d == Settings::StereoRenderOption::Interlaced ||
-        Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
+    if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
+        Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::ReverseInterlaced) {
         GLuint uniform_reverse_interlaced =
             glGetUniformLocation(shader.handle, "reverse_interlaced");
-        if (Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced)
+        if (Settings::values.render_3d.GetValue() ==
+            Settings::StereoRenderOption::ReverseInterlaced)
             glUniform1i(uniform_reverse_interlaced, 1);
         else
             glUniform1i(uniform_reverse_interlaced, 0);
@@ -958,8 +961,8 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
 void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool flipped) {
     if (VideoCore::g_renderer_bg_color_update_requested.exchange(false)) {
         // Update background color before drawing
-        glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue,
-                     0.0f);
+        glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
+                     Settings::values.bg_blue.GetValue(), 0.0f);
     }
 
     if (VideoCore::g_renderer_sampler_update_requested.exchange(false)) {
@@ -989,9 +992,9 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
     glUniform1i(uniform_color_texture, 0);
 
     const bool stereo_single_screen =
-        Settings::values.render_3d == Settings::StereoRenderOption::Anaglyph ||
-        Settings::values.render_3d == Settings::StereoRenderOption::Interlaced ||
-        Settings::values.render_3d == Settings::StereoRenderOption::ReverseInterlaced;
+        Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph ||
+        Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
+        Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::ReverseInterlaced;
 
     // Bind a second texture for the right eye if in Anaglyph mode
     if (stereo_single_screen) {
@@ -1001,12 +1004,13 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
     glUniform1i(uniform_layer, 0);
     if (layout.top_screen_enabled) {
         if (layout.is_rotated) {
-            if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
+            if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) {
                 int eye = Settings::values.mono_render_left_eye ? 0 : 1;
                 DrawSingleScreenRotated(screen_infos[eye], (float)top_screen.left,
                                         (float)top_screen.top, (float)top_screen.GetWidth(),
                                         (float)top_screen.GetHeight());
-            } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
+            } else if (Settings::values.render_3d.GetValue() ==
+                       Settings::StereoRenderOption::SideBySide) {
                 DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left / 2,
                                         (float)top_screen.top, (float)top_screen.GetWidth() / 2,
                                         (float)top_screen.GetHeight());
@@ -1015,7 +1019,8 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
                                         ((float)top_screen.left / 2) + ((float)layout.width / 2),
                                         (float)top_screen.top, (float)top_screen.GetWidth() / 2,
                                         (float)top_screen.GetHeight());
-            } else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
+            } else if (Settings::values.render_3d.GetValue() ==
+                       Settings::StereoRenderOption::CardboardVR) {
                 DrawSingleScreenRotated(screen_infos[0], layout.top_screen.left,
                                         layout.top_screen.top, layout.top_screen.GetWidth(),
                                         layout.top_screen.GetHeight());
@@ -1031,11 +1036,12 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
                     (float)top_screen.GetWidth(), (float)top_screen.GetHeight());
             }
         } else {
-            if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
-                int eye = Settings::values.mono_render_left_eye ? 0 : 1;
+            if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) {
+                int eye = Settings::values.mono_render_left_eye.GetValue() ? 0 : 1;
                 DrawSingleScreen(screen_infos[eye], (float)top_screen.left, (float)top_screen.top,
                                  (float)top_screen.GetWidth(), (float)top_screen.GetHeight());
-            } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
+            } else if (Settings::values.render_3d.GetValue() ==
+                       Settings::StereoRenderOption::SideBySide) {
                 DrawSingleScreen(screen_infos[0], (float)top_screen.left / 2, (float)top_screen.top,
                                  (float)top_screen.GetWidth() / 2, (float)top_screen.GetHeight());
                 glUniform1i(uniform_layer, 1);
@@ -1043,7 +1049,8 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
                                  ((float)top_screen.left / 2) + ((float)layout.width / 2),
                                  (float)top_screen.top, (float)top_screen.GetWidth() / 2,
                                  (float)top_screen.GetHeight());
-            } else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
+            } else if (Settings::values.render_3d.GetValue() ==
+                       Settings::StereoRenderOption::CardboardVR) {
                 DrawSingleScreen(screen_infos[0], layout.top_screen.left, layout.top_screen.top,
                                  layout.top_screen.GetWidth(), layout.top_screen.GetHeight());
                 glUniform1i(uniform_layer, 1);
@@ -1061,11 +1068,12 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
     glUniform1i(uniform_layer, 0);
     if (layout.bottom_screen_enabled) {
         if (layout.is_rotated) {
-            if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
+            if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) {
                 DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left,
                                         (float)bottom_screen.top, (float)bottom_screen.GetWidth(),
                                         (float)bottom_screen.GetHeight());
-            } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
+            } else if (Settings::values.render_3d.GetValue() ==
+                       Settings::StereoRenderOption::SideBySide) {
                 DrawSingleScreenRotated(
                     screen_infos[2], (float)bottom_screen.left / 2, (float)bottom_screen.top,
                     (float)bottom_screen.GetWidth() / 2, (float)bottom_screen.GetHeight());
@@ -1074,7 +1082,8 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
                     screen_infos[2], ((float)bottom_screen.left / 2) + ((float)layout.width / 2),
                     (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
                     (float)bottom_screen.GetHeight());
-            } else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
+            } else if (Settings::values.render_3d.GetValue() ==
+                       Settings::StereoRenderOption::CardboardVR) {
                 DrawSingleScreenRotated(screen_infos[2], layout.bottom_screen.left,
                                         layout.bottom_screen.top, layout.bottom_screen.GetWidth(),
                                         layout.bottom_screen.GetHeight());
@@ -1091,11 +1100,12 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
                                               (float)bottom_screen.GetHeight());
             }
         } else {
-            if (Settings::values.render_3d == Settings::StereoRenderOption::Off) {
+            if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) {
                 DrawSingleScreen(screen_infos[2], (float)bottom_screen.left,
                                  (float)bottom_screen.top, (float)bottom_screen.GetWidth(),
                                  (float)bottom_screen.GetHeight());
-            } else if (Settings::values.render_3d == Settings::StereoRenderOption::SideBySide) {
+            } else if (Settings::values.render_3d.GetValue() ==
+                       Settings::StereoRenderOption::SideBySide) {
                 DrawSingleScreen(screen_infos[2], (float)bottom_screen.left / 2,
                                  (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
                                  (float)bottom_screen.GetHeight());
@@ -1104,7 +1114,8 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
                                  ((float)bottom_screen.left / 2) + ((float)layout.width / 2),
                                  (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2,
                                  (float)bottom_screen.GetHeight());
-            } else if (Settings::values.render_3d == Settings::StereoRenderOption::CardboardVR) {
+            } else if (Settings::values.render_3d.GetValue() ==
+                       Settings::StereoRenderOption::CardboardVR) {
                 DrawSingleScreen(screen_infos[2], layout.bottom_screen.left,
                                  layout.bottom_screen.top, layout.bottom_screen.GetWidth(),
                                  layout.bottom_screen.GetHeight());
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 53388f020..fdf7bcc3e 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -5,7 +5,7 @@
 #include <memory>
 #include "common/archives.h"
 #include "common/logging/log.h"
-#include "core/settings.h"
+#include "common/settings.h"
 #include "video_core/pica.h"
 #include "video_core/pica_state.h"
 #include "video_core/renderer_base.h"
@@ -44,7 +44,7 @@ ResultStatus Init(Frontend::EmuWindow& emu_window, Frontend::EmuWindow* secondar
     g_memory = &memory;
     Pica::Init();
 
-    OpenGL::GLES = Settings::values.use_gles;
+    OpenGL::GLES = Settings::values.use_gles.GetValue();
 
     g_renderer = std::make_unique<OpenGL::RendererOpenGL>(emu_window, secondary_window);
     ResultStatus result = g_renderer->Init();
@@ -82,8 +82,8 @@ void RequestScreenshot(void* data, std::function<void()> callback,
 
 u16 GetResolutionScaleFactor() {
     if (g_hw_renderer_enabled) {
-        return Settings::values.resolution_factor
-                   ? Settings::values.resolution_factor
+        return Settings::values.resolution_factor.GetValue()
+                   ? Settings::values.resolution_factor.GetValue()
                    : g_renderer->GetRenderWindow().GetFramebufferLayout().GetScalingRatio();
     } else {
         // Software renderer always render at native resolution