diff --git a/.gitmodules b/.gitmodules
index b72a2ec8c8..45dd0d259d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,9 +4,6 @@
 [submodule "enet"]
 	path = externals/enet
 	url = https://github.com/lsalzman/enet.git
-[submodule "inih"]
-	path = externals/inih/inih
-	url = https://github.com/benhoyt/inih.git
 [submodule "cubeb"]
 	path = externals/cubeb
 	url = https://github.com/mozilla/cubeb.git
@@ -61,3 +58,6 @@
 [submodule "breakpad"]
 	path = externals/breakpad
 	url = https://github.com/yuzu-emu/breakpad.git
+[submodule "simpleini"]
+	path = externals/simpleini
+	url = https://github.com/brofield/simpleini.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9c35e09461..e5cac8fe94 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -285,7 +285,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
 find_package(Boost 1.79.0 REQUIRED context)
 find_package(enet 1.3 MODULE)
 find_package(fmt 9 REQUIRED)
-find_package(inih 52 MODULE COMPONENTS INIReader)
 find_package(LLVM 17.0.2 MODULE COMPONENTS Demangle)
 find_package(lz4 REQUIRED)
 find_package(nlohmann_json 3.8 REQUIRED)
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index be8b0b5e82..d0d4926bb2 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -295,3 +295,6 @@ if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client)
         target_link_libraries(dump_syms PRIVATE libbreakpad_client ZLIB::ZLIB)
     endif()
 endif()
+
+# SimpleIni
+add_subdirectory(simpleini)
diff --git a/externals/simpleini b/externals/simpleini
new file mode 160000
index 0000000000..382ddbb4b9
--- /dev/null
+++ b/externals/simpleini
@@ -0,0 +1 @@
+Subproject commit 382ddbb4b92c0b26aa1b32cefba2002119a5b1f2
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d2ca4904ab..e04d2418bf 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -187,6 +187,7 @@ add_subdirectory(audio_core)
 add_subdirectory(video_core)
 add_subdirectory(network)
 add_subdirectory(input_common)
+add_subdirectory(frontend_common)
 add_subdirectory(shader_recompiler)
 
 if (YUZU_ROOM)
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index 88a570f686..49ad029aa1 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -6,9 +6,6 @@ add_library(yuzu-android SHARED
     android_common/android_common.h
     applets/software_keyboard.cpp
     applets/software_keyboard.h
-    config.cpp
-    config.h
-    default_ini.h
     emu_window/emu_window.cpp
     emu_window/emu_window.h
     id_cache.cpp
@@ -16,15 +13,17 @@ add_library(yuzu-android SHARED
     native.cpp
     native.h
     native_config.cpp
-    uisettings.cpp
+    android_settings.cpp
     game_metadata.cpp
     native_log.cpp
+    android_config.cpp
+    android_config.h
 )
 
 set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
 
-target_link_libraries(yuzu-android PRIVATE audio_core common core input_common)
 target_link_libraries(yuzu-android PRIVATE android camera2ndk EGL glad inih jnigraphics log)
+target_link_libraries(yuzu-android PRIVATE audio_core common core input_common frontend_common)
 if (ARCHITECTURE_arm64)
     target_link_libraries(yuzu-android PRIVATE adrenotools)
 endif()
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
new file mode 100644
index 0000000000..3041c25c9b
--- /dev/null
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "android_config.h"
+#include "android_settings.h"
+#include "common/settings_setting.h"
+
+AndroidConfig::AndroidConfig(const std::string& config_name, ConfigType config_type)
+    : Config(config_type) {
+    Initialize(config_name);
+    if (config_type != ConfigType::InputProfile) {
+        ReadAndroidValues();
+        SaveAndroidValues();
+    }
+}
+
+AndroidConfig::~AndroidConfig() {
+    if (global) {
+        AndroidConfig::SaveAllValues();
+    }
+}
+
+void AndroidConfig::ReloadAllValues() {
+    Reload();
+    ReadAndroidValues();
+    SaveAndroidValues();
+}
+
+void AndroidConfig::SaveAllValues() {
+    Save();
+    SaveAndroidValues();
+}
+
+void AndroidConfig::ReadAndroidValues() {
+    if (global) {
+        ReadAndroidUIValues();
+    }
+}
+
+void AndroidConfig::ReadAndroidUIValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Android));
+
+    ReadCategory(Settings::Category::Android);
+
+    EndGroup();
+}
+
+void AndroidConfig::SaveAndroidValues() {
+    if (global) {
+        SaveAndroidUIValues();
+    }
+
+    WriteToIni();
+}
+
+void AndroidConfig::SaveAndroidUIValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Android));
+
+    WriteCategory(Settings::Category::Android);
+
+    EndGroup();
+}
+
+std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
+    auto& map = Settings::values.linkage.by_category;
+    if (map.contains(category)) {
+        return Settings::values.linkage.by_category[category];
+    }
+    return AndroidSettings::values.linkage.by_category[category];
+}
diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h
new file mode 100644
index 0000000000..e679392fd0
--- /dev/null
+++ b/src/android/app/src/main/jni/android_config.h
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "frontend_common/config.h"
+
+class AndroidConfig final : public Config {
+public:
+    explicit AndroidConfig(const std::string& config_name = "config",
+                           ConfigType config_type = ConfigType::GlobalConfig);
+    ~AndroidConfig() override;
+
+    void ReloadAllValues() override;
+    void SaveAllValues() override;
+
+protected:
+    void ReadAndroidValues();
+    void ReadAndroidUIValues();
+    void ReadHidbusValues() override {}
+    void ReadDebugControlValues() override {}
+    void ReadPathValues() override {}
+    void ReadShortcutValues() override {}
+    void ReadUIValues() override {}
+    void ReadUIGamelistValues() override {}
+    void ReadUILayoutValues() override {}
+    void ReadMultiplayerValues() override {}
+
+    void SaveAndroidValues();
+    void SaveAndroidUIValues();
+    void SaveHidbusValues() override {}
+    void SaveDebugControlValues() override {}
+    void SavePathValues() override {}
+    void SaveShortcutValues() override {}
+    void SaveUIValues() override {}
+    void SaveUIGamelistValues() override {}
+    void SaveUILayoutValues() override {}
+    void SaveMultiplayerValues() override {}
+
+    std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;
+};
diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/android_settings.cpp
similarity index 85%
rename from src/android/app/src/main/jni/uisettings.cpp
rename to src/android/app/src/main/jni/android_settings.cpp
index f2f0bad50b..16023a6b05 100644
--- a/src/android/app/src/main/jni/uisettings.cpp
+++ b/src/android/app/src/main/jni/android_settings.cpp
@@ -1,7 +1,7 @@
 // SPDX-FileCopyrightText: 2023 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
-#include "uisettings.h"
+#include "android_settings.h"
 
 namespace AndroidSettings {
 
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/android_settings.h
similarity index 100%
rename from src/android/app/src/main/jni/uisettings.h
rename to src/android/app/src/main/jni/android_settings.h
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
deleted file mode 100644
index 81120ab0f1..0000000000
--- a/src/android/app/src/main/jni/config.cpp
+++ /dev/null
@@ -1,330 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <memory>
-#include <optional>
-#include <sstream>
-
-#include <INIReader.h>
-#include "common/fs/file.h"
-#include "common/fs/fs.h"
-#include "common/fs/path_util.h"
-#include "common/logging/log.h"
-#include "common/settings.h"
-#include "common/settings_enums.h"
-#include "core/hle/service/acc/profile_manager.h"
-#include "input_common/main.h"
-#include "jni/config.h"
-#include "jni/default_ini.h"
-#include "uisettings.h"
-
-namespace FS = Common::FS;
-
-Config::Config(const std::string& config_name, ConfigType config_type)
-    : type(config_type), global{config_type == ConfigType::GlobalConfig} {
-    Initialize(config_name);
-}
-
-Config::~Config() = default;
-
-bool Config::LoadINI(const std::string& default_contents, bool retry) {
-    void(FS::CreateParentDir(config_loc));
-    config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
-    const auto config_loc_str = FS::PathToUTF8String(config_loc);
-    if (config->ParseError() < 0) {
-        if (retry) {
-            LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...",
-                        config_loc_str);
-
-            void(FS::CreateParentDir(config_loc));
-            void(FS::WriteStringToFile(config_loc, FS::FileType::TextFile, default_contents));
-
-            config = std::make_unique<INIReader>(config_loc_str);
-
-            return LoadINI(default_contents, false);
-        }
-        LOG_ERROR(Config, "Failed.");
-        return false;
-    }
-    LOG_INFO(Config, "Successfully loaded {}", config_loc_str);
-    return true;
-}
-
-template <>
-void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
-    std::string setting_value = config->Get(group, setting.GetLabel(), setting.GetDefault());
-    if (setting_value.empty()) {
-        setting_value = setting.GetDefault();
-    }
-    setting = std::move(setting_value);
-}
-
-template <>
-void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
-    setting = config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
-}
-
-template <typename Type, bool ranged>
-void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
-    setting = static_cast<Type>(
-        config->GetInteger(group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
-}
-
-void Config::ReadValues() {
-    ReadSetting("ControlsGeneral", Settings::values.mouse_enabled);
-    ReadSetting("ControlsGeneral", Settings::values.touch_device);
-    ReadSetting("ControlsGeneral", Settings::values.keyboard_enabled);
-    ReadSetting("ControlsGeneral", Settings::values.debug_pad_enabled);
-    ReadSetting("ControlsGeneral", Settings::values.vibration_enabled);
-    ReadSetting("ControlsGeneral", Settings::values.enable_accurate_vibrations);
-    ReadSetting("ControlsGeneral", Settings::values.motion_enabled);
-    Settings::values.touchscreen.enabled =
-        config->GetBoolean("ControlsGeneral", "touch_enabled", true);
-    Settings::values.touchscreen.rotation_angle =
-        config->GetInteger("ControlsGeneral", "touch_angle", 0);
-    Settings::values.touchscreen.diameter_x =
-        config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
-    Settings::values.touchscreen.diameter_y =
-        config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
-
-    int num_touch_from_button_maps =
-        config->GetInteger("ControlsGeneral", "touch_from_button_map", 0);
-    if (num_touch_from_button_maps > 0) {
-        for (int i = 0; i < num_touch_from_button_maps; ++i) {
-            Settings::TouchFromButtonMap map;
-            map.name = config->Get("ControlsGeneral",
-                                   std::string("touch_from_button_maps_") + std::to_string(i) +
-                                       std::string("_name"),
-                                   "default");
-            const int num_touch_maps = config->GetInteger(
-                "ControlsGeneral",
-                std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"),
-                0);
-            map.buttons.reserve(num_touch_maps);
-
-            for (int j = 0; j < num_touch_maps; ++j) {
-                std::string touch_mapping =
-                    config->Get("ControlsGeneral",
-                                std::string("touch_from_button_maps_") + std::to_string(i) +
-                                    std::string("_bind_") + std::to_string(j),
-                                "");
-                map.buttons.emplace_back(std::move(touch_mapping));
-            }
-
-            Settings::values.touch_from_button_maps.emplace_back(std::move(map));
-        }
-    } else {
-        Settings::values.touch_from_button_maps.emplace_back(
-            Settings::TouchFromButtonMap{"default", {}});
-        num_touch_from_button_maps = 1;
-    }
-    Settings::values.touch_from_button_map_index = std::clamp(
-        Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
-
-    ReadSetting("ControlsGeneral", Settings::values.udp_input_servers);
-
-    // Data Storage
-    ReadSetting("Data Storage", Settings::values.use_virtual_sd);
-    FS::SetYuzuPath(FS::YuzuPath::NANDDir,
-                    config->Get("Data Storage", "nand_directory",
-                                FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
-    FS::SetYuzuPath(FS::YuzuPath::SDMCDir,
-                    config->Get("Data Storage", "sdmc_directory",
-                                FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
-    FS::SetYuzuPath(FS::YuzuPath::LoadDir,
-                    config->Get("Data Storage", "load_directory",
-                                FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
-    FS::SetYuzuPath(FS::YuzuPath::DumpDir,
-                    config->Get("Data Storage", "dump_directory",
-                                FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
-    ReadSetting("Data Storage", Settings::values.gamecard_inserted);
-    ReadSetting("Data Storage", Settings::values.gamecard_current_game);
-    ReadSetting("Data Storage", Settings::values.gamecard_path);
-
-    // System
-    ReadSetting("System", Settings::values.current_user);
-    Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
-                                                    Service::Account::MAX_USERS - 1);
-
-    // Disable docked mode by default on Android
-    Settings::values.use_docked_mode.SetValue(config->GetBoolean("System", "use_docked_mode", false)
-                                                  ? Settings::ConsoleMode::Docked
-                                                  : Settings::ConsoleMode::Handheld);
-
-    const auto rng_seed_enabled = config->GetBoolean("System", "rng_seed_enabled", false);
-    if (rng_seed_enabled) {
-        Settings::values.rng_seed.SetValue(config->GetInteger("System", "rng_seed", 0));
-    } else {
-        Settings::values.rng_seed.SetValue(0);
-    }
-    Settings::values.rng_seed_enabled.SetValue(rng_seed_enabled);
-
-    const auto custom_rtc_enabled = config->GetBoolean("System", "custom_rtc_enabled", false);
-    if (custom_rtc_enabled) {
-        Settings::values.custom_rtc = config->GetInteger("System", "custom_rtc", 0);
-    } else {
-        Settings::values.custom_rtc = 0;
-    }
-    Settings::values.custom_rtc_enabled = custom_rtc_enabled;
-
-    ReadSetting("System", Settings::values.language_index);
-    ReadSetting("System", Settings::values.region_index);
-    ReadSetting("System", Settings::values.time_zone_index);
-    ReadSetting("System", Settings::values.sound_index);
-
-    // Core
-    ReadSetting("Core", Settings::values.use_multi_core);
-    ReadSetting("Core", Settings::values.memory_layout_mode);
-
-    // Cpu
-    ReadSetting("Cpu", Settings::values.cpu_accuracy);
-    ReadSetting("Cpu", Settings::values.cpu_debug_mode);
-    ReadSetting("Cpu", Settings::values.cpuopt_page_tables);
-    ReadSetting("Cpu", Settings::values.cpuopt_block_linking);
-    ReadSetting("Cpu", Settings::values.cpuopt_return_stack_buffer);
-    ReadSetting("Cpu", Settings::values.cpuopt_fast_dispatcher);
-    ReadSetting("Cpu", Settings::values.cpuopt_context_elimination);
-    ReadSetting("Cpu", Settings::values.cpuopt_const_prop);
-    ReadSetting("Cpu", Settings::values.cpuopt_misc_ir);
-    ReadSetting("Cpu", Settings::values.cpuopt_reduce_misalign_checks);
-    ReadSetting("Cpu", Settings::values.cpuopt_fastmem);
-    ReadSetting("Cpu", Settings::values.cpuopt_fastmem_exclusives);
-    ReadSetting("Cpu", Settings::values.cpuopt_recompile_exclusives);
-    ReadSetting("Cpu", Settings::values.cpuopt_ignore_memory_aborts);
-    ReadSetting("Cpu", Settings::values.cpuopt_unsafe_unfuse_fma);
-    ReadSetting("Cpu", Settings::values.cpuopt_unsafe_reduce_fp_error);
-    ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_standard_fpcr);
-    ReadSetting("Cpu", Settings::values.cpuopt_unsafe_inaccurate_nan);
-    ReadSetting("Cpu", Settings::values.cpuopt_unsafe_fastmem_check);
-    ReadSetting("Cpu", Settings::values.cpuopt_unsafe_ignore_global_monitor);
-
-    // Renderer
-    ReadSetting("Renderer", Settings::values.renderer_backend);
-    ReadSetting("Renderer", Settings::values.renderer_debug);
-    ReadSetting("Renderer", Settings::values.renderer_shader_feedback);
-    ReadSetting("Renderer", Settings::values.enable_nsight_aftermath);
-    ReadSetting("Renderer", Settings::values.disable_shader_loop_safety_checks);
-    ReadSetting("Renderer", Settings::values.vulkan_device);
-
-    ReadSetting("Renderer", Settings::values.resolution_setup);
-    ReadSetting("Renderer", Settings::values.scaling_filter);
-    ReadSetting("Renderer", Settings::values.fsr_sharpening_slider);
-    ReadSetting("Renderer", Settings::values.anti_aliasing);
-    ReadSetting("Renderer", Settings::values.fullscreen_mode);
-    ReadSetting("Renderer", Settings::values.aspect_ratio);
-    ReadSetting("Renderer", Settings::values.max_anisotropy);
-    ReadSetting("Renderer", Settings::values.use_speed_limit);
-    ReadSetting("Renderer", Settings::values.speed_limit);
-    ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
-    ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation);
-    ReadSetting("Renderer", Settings::values.vsync_mode);
-    ReadSetting("Renderer", Settings::values.shader_backend);
-    ReadSetting("Renderer", Settings::values.use_asynchronous_shaders);
-    ReadSetting("Renderer", Settings::values.nvdec_emulation);
-    ReadSetting("Renderer", Settings::values.use_fast_gpu_time);
-    ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache);
-
-    ReadSetting("Renderer", Settings::values.bg_red);
-    ReadSetting("Renderer", Settings::values.bg_green);
-    ReadSetting("Renderer", Settings::values.bg_blue);
-
-    // Use GPU accuracy normal by default on Android
-    Settings::values.gpu_accuracy = static_cast<Settings::GpuAccuracy>(config->GetInteger(
-        "Renderer", "gpu_accuracy", static_cast<u32>(Settings::GpuAccuracy::Normal)));
-
-    // Use GPU default anisotropic filtering on Android
-    Settings::values.max_anisotropy =
-        static_cast<Settings::AnisotropyMode>(config->GetInteger("Renderer", "max_anisotropy", 1));
-
-    // Disable ASTC compute by default on Android
-    Settings::values.accelerate_astc.SetValue(
-        config->GetBoolean("Renderer", "accelerate_astc", false) ? Settings::AstcDecodeMode::Gpu
-                                                                 : Settings::AstcDecodeMode::Cpu);
-
-    // Enable asynchronous presentation by default on Android
-    Settings::values.async_presentation =
-        config->GetBoolean("Renderer", "async_presentation", true);
-
-    // Disable force_max_clock by default on Android
-    Settings::values.renderer_force_max_clock =
-        config->GetBoolean("Renderer", "force_max_clock", false);
-
-    // Disable use_reactive_flushing by default on Android
-    Settings::values.use_reactive_flushing =
-        config->GetBoolean("Renderer", "use_reactive_flushing", false);
-
-    // Audio
-    ReadSetting("Audio", Settings::values.sink_id);
-    ReadSetting("Audio", Settings::values.audio_output_device_id);
-    ReadSetting("Audio", Settings::values.volume);
-
-    // Miscellaneous
-    // log_filter has a different default here than from common
-    Settings::values.log_filter = "*:Info";
-    ReadSetting("Miscellaneous", Settings::values.use_dev_keys);
-
-    // Debugging
-    Settings::values.record_frame_times =
-        config->GetBoolean("Debugging", "record_frame_times", false);
-    ReadSetting("Debugging", Settings::values.dump_exefs);
-    ReadSetting("Debugging", Settings::values.dump_nso);
-    ReadSetting("Debugging", Settings::values.enable_fs_access_log);
-    ReadSetting("Debugging", Settings::values.reporting_services);
-    ReadSetting("Debugging", Settings::values.quest_flag);
-    ReadSetting("Debugging", Settings::values.use_debug_asserts);
-    ReadSetting("Debugging", Settings::values.use_auto_stub);
-    ReadSetting("Debugging", Settings::values.disable_macro_jit);
-    ReadSetting("Debugging", Settings::values.disable_macro_hle);
-    ReadSetting("Debugging", Settings::values.use_gdbstub);
-    ReadSetting("Debugging", Settings::values.gdbstub_port);
-
-    const auto title_list = config->Get("AddOns", "title_ids", "");
-    std::stringstream ss(title_list);
-    std::string line;
-    while (std::getline(ss, line, '|')) {
-        const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
-        const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
-
-        std::stringstream inner_ss(disabled_list);
-        std::string inner_line;
-        std::vector<std::string> out;
-        while (std::getline(inner_ss, inner_line, '|')) {
-            out.push_back(inner_line);
-        }
-
-        Settings::values.disabled_addons.insert_or_assign(title_id, out);
-    }
-
-    // Web Service
-    ReadSetting("WebService", Settings::values.enable_telemetry);
-    ReadSetting("WebService", Settings::values.web_api_url);
-    ReadSetting("WebService", Settings::values.yuzu_username);
-    ReadSetting("WebService", Settings::values.yuzu_token);
-
-    // Network
-    ReadSetting("Network", Settings::values.network_interface);
-
-    // Android
-    ReadSetting("Android", AndroidSettings::values.picture_in_picture);
-    ReadSetting("Android", AndroidSettings::values.screen_layout);
-}
-
-void Config::Initialize(const std::string& config_name) {
-    const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
-    const auto config_file = fmt::format("{}.ini", config_name);
-
-    switch (type) {
-    case ConfigType::GlobalConfig:
-        config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
-        break;
-    case ConfigType::PerGameConfig:
-        config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
-        break;
-    case ConfigType::InputProfile:
-        config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
-        LoadINI(DefaultINI::android_config_file);
-        return;
-    }
-    LoadINI(DefaultINI::android_config_file);
-    ReadValues();
-}
diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h
deleted file mode 100644
index e1e8f47ed2..0000000000
--- a/src/android/app/src/main/jni/config.h
+++ /dev/null
@@ -1,47 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <filesystem>
-#include <memory>
-#include <optional>
-#include <string>
-
-#include "common/settings.h"
-
-class INIReader;
-
-class Config {
-    bool LoadINI(const std::string& default_contents = "", bool retry = true);
-
-public:
-    enum class ConfigType {
-        GlobalConfig,
-        PerGameConfig,
-        InputProfile,
-    };
-
-    explicit Config(const std::string& config_name = "config",
-                    ConfigType config_type = ConfigType::GlobalConfig);
-    ~Config();
-
-    void Initialize(const std::string& config_name);
-
-private:
-    /**
-     * Applies a value read from the config to a Setting.
-     *
-     * @param group The name of the INI group
-     * @param setting The yuzu setting to modify
-     */
-    template <typename Type, bool ranged>
-    void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
-
-    void ReadValues();
-
-    const ConfigType type;
-    std::unique_ptr<INIReader> config;
-    std::string config_loc;
-    const bool global;
-};
diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h
deleted file mode 100644
index d81422a748..0000000000
--- a/src/android/app/src/main/jni/default_ini.h
+++ /dev/null
@@ -1,511 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-namespace DefaultINI {
-
-const char* android_config_file = R"(
-
-[ControlsP0]
-# The input devices and parameters for each Switch native input
-# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ...
-# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
-# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
-
-# Indicates if this player should be connected at boot
-connected=
-
-# for button input, the following devices are available:
-#  - "keyboard" (default) for keyboard input. Required parameters:
-#      - "code": the code of the key to bind
-#  - "sdl" for joystick input using SDL. Required parameters:
-#      - "guid": SDL identification GUID of the joystick
-#      - "port": the index of the joystick to bind
-#      - "button"(optional): the index of the button to bind
-#      - "hat"(optional): the index of the hat to bind as direction buttons
-#      - "axis"(optional): the index of the axis to bind
-#      - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
-#      - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
-#          triggered if the axis value crosses
-#      - "direction"(only used for axis): "+" means the button is triggered when the axis value
-#          is greater than the threshold; "-" means the button is triggered when the axis value
-#          is smaller than the threshold
-button_a=
-button_b=
-button_x=
-button_y=
-button_lstick=
-button_rstick=
-button_l=
-button_r=
-button_zl=
-button_zr=
-button_plus=
-button_minus=
-button_dleft=
-button_dup=
-button_dright=
-button_ddown=
-button_lstick_left=
-button_lstick_up=
-button_lstick_right=
-button_lstick_down=
-button_sl=
-button_sr=
-button_home=
-button_screenshot=
-
-# for analog input, the following devices are available:
-#  - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
-#      - "up", "down", "left", "right": sub-devices for each direction.
-#          Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
-#      - "modifier": sub-devices as a modifier.
-#      - "modifier_scale": a float number representing the applied modifier scale to the analog input.
-#          Must be in range of 0.0-1.0. Defaults to 0.5
-#  - "sdl" for joystick input using SDL. Required parameters:
-#      - "guid": SDL identification GUID of the joystick
-#      - "port": the index of the joystick to bind
-#      - "axis_x": the index of the axis to bind as x-axis (default to 0)
-#      - "axis_y": the index of the axis to bind as y-axis (default to 1)
-lstick=
-rstick=
-
-# for motion input, the following devices are available:
-#  - "keyboard" (default) for emulating random motion input from buttons. Required parameters:
-#      - "code": the code of the key to bind
-#  - "sdl" for motion input using SDL. Required parameters:
-#      - "guid": SDL identification GUID of the joystick
-#      - "port": the index of the joystick to bind
-#      - "motion": the index of the motion sensor to bind
-#  - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters:
-#      - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001"
-#      - "port": the port of the cemu hook server
-#      - "pad": the index of the joystick
-#      - "motion": the index of the motion sensor of the joystick to bind
-motionleft=
-motionright=
-
-[ControlsGeneral]
-# To use the debug_pad, prepend `debug_pad_` before each button setting above.
-# i.e. debug_pad_button_a=
-
-# Enable debug pad inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-debug_pad_enabled =
-
-# Whether to enable or disable vibration
-# 0: Disabled, 1 (default): Enabled
-vibration_enabled=
-
-# Whether to enable or disable accurate vibrations
-# 0 (default): Disabled, 1: Enabled
-enable_accurate_vibrations=
-
-# Enables controller motion inputs
-# 0: Disabled, 1 (default): Enabled
-motion_enabled =
-
-# Defines the udp device's touch screen coordinate system for cemuhookudp devices
-#  - "min_x", "min_y", "max_x", "max_y"
-touch_device=
-
-# for mapping buttons to touch inputs.
-#touch_from_button_map=1
-#touch_from_button_maps_0_name=default
-#touch_from_button_maps_0_count=2
-#touch_from_button_maps_0_bind_0=foo
-#touch_from_button_maps_0_bind_1=bar
-# etc.
-
-# List of Cemuhook UDP servers, delimited by ','.
-# Default: 127.0.0.1:26760
-# Example: 127.0.0.1:26760,123.4.5.67:26761
-udp_input_servers =
-
-# Enable controlling an axis via a mouse input.
-# 0 (default): Off, 1: On
-mouse_panning =
-
-# Set mouse sensitivity.
-# Default: 1.0
-mouse_panning_sensitivity =
-
-# Emulate an analog control stick from keyboard inputs.
-# 0 (default): Disabled, 1: Enabled
-emulate_analog_keyboard =
-
-# Enable mouse inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-mouse_enabled =
-
-# Enable keyboard inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-keyboard_enabled =
-
-[Core]
-# Whether to use multi-core for CPU emulation
-# 0: Disabled, 1 (default): Enabled
-use_multi_core =
-
-# Enable unsafe extended guest system memory layout (8GB DRAM)
-# 0 (default): Disabled, 1: Enabled
-use_unsafe_extended_memory_layout =
-
-[Cpu]
-# Adjusts various optimizations.
-# Auto-select mode enables choice unsafe optimizations.
-# Accurate enables only safe optimizations.
-# Unsafe allows any unsafe optimizations.
-# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations
-cpu_accuracy =
-
-# Allow disabling safe optimizations.
-# 0 (default): Disabled, 1: Enabled
-cpu_debug_mode =
-
-# Enable inline page tables optimization (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_page_tables =
-
-# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_block_linking =
-
-# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_return_stack_buffer =
-
-# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fast_dispatcher =
-
-# Enable context elimination CPU Optimization (reduce host memory use for guest context)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_context_elimination =
-
-# Enable constant propagation CPU optimization (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_const_prop =
-
-# Enable miscellaneous CPU optimizations (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_misc_ir =
-
-# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_reduce_misalign_checks =
-
-# Enable Host MMU Emulation (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fastmem =
-
-# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fastmem_exclusives =
-
-# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_recompile_exclusives =
-
-# Enable optimization to ignore invalid memory accesses (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_ignore_memory_aborts =
-
-# Enable unfuse FMA (improve performance on CPUs without FMA)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_unfuse_fma =
-
-# Enable faster FRSQRTE and FRECPE
-# Only enabled if cpu_accuracy is set to Unsafe.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_reduce_fp_error =
-
-# Enable faster ASIMD instructions (32 bits only)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_ignore_standard_fpcr =
-
-# Enable inaccurate NaN handling
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_inaccurate_nan =
-
-# Disable address space checks (64 bits only)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_fastmem_check =
-
-# Enable faster exclusive instructions
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_ignore_global_monitor =
-
-[Renderer]
-# Which backend API to use.
-# 0: OpenGL (unsupported), 1 (default): Vulkan, 2: Null
-backend =
-
-# Whether to enable asynchronous presentation (Vulkan only)
-# 0: Off, 1 (default): On
-async_presentation =
-
-# Forces the GPU to run at the maximum possible clocks (thermal constraints will still be applied).
-# 0 (default): Disabled, 1: Enabled
-force_max_clock =
-
-# Enable graphics API debugging mode.
-# 0 (default): Disabled, 1: Enabled
-debug =
-
-# Enable shader feedback.
-# 0 (default): Disabled, 1: Enabled
-renderer_shader_feedback =
-
-# Enable Nsight Aftermath crash dumps
-# 0 (default): Disabled, 1: Enabled
-nsight_aftermath =
-
-# Disable shader loop safety checks, executing the shader without loop logic changes
-# 0 (default): Disabled, 1: Enabled
-disable_shader_loop_safety_checks =
-
-# Which Vulkan physical device to use (defaults to 0)
-vulkan_device =
-
-# 0: 0.5x (360p/540p) [EXPERIMENTAL]
-# 1: 0.75x (540p/810p) [EXPERIMENTAL]
-# 2 (default): 1x (720p/1080p)
-# 3: 2x (1440p/2160p)
-# 4: 3x (2160p/3240p)
-# 5: 4x (2880p/4320p)
-# 6: 5x (3600p/5400p)
-# 7: 6x (4320p/6480p)
-resolution_setup =
-
-# Pixel filter to use when up- or down-sampling rendered frames.
-# 0: Nearest Neighbor
-# 1 (default): Bilinear
-# 2: Bicubic
-# 3: Gaussian
-# 4: ScaleForce
-# 5: AMD FidelityFX™️ Super Resolution [Vulkan Only]
-scaling_filter =
-
-# Anti-Aliasing (AA)
-# 0 (default): None, 1: FXAA
-anti_aliasing =
-
-# Whether to use fullscreen or borderless window mode
-# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen
-fullscreen_mode =
-
-# Aspect ratio
-# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window
-aspect_ratio =
-
-# Anisotropic filtering
-# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x
-max_anisotropy =
-
-# Whether to enable VSync or not.
-# OpenGL: Values other than 0 enable VSync
-# Vulkan: FIFO is selected if the requested mode is not supported by the driver.
-# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
-# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
-# Mailbox can have lower latency than FIFO and does not tear but may drop frames.
-# Immediate (no synchronization) just presents whatever is available and can exhibit tearing.
-# 0: Immediate (Off), 1 (Default): Mailbox (On), 2: FIFO, 3: FIFO Relaxed
-use_vsync =
-
-# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is
-# not available and GLASM is selected, GLSL will be used.
-# 0: GLSL, 1 (default): GLASM, 2: SPIR-V
-shader_backend =
-
-# Whether to allow asynchronous shader building.
-# 0 (default): Off, 1: On
-use_asynchronous_shaders =
-
-# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
-# 0 (default): Off, 1: On
-use_reactive_flushing =
-
-# NVDEC emulation.
-# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding
-nvdec_emulation =
-
-# Accelerate ASTC texture decoding.
-# 0 (default): Off, 1: On
-accelerate_astc =
-
-# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value
-# 0: Off, 1: On (default)
-use_speed_limit =
-
-# Limits the speed of the game to run no faster than this value as a percentage of target speed
-# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
-speed_limit =
-
-# Whether to use disk based shader cache
-# 0: Off, 1 (default): On
-use_disk_shader_cache =
-
-# Which gpu accuracy level to use
-# 0 (default): Normal, 1: High, 2: Extreme (Very slow)
-gpu_accuracy =
-
-# Whether to use asynchronous GPU emulation
-# 0 : Off (slow), 1 (default): On (fast)
-use_asynchronous_gpu_emulation =
-
-# Inform the guest that GPU operations completed more quickly than they did.
-# 0: Off, 1 (default): On
-use_fast_gpu_time =
-
-# Force unmodified buffers to be flushed, which can cost performance.
-# 0: Off (default), 1: On
-use_pessimistic_flushes =
-
-# Whether to use garbage collection or not for GPU caches.
-# 0 (default): Off, 1: On
-use_caches_gc =
-
-# The clear color for the renderer. What shows up on the sides of the bottom screen.
-# Must be in range of 0-255. Defaults to 0 for all.
-bg_red =
-bg_blue =
-bg_green =
-
-[Audio]
-# Which audio output engine to use.
-# auto (default): Auto-select
-# cubeb: Cubeb audio engine (if available)
-# sdl2: SDL2 audio engine (if available)
-# null: No audio output
-output_engine =
-
-# Which audio device to use.
-# auto (default): Auto-select
-output_device =
-
-# Output volume.
-# 100 (default): 100%, 0; mute
-volume =
-
-[Data Storage]
-# Whether to create a virtual SD card.
-# 1: Yes, 0 (default): No
-use_virtual_sd =
-
-# Whether or not to enable gamecard emulation
-# 1: Yes, 0 (default): No
-gamecard_inserted =
-
-# Whether or not the gamecard should be emulated as the current game
-# If 'gamecard_inserted' is 0 this setting is irrelevant
-# 1: Yes, 0 (default): No
-gamecard_current_game =
-
-# Path to an XCI file to use as the gamecard
-# If 'gamecard_inserted' is 0 this setting is irrelevant
-# If 'gamecard_current_game' is 1 this setting is irrelevant
-gamecard_path =
-
-[System]
-# Whether the system is docked
-# 1 (default): Yes, 0: No
-use_docked_mode =
-
-# Sets the seed for the RNG generator built into the switch
-# rng_seed will be ignored and randomly generated if rng_seed_enabled is false
-rng_seed_enabled =
-rng_seed =
-
-# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
-# This will auto-increment, with the time set being the time the game is started
-# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
-custom_rtc_enabled =
-custom_rtc =
-
-# Sets the systems language index
-# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese,
-# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French,
-# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese
-language_index =
-
-# The system region that yuzu will use during emulation
-# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
-region_index =
-
-# The system time zone that yuzu will use during emulation
-# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone
-time_zone_index =
-
-# Sets the sound output mode.
-# 0: Mono, 1 (default): Stereo, 2: Surround
-sound_index =
-
-[Miscellaneous]
-# A filter which removes logs below a certain logging level.
-# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
-log_filter = *:Trace
-
-# Use developer keys
-# 0 (default): Disabled, 1: Enabled
-use_dev_keys =
-
-[Debugging]
-# Record frame time data, can be found in the log directory. Boolean value
-record_frame_times =
-# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them
-dump_exefs=false
-# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
-dump_nso=false
-# Determines whether or not yuzu will save the filesystem access log.
-enable_fs_access_log=false
-# Enables verbose reporting services
-reporting_services =
-# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode
-# false: Retail/Normal Mode (default), true: Kiosk Mode
-quest_flag =
-# Determines whether debug asserts should be enabled, which will throw an exception on asserts.
-# false: Disabled (default), true: Enabled
-use_debug_asserts =
-# Determines whether unimplemented HLE service calls should be automatically stubbed.
-# false: Disabled (default), true: Enabled
-use_auto_stub =
-# Enables/Disables the macro JIT compiler
-disable_macro_jit=false
-# Determines whether to enable the GDB stub and wait for the debugger to attach before running.
-# false: Disabled (default), true: Enabled
-use_gdbstub=false
-# The port to use for the GDB server, if it is enabled.
-gdbstub_port=6543
-
-[WebService]
-# Whether or not to enable telemetry
-# 0: No, 1 (default): Yes
-enable_telemetry =
-# URL for Web API
-web_api_url = https://api.yuzu-emu.org
-# Username and token for yuzu Web Service
-# See https://profile.yuzu-emu.org/ for more info
-yuzu_username =
-yuzu_token =
-
-[Network]
-# Name of the network interface device to use with yuzu LAN play.
-# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo'
-# e.g. On Windows: 'Ethernet', 'Wi-Fi'
-network_interface =
-
-[AddOns]
-# Used to disable add-ons
-# List of title IDs of games that will have add-ons disabled (separated by '|'):
-title_ids =
-# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
-# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
-)";
-} // namespace DefaultINI
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 64663b0845..617288ae4f 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -52,8 +52,8 @@
 #include "core/hle/service/am/applets/applets.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/loader/loader.h"
+#include "frontend_common/config.h"
 #include "jni/android_common/android_common.h"
-#include "jni/config.h"
 #include "jni/id_cache.h"
 #include "jni/native.h"
 #include "video_core/renderer_base.h"
@@ -664,8 +664,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
 
 void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz,
                                                             jboolean reload) {
-    // Create the default config.ini.
-    Config{};
     // Initialize the emulated system.
     if (!reload) {
         EmulationSession::GetInstance().System().Initialize();
@@ -680,17 +678,6 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass cl
 void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z(
     JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {}
 
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass clazz) {
-    Config{};
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
-                                                       jstring j_game_id) {
-    std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
-
-    env->ReleaseStringUTFChars(j_game_id, game_id.data());
-}
-
 jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) {
     jdoubleArray j_stats = env->NewDoubleArray(4);
 
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 8a704960cd..8e81816e52 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -5,11 +5,14 @@
 
 #include <jni.h>
 
+#include "android_config.h"
+#include "android_settings.h"
 #include "common/logging/log.h"
 #include "common/settings.h"
+#include "frontend_common/config.h"
 #include "jni/android_common/android_common.h"
-#include "jni/config.h"
-#include "uisettings.h"
+
+std::unique_ptr<AndroidConfig> config;
 
 template <typename T>
 Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
@@ -28,6 +31,22 @@ Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
 
 extern "C" {
 
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeConfig(JNIEnv* env, jobject obj) {
+    config = std::make_unique<AndroidConfig>();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadConfig(JNIEnv* env, jobject obj) {
+    config.reset();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadSettings(JNIEnv* env, jobject obj) {
+    config->AndroidConfig::ReloadAllValues();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveSettings(JNIEnv* env, jobject obj) {
+    config->AndroidConfig::SaveAllValues();
+}
+
 jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
                                                                jstring jkey, jboolean getDefault) {
     auto setting = getSetting<bool>(env, jkey);
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 51717be06f..a10131eb28 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -206,9 +206,9 @@ const char* TranslateCategory(Category category) {
     case Category::UiAudio:
         return "UiAudio";
     case Category::UiLayout:
-        return "UiLayout";
+        return "UILayout";
     case Category::UiGameList:
-        return "UiGameList";
+        return "UIGameList";
     case Category::Screenshots:
         return "Screenshots";
     case Category::Shortcuts:
diff --git a/src/frontend_common/CMakeLists.txt b/src/frontend_common/CMakeLists.txt
new file mode 100644
index 0000000000..1537271fc2
--- /dev/null
+++ b/src/frontend_common/CMakeLists.txt
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+add_library(frontend_common STATIC
+    config.cpp
+    config.h
+)
+
+create_target_directory_groups(frontend_common)
+target_link_libraries(frontend_common PUBLIC core SimpleIni PRIVATE common Boost::headers)
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp
new file mode 100644
index 0000000000..d317d81fd7
--- /dev/null
+++ b/src/frontend_common/config.cpp
@@ -0,0 +1,931 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <array>
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/settings.h"
+#include "common/settings_common.h"
+#include "common/settings_enums.h"
+#include "config.h"
+#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "network/network.h"
+
+#include <boost/algorithm/string/replace.hpp>
+
+namespace FS = Common::FS;
+
+Config::Config(const ConfigType config_type)
+    : type(config_type), global{config_type == ConfigType::GlobalConfig} {}
+
+void Config::Initialize(const std::string& config_name) {
+    const std::filesystem::path fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
+    const auto config_file = fmt::format("{}.ini", config_name);
+
+    switch (type) {
+    case ConfigType::GlobalConfig:
+        config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
+        void(FS::CreateParentDir(config_loc));
+        SetUpIni();
+        Reload();
+        break;
+    case ConfigType::PerGameConfig:
+        config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
+        void(FS::CreateParentDir(config_loc));
+        SetUpIni();
+        Reload();
+        break;
+    case ConfigType::InputProfile:
+        config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
+        void(FS::CreateParentDir(config_loc));
+        SetUpIni();
+        break;
+    }
+}
+
+void Config::Initialize(const std::optional<std::string> config_path) {
+    const std::filesystem::path default_sdl_config_path =
+        FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini";
+    config_loc = config_path.value_or(FS::PathToUTF8String(default_sdl_config_path));
+    void(FS::CreateParentDir(config_loc));
+    SetUpIni();
+    Reload();
+}
+
+void Config::WriteToIni() const {
+    if (const SI_Error rc = config->SaveFile(config_loc.c_str()); rc < 0) {
+        LOG_ERROR(Frontend, "Config file could not be saved!");
+    }
+}
+
+void Config::SetUpIni() {
+    config = std::make_unique<CSimpleIniA>();
+    config->SetUnicode(true);
+    config->SetSpaces(false);
+    config->LoadFile(config_loc.c_str());
+}
+
+bool Config::IsCustomConfig() const {
+    return type == ConfigType::PerGameConfig;
+}
+
+void Config::ReadPlayerValues(const std::size_t player_index) {
+    std::string player_prefix;
+    if (type != ConfigType::InputProfile) {
+        player_prefix.append("player_").append(ToString(player_index)).append("_");
+    }
+
+    auto& player = Settings::values.players.GetValue()[player_index];
+    if (IsCustomConfig()) {
+        const auto profile_name =
+            ReadStringSetting(std::string(player_prefix).append("profile_name"));
+        if (profile_name.empty()) {
+            // Use the global input config
+            player = Settings::values.players.GetValue(true)[player_index];
+            return;
+        }
+        player.profile_name = profile_name;
+    }
+
+    if (player_prefix.empty() && Settings::IsConfiguringGlobal()) {
+        const auto controller = static_cast<Settings::ControllerType>(
+            ReadIntegerSetting(std::string(player_prefix).append("type"),
+                               static_cast<u8>(Settings::ControllerType::ProController)));
+
+        if (controller == Settings::ControllerType::LeftJoycon ||
+            controller == Settings::ControllerType::RightJoycon) {
+            player.controller_type = controller;
+        }
+    } else {
+        std::string connected_key = player_prefix;
+        player.connected = ReadBooleanSetting(connected_key.append("connected"),
+                                              std::make_optional(player_index == 0));
+
+        player.controller_type = static_cast<Settings::ControllerType>(
+            ReadIntegerSetting(std::string(player_prefix).append("type"),
+                               static_cast<u8>(Settings::ControllerType::ProController)));
+
+        player.vibration_enabled = ReadBooleanSetting(
+            std::string(player_prefix).append("vibration_enabled"), std::make_optional(true));
+
+        player.vibration_strength = static_cast<int>(
+            ReadIntegerSetting(std::string(player_prefix).append("vibration_strength"), 100));
+
+        player.body_color_left = static_cast<u32>(ReadIntegerSetting(
+            std::string(player_prefix).append("body_color_left"), Settings::JOYCON_BODY_NEON_BLUE));
+        player.body_color_right = static_cast<u32>(ReadIntegerSetting(
+            std::string(player_prefix).append("body_color_right"), Settings::JOYCON_BODY_NEON_RED));
+        player.button_color_left = static_cast<u32>(
+            ReadIntegerSetting(std::string(player_prefix).append("button_color_left"),
+                               Settings::JOYCON_BUTTONS_NEON_BLUE));
+        player.button_color_right = static_cast<u32>(
+            ReadIntegerSetting(std::string(player_prefix).append("button_color_right"),
+                               Settings::JOYCON_BUTTONS_NEON_RED));
+    }
+}
+
+void Config::ReadTouchscreenValues() {
+    Settings::values.touchscreen.enabled =
+        ReadBooleanSetting(std::string("touchscreen_enabled"), std::make_optional(true));
+    Settings::values.touchscreen.rotation_angle =
+        static_cast<u32>(ReadIntegerSetting(std::string("touchscreen_angle"), 0));
+    Settings::values.touchscreen.diameter_x =
+        static_cast<u32>(ReadIntegerSetting(std::string("touchscreen_diameter_x"), 15));
+    Settings::values.touchscreen.diameter_y =
+        static_cast<u32>(ReadIntegerSetting(std::string("touchscreen_diameter_y"), 15));
+}
+
+void Config::ReadAudioValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Audio));
+
+    ReadCategory(Settings::Category::Audio);
+    ReadCategory(Settings::Category::UiAudio);
+
+    EndGroup();
+}
+
+void Config::ReadControlValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+    ReadCategory(Settings::Category::Controls);
+
+    Settings::values.players.SetGlobal(!IsCustomConfig());
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+        ReadPlayerValues(p);
+    }
+
+    // Disable docked mode if handheld is selected
+    const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
+    if (controller_type == Settings::ControllerType::Handheld) {
+        Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
+        Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
+    }
+
+    if (IsCustomConfig()) {
+        EndGroup();
+        return;
+    }
+    ReadTouchscreenValues();
+    ReadMotionTouchValues();
+
+    EndGroup();
+}
+
+void Config::ReadMotionTouchValues() {
+    int num_touch_from_button_maps = BeginArray(std::string("touch_from_button_maps"));
+
+    if (num_touch_from_button_maps > 0) {
+        for (int i = 0; i < num_touch_from_button_maps; ++i) {
+            SetArrayIndex(i);
+
+            Settings::TouchFromButtonMap map;
+            map.name = ReadStringSetting(std::string("name"), std::string("default"));
+
+            const int num_touch_maps = BeginArray(std::string("entries"));
+            map.buttons.reserve(num_touch_maps);
+            for (int j = 0; j < num_touch_maps; j++) {
+                SetArrayIndex(j);
+                std::string touch_mapping = ReadStringSetting(std::string("bind"));
+                map.buttons.emplace_back(std::move(touch_mapping));
+            }
+            EndArray(); // entries
+            Settings::values.touch_from_button_maps.emplace_back(std::move(map));
+        }
+    } else {
+        Settings::values.touch_from_button_maps.emplace_back(
+            Settings::TouchFromButtonMap{"default", {}});
+        num_touch_from_button_maps = 1;
+    }
+    EndArray(); // touch_from_button_maps
+
+    Settings::values.touch_from_button_map_index = std::clamp(
+        Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
+}
+
+void Config::ReadCoreValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Core));
+
+    ReadCategory(Settings::Category::Core);
+
+    EndGroup();
+}
+
+void Config::ReadDataStorageValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
+
+    FS::SetYuzuPath(FS::YuzuPath::NANDDir, ReadStringSetting(std::string("nand_directory")));
+    FS::SetYuzuPath(FS::YuzuPath::SDMCDir, ReadStringSetting(std::string("sdmc_directory")));
+    FS::SetYuzuPath(FS::YuzuPath::LoadDir, ReadStringSetting(std::string("load_directory")));
+    FS::SetYuzuPath(FS::YuzuPath::DumpDir, ReadStringSetting(std::string("dump_directory")));
+    FS::SetYuzuPath(FS::YuzuPath::TASDir, ReadStringSetting(std::string("tas_directory")));
+
+    ReadCategory(Settings::Category::DataStorage);
+
+    EndGroup();
+}
+
+void Config::ReadDebuggingValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
+
+    // Intentionally not using the QT default setting as this is intended to be changed in the ini
+    Settings::values.record_frame_times =
+        ReadBooleanSetting(std::string("record_frame_times"), std::make_optional(false));
+
+    ReadCategory(Settings::Category::Debugging);
+    ReadCategory(Settings::Category::DebuggingGraphics);
+
+    EndGroup();
+}
+
+void Config::ReadServiceValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
+
+    ReadCategory(Settings::Category::Services);
+
+    EndGroup();
+}
+
+void Config::ReadDisabledAddOnValues() {
+    // Custom config section
+    BeginGroup(std::string("DisabledAddOns"));
+
+    const int size = BeginArray(std::string(""));
+    for (int i = 0; i < size; ++i) {
+        SetArrayIndex(i);
+        const auto title_id = ReadIntegerSetting(std::string("title_id"), 0);
+        std::vector<std::string> out;
+        const int d_size = BeginArray("disabled");
+        for (int j = 0; j < d_size; ++j) {
+            SetArrayIndex(j);
+            out.push_back(ReadStringSetting(std::string("d"), std::string("")));
+        }
+        EndArray(); // d
+        Settings::values.disabled_addons.insert_or_assign(title_id, out);
+    }
+    EndArray(); // Base disabled addons array - Has no base key
+
+    EndGroup();
+}
+
+void Config::ReadMiscellaneousValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous));
+
+    ReadCategory(Settings::Category::Miscellaneous);
+
+    EndGroup();
+}
+
+void Config::ReadCpuValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Cpu));
+
+    ReadCategory(Settings::Category::Cpu);
+    ReadCategory(Settings::Category::CpuDebug);
+    ReadCategory(Settings::Category::CpuUnsafe);
+
+    EndGroup();
+}
+
+void Config::ReadRendererValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Renderer));
+
+    ReadCategory(Settings::Category::Renderer);
+    ReadCategory(Settings::Category::RendererAdvanced);
+    ReadCategory(Settings::Category::RendererDebug);
+
+    EndGroup();
+}
+
+void Config::ReadScreenshotValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
+
+    ReadCategory(Settings::Category::Screenshots);
+    FS::SetYuzuPath(FS::YuzuPath::ScreenshotsDir,
+                    ReadStringSetting(std::string("screenshot_path"),
+                                      FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)));
+
+    EndGroup();
+}
+
+void Config::ReadSystemValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::System));
+
+    ReadCategory(Settings::Category::System);
+    ReadCategory(Settings::Category::SystemAudio);
+
+    EndGroup();
+}
+
+void Config::ReadWebServiceValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::WebService));
+
+    ReadCategory(Settings::Category::WebService);
+
+    EndGroup();
+}
+
+void Config::ReadNetworkValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
+
+    ReadCategory(Settings::Category::Network);
+
+    EndGroup();
+}
+
+void Config::ReadValues() {
+    if (global) {
+        ReadDataStorageValues();
+        ReadDebuggingValues();
+        ReadDisabledAddOnValues();
+        ReadNetworkValues();
+        ReadServiceValues();
+        ReadWebServiceValues();
+        ReadMiscellaneousValues();
+    }
+    ReadControlValues();
+    ReadCoreValues();
+    ReadCpuValues();
+    ReadRendererValues();
+    ReadAudioValues();
+    ReadSystemValues();
+}
+
+void Config::SavePlayerValues(const std::size_t player_index) {
+    std::string player_prefix;
+    if (type != ConfigType::InputProfile) {
+        player_prefix = std::string("player_").append(ToString(player_index)).append("_");
+    }
+
+    const auto& player = Settings::values.players.GetValue()[player_index];
+    if (IsCustomConfig()) {
+        if (player.profile_name.empty()) {
+            // No custom profile selected
+            return;
+        }
+        WriteSetting(std::string(player_prefix).append("profile_name"), player.profile_name,
+                     std::make_optional(std::string("")));
+    }
+
+    WriteSetting(std::string(player_prefix).append("type"), static_cast<u8>(player.controller_type),
+                 std::make_optional(static_cast<u8>(Settings::ControllerType::ProController)));
+
+    if (!player_prefix.empty() || !Settings::IsConfiguringGlobal()) {
+        WriteSetting(std::string(player_prefix).append("connected"), player.connected,
+                     std::make_optional(player_index == 0));
+        WriteSetting(std::string(player_prefix).append("vibration_enabled"),
+                     player.vibration_enabled, std::make_optional(true));
+        WriteSetting(std::string(player_prefix).append("vibration_strength"),
+                     player.vibration_strength, std::make_optional(100));
+        WriteSetting(std::string(player_prefix).append("body_color_left"), player.body_color_left,
+                     std::make_optional(Settings::JOYCON_BODY_NEON_BLUE));
+        WriteSetting(std::string(player_prefix).append("body_color_right"), player.body_color_right,
+                     std::make_optional(Settings::JOYCON_BODY_NEON_RED));
+        WriteSetting(std::string(player_prefix).append("button_color_left"),
+                     player.button_color_left,
+                     std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE));
+        WriteSetting(std::string(player_prefix).append("button_color_right"),
+                     player.button_color_right,
+                     std::make_optional(Settings::JOYCON_BUTTONS_NEON_RED));
+    }
+}
+
+void Config::SaveTouchscreenValues() {
+    const auto& touchscreen = Settings::values.touchscreen;
+
+    WriteSetting(std::string("touchscreen_enabled"), touchscreen.enabled, std::make_optional(true));
+
+    WriteSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle,
+                 std::make_optional(static_cast<u32>(0)));
+    WriteSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x,
+                 std::make_optional(static_cast<u32>(15)));
+    WriteSetting(std::string("touchscreen_diameter_y"), touchscreen.diameter_y,
+                 std::make_optional(static_cast<u32>(15)));
+}
+
+void Config::SaveMotionTouchValues() {
+    BeginArray(std::string("touch_from_button_maps"));
+    for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
+        SetArrayIndex(static_cast<int>(p));
+        WriteSetting(std::string("name"), Settings::values.touch_from_button_maps[p].name,
+                     std::make_optional(std::string("default")));
+
+        BeginArray(std::string("entries"));
+        for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
+             ++q) {
+            SetArrayIndex(static_cast<int>(q));
+            WriteSetting(std::string("bind"),
+                         Settings::values.touch_from_button_maps[p].buttons[q]);
+        }
+        EndArray(); // entries
+    }
+    EndArray(); // touch_from_button_maps
+}
+
+void Config::SaveValues() {
+    if (global) {
+        SaveDataStorageValues();
+        SaveDebuggingValues();
+        SaveDisabledAddOnValues();
+        SaveNetworkValues();
+        SaveWebServiceValues();
+        SaveMiscellaneousValues();
+    }
+    SaveControlValues();
+    SaveCoreValues();
+    SaveCpuValues();
+    SaveRendererValues();
+    SaveAudioValues();
+    SaveSystemValues();
+
+    WriteToIni();
+}
+
+void Config::SaveAudioValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Audio));
+
+    WriteCategory(Settings::Category::Audio);
+    WriteCategory(Settings::Category::UiAudio);
+
+    EndGroup();
+}
+
+void Config::SaveControlValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+    WriteCategory(Settings::Category::Controls);
+
+    Settings::values.players.SetGlobal(!IsCustomConfig());
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+        SavePlayerValues(p);
+    }
+    if (IsCustomConfig()) {
+        EndGroup();
+        return;
+    }
+    SaveTouchscreenValues();
+    SaveMotionTouchValues();
+
+    EndGroup();
+}
+
+void Config::SaveCoreValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Core));
+
+    WriteCategory(Settings::Category::Core);
+
+    EndGroup();
+}
+
+void Config::SaveDataStorageValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::DataStorage));
+
+    WriteSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir),
+                 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
+    WriteSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir),
+                 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
+    WriteSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir),
+                 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
+    WriteSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir),
+                 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
+    WriteSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir),
+                 std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
+
+    WriteCategory(Settings::Category::DataStorage);
+
+    EndGroup();
+}
+
+void Config::SaveDebuggingValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Debugging));
+
+    // Intentionally not using the QT default setting as this is intended to be changed in the ini
+    WriteSetting(std::string("record_frame_times"), Settings::values.record_frame_times);
+
+    WriteCategory(Settings::Category::Debugging);
+    WriteCategory(Settings::Category::DebuggingGraphics);
+
+    EndGroup();
+}
+
+void Config::SaveNetworkValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Services));
+
+    WriteCategory(Settings::Category::Network);
+
+    EndGroup();
+}
+
+void Config::SaveDisabledAddOnValues() {
+    // Custom config section
+    BeginGroup(std::string("DisabledAddOns"));
+
+    int i = 0;
+    BeginArray(std::string(""));
+    for (const auto& elem : Settings::values.disabled_addons) {
+        SetArrayIndex(i);
+        WriteSetting(std::string("title_id"), elem.first, std::make_optional(static_cast<u64>(0)));
+        BeginArray(std::string("disabled"));
+        for (std::size_t j = 0; j < elem.second.size(); ++j) {
+            SetArrayIndex(static_cast<int>(j));
+            WriteSetting(std::string("d"), elem.second[j], std::make_optional(std::string("")));
+        }
+        EndArray(); // disabled
+        ++i;
+    }
+    EndArray(); // Base disabled addons array - Has no base key
+
+    EndGroup();
+}
+
+void Config::SaveMiscellaneousValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Miscellaneous));
+
+    WriteCategory(Settings::Category::Miscellaneous);
+
+    EndGroup();
+}
+
+void Config::SaveCpuValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Cpu));
+
+    WriteCategory(Settings::Category::Cpu);
+    WriteCategory(Settings::Category::CpuDebug);
+    WriteCategory(Settings::Category::CpuUnsafe);
+
+    EndGroup();
+}
+
+void Config::SaveRendererValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Renderer));
+
+    WriteCategory(Settings::Category::Renderer);
+    WriteCategory(Settings::Category::RendererAdvanced);
+    WriteCategory(Settings::Category::RendererDebug);
+
+    EndGroup();
+}
+
+void Config::SaveScreenshotValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
+
+    WriteSetting(std::string("screenshot_path"),
+                 FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir));
+    WriteCategory(Settings::Category::Screenshots);
+
+    EndGroup();
+}
+
+void Config::SaveSystemValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::System));
+
+    WriteCategory(Settings::Category::System);
+    WriteCategory(Settings::Category::SystemAudio);
+
+    EndGroup();
+}
+
+void Config::SaveWebServiceValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::WebService));
+
+    WriteCategory(Settings::Category::WebService);
+
+    EndGroup();
+}
+
+bool Config::ReadBooleanSetting(const std::string& key, const std::optional<bool> default_value) {
+    std::string full_key = GetFullKey(key, false);
+    if (!default_value.has_value()) {
+        return config->GetBoolValue(GetSection().c_str(), full_key.c_str(), false);
+    }
+
+    if (config->GetBoolValue(GetSection().c_str(),
+                             std::string(full_key).append("\\default").c_str(), false)) {
+        return static_cast<bool>(default_value.value());
+    } else {
+        return config->GetBoolValue(GetSection().c_str(), full_key.c_str(),
+                                    static_cast<bool>(default_value.value()));
+    }
+}
+
+s64 Config::ReadIntegerSetting(const std::string& key, const std::optional<s64> default_value) {
+    std::string full_key = GetFullKey(key, false);
+    if (!default_value.has_value()) {
+        try {
+            return std::stoll(
+                std::string(config->GetValue(GetSection().c_str(), full_key.c_str(), "0")));
+        } catch (...) {
+            return 0;
+        }
+    }
+
+    s64 result = 0;
+    if (config->GetBoolValue(GetSection().c_str(),
+                             std::string(full_key).append("\\default").c_str(), true)) {
+        result = default_value.value();
+    } else {
+        try {
+            result = std::stoll(std::string(config->GetValue(
+                GetSection().c_str(), full_key.c_str(), ToString(default_value.value()).c_str())));
+        } catch (...) {
+            result = default_value.value();
+        }
+    }
+    return result;
+}
+
+double Config::ReadDoubleSetting(const std::string& key,
+                                 const std::optional<double> default_value) {
+    std::string full_key = GetFullKey(key, false);
+    if (!default_value.has_value()) {
+        return config->GetDoubleValue(GetSection().c_str(), full_key.c_str(), 0);
+    }
+
+    double result;
+    if (config->GetBoolValue(GetSection().c_str(),
+                             std::string(full_key).append("\\default").c_str(), true)) {
+        result = default_value.value();
+    } else {
+        result =
+            config->GetDoubleValue(GetSection().c_str(), full_key.c_str(), default_value.value());
+    }
+    return result;
+}
+
+std::string Config::ReadStringSetting(const std::string& key,
+                                      const std::optional<std::string> default_value) {
+    std::string result;
+    std::string full_key = GetFullKey(key, false);
+    if (!default_value.has_value()) {
+        result = config->GetValue(GetSection().c_str(), full_key.c_str(), "");
+        boost::replace_all(result, "\"", "");
+        return result;
+    }
+
+    if (config->GetBoolValue(GetSection().c_str(),
+                             std::string(full_key).append("\\default").c_str(), true)) {
+        result = default_value.value();
+    } else {
+        result =
+            config->GetValue(GetSection().c_str(), full_key.c_str(), default_value.value().c_str());
+    }
+    boost::replace_all(result, "\"", "");
+    boost::replace_all(result, "//", "/");
+    return result;
+}
+
+bool Config::Exists(const std::string& section, const std::string& key) const {
+    const std::string value = config->GetValue(section.c_str(), key.c_str(), "");
+    return !value.empty();
+}
+
+template <typename Type>
+void Config::WriteSetting(const std::string& key, const Type& value,
+                          const std::optional<Type>& default_value,
+                          const std::optional<bool>& use_global) {
+    std::string full_key = GetFullKey(key, false);
+
+    std::string saved_value;
+    std::string string_default;
+    if constexpr (std::is_same_v<Type, std::string>) {
+        saved_value.append(AdjustOutputString(value));
+        if (default_value.has_value()) {
+            string_default.append(AdjustOutputString(default_value.value()));
+        }
+    } else {
+        saved_value.append(AdjustOutputString(ToString(value)));
+        if (default_value.has_value()) {
+            string_default.append(ToString(default_value.value()));
+        }
+    }
+
+    if (default_value.has_value() && use_global.has_value()) {
+        if (!global) {
+            WriteSettingInternal(std::string(full_key).append("\\global"),
+                                 ToString(use_global.value()));
+        }
+        if (global || use_global.value() == false) {
+            WriteSettingInternal(std::string(full_key).append("\\default"),
+                                 ToString(string_default == saved_value));
+            WriteSettingInternal(full_key, saved_value);
+        }
+    } else if (default_value.has_value() && !use_global.has_value()) {
+        WriteSettingInternal(std::string(full_key).append("\\default"),
+                             ToString(string_default == saved_value));
+        WriteSettingInternal(full_key, saved_value);
+    } else {
+        WriteSettingInternal(full_key, saved_value);
+    }
+}
+
+void Config::WriteSettingInternal(const std::string& key, const std::string& value) {
+    config->SetValue(GetSection().c_str(), key.c_str(), value.c_str());
+}
+
+void Config::Reload() {
+    ReadValues();
+    // To apply default value changes
+    SaveValues();
+}
+
+void Config::Save() {
+    SaveValues();
+}
+
+void Config::ClearControlPlayerValues() const {
+    // If key is an empty string, all keys in the current group() are removed.
+    const char* section = Settings::TranslateCategory(Settings::Category::Controls);
+    CSimpleIniA::TNamesDepend keys;
+    config->GetAllKeys(section, keys);
+    for (const auto& key : keys) {
+        if (std::string(config->GetValue(section, key.pItem)).empty()) {
+            config->Delete(section, key.pItem);
+        }
+    }
+}
+
+const std::string& Config::GetConfigFilePath() const {
+    return config_loc;
+}
+
+void Config::ReadCategory(const Settings::Category category) {
+    const auto& settings = FindRelevantList(category);
+    std::ranges::for_each(settings, [&](const auto& setting) { ReadSettingGeneric(setting); });
+}
+
+void Config::WriteCategory(const Settings::Category category) {
+    const auto& settings = FindRelevantList(category);
+    std::ranges::for_each(settings, [&](const auto& setting) { WriteSettingGeneric(setting); });
+}
+
+void Config::ReadSettingGeneric(Settings::BasicSetting* const setting) {
+    if (!setting->Save() || (!setting->Switchable() && !global)) {
+        return;
+    }
+
+    const std::string key = AdjustKey(setting->GetLabel());
+    const std::string default_value(setting->DefaultToString());
+
+    bool use_global = true;
+    if (setting->Switchable() && !global) {
+        use_global =
+            ReadBooleanSetting(std::string(key).append("\\use_global"), std::make_optional(true));
+        setting->SetGlobal(use_global);
+    }
+
+    if (global || !use_global) {
+        const bool is_default =
+            ReadBooleanSetting(std::string(key).append("\\default"), std::make_optional(true));
+        if (!is_default) {
+            const std::string setting_string = ReadStringSetting(key, default_value);
+            setting->LoadString(setting_string);
+        } else {
+            // Empty string resets the Setting to default
+            setting->LoadString("");
+        }
+    }
+}
+
+void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) {
+    if (!setting->Save()) {
+        return;
+    }
+
+    std::string key = AdjustKey(setting->GetLabel());
+    if (setting->Switchable()) {
+        if (!global) {
+            WriteSetting(std::string(key).append("\\use_global"), setting->UsingGlobal());
+        }
+        if (global || !setting->UsingGlobal()) {
+            WriteSetting(std::string(key).append("\\default"),
+                         setting->ToString() == setting->DefaultToString());
+            WriteSetting(key, setting->ToString());
+        }
+    } else if (global) {
+        WriteSetting(std::string(key).append("\\default"),
+                     setting->ToString() == setting->DefaultToString());
+        WriteSetting(key, setting->ToString());
+    }
+}
+
+void Config::BeginGroup(const std::string& group) {
+    // You can't begin a group while reading/writing from a config array
+    ASSERT(array_stack.empty());
+
+    key_stack.push_back(AdjustKey(group));
+}
+
+void Config::EndGroup() {
+    // You can't end a group if you haven't started one yet
+    ASSERT(!key_stack.empty());
+
+    // You can't end a group when reading/writing from a config array
+    ASSERT(array_stack.empty());
+
+    key_stack.pop_back();
+}
+
+std::string Config::GetSection() {
+    if (key_stack.empty()) {
+        return std::string{""};
+    }
+
+    return key_stack.front();
+}
+
+std::string Config::GetGroup() const {
+    if (key_stack.size() <= 1) {
+        return std::string{""};
+    }
+
+    std::string key;
+    for (size_t i = 1; i < key_stack.size(); ++i) {
+        key.append(key_stack[i]).append("\\");
+    }
+    return key;
+}
+
+std::string Config::AdjustKey(const std::string& key) {
+    std::string adjusted_key(key);
+    boost::replace_all(adjusted_key, "/", "\\");
+    boost::replace_all(adjusted_key, " ", "%20");
+    return adjusted_key;
+}
+
+std::string Config::AdjustOutputString(const std::string& string) {
+    std::string adjusted_string(string);
+    boost::replace_all(adjusted_string, "\\", "/");
+    boost::replace_all(adjusted_string, "//", "/");
+
+    // Needed for backwards compatibility with QSettings deserialization
+    for (const auto& special_character : special_characters) {
+        if (adjusted_string.find(special_character) != std::string::npos) {
+            adjusted_string.insert(0, "\"");
+            adjusted_string.append("\"");
+            break;
+        }
+    }
+    return adjusted_string;
+}
+
+std::string Config::GetFullKey(const std::string& key, bool skipArrayIndex) {
+    if (array_stack.empty()) {
+        return std::string(GetGroup()).append(AdjustKey(key));
+    }
+
+    std::string array_key;
+    for (size_t i = 0; i < array_stack.size(); ++i) {
+        if (!array_stack[i].name.empty()) {
+            array_key.append(array_stack[i].name).append("\\");
+        }
+
+        if (!skipArrayIndex || (array_stack.size() - 1 != i && array_stack.size() > 1)) {
+            array_key.append(ToString(array_stack[i].index)).append("\\");
+        }
+    }
+    std::string final_key = std::string(GetGroup()).append(array_key).append(AdjustKey(key));
+    return final_key;
+}
+
+int Config::BeginArray(const std::string& array) {
+    array_stack.push_back(ConfigArray{AdjustKey(array), 0, 0});
+    const int size = config->GetLongValue(GetSection().c_str(),
+                                          GetFullKey(std::string("size"), true).c_str(), 0);
+    array_stack.back().size = size;
+    return size;
+}
+
+void Config::EndArray() {
+    // You can't end a config array before starting one
+    ASSERT(!array_stack.empty());
+
+    // Write out the size to config
+    if (key_stack.size() == 1 && array_stack.back().name.empty()) {
+        // Edge-case where the first array created doesn't have a name
+        config->SetValue(GetSection().c_str(), std::string("size").c_str(),
+                         ToString(array_stack.back().size).c_str());
+    } else {
+        const auto key = GetFullKey(std::string("size"), true);
+        config->SetValue(GetSection().c_str(), key.c_str(),
+                         ToString(array_stack.back().size).c_str());
+    }
+
+    array_stack.pop_back();
+}
+
+void Config::SetArrayIndex(const int index) {
+    // You can't set the array index if you haven't started one yet
+    ASSERT(!array_stack.empty());
+
+    const int array_index = index + 1;
+
+    // You can't exceed the known max size of the array by more than 1
+    ASSERT(array_stack.front().size + 1 >= array_index);
+
+    // Change the config array size to the current index since you may want
+    // to reduce the number of elements that you read back from the config
+    // in the future.
+    array_stack.back().size = array_index;
+    array_stack.back().index = array_index;
+}
diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h
new file mode 100644
index 0000000000..f741aa8bba
--- /dev/null
+++ b/src/frontend_common/config.h
@@ -0,0 +1,206 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include "common/settings.h"
+
+#include <SimpleIni.h>
+#include <boost/algorithm/string/replace.hpp>
+
+// Workaround for conflicting definition in libloaderapi.h caused by SimpleIni
+#undef LoadString
+#undef CreateFile
+#undef DeleteFile
+#undef CopyFile
+#undef CreateDirectory
+#undef MoveFile
+
+namespace Core {
+class System;
+}
+
+class Config {
+public:
+    enum class ConfigType {
+        GlobalConfig,
+        PerGameConfig,
+        InputProfile,
+    };
+
+    virtual ~Config() = default;
+
+    void ClearControlPlayerValues() const;
+
+    [[nodiscard]] const std::string& GetConfigFilePath() const;
+
+    [[nodiscard]] bool Exists(const std::string& section, const std::string& key) const;
+
+protected:
+    explicit Config(ConfigType config_type = ConfigType::GlobalConfig);
+
+    void Initialize(const std::string& config_name = "config");
+    void Initialize(std::optional<std::string> config_path);
+
+    void WriteToIni() const;
+
+    void SetUpIni();
+    [[nodiscard]] bool IsCustomConfig() const;
+
+    void Reload();
+    void Save();
+
+    /**
+     * Derived config classes must implement this so they can reload all platform-specific
+     * values and global ones.
+     */
+    virtual void ReloadAllValues() = 0;
+
+    /**
+     * Derived config classes must implement this so they can save all platform-specific
+     * and global values.
+     */
+    virtual void SaveAllValues() = 0;
+
+    void ReadValues();
+    void ReadPlayerValues(std::size_t player_index);
+
+    void ReadTouchscreenValues();
+    void ReadMotionTouchValues();
+
+    // Read functions bases off the respective config section names.
+    void ReadAudioValues();
+    void ReadControlValues();
+    void ReadCoreValues();
+    void ReadDataStorageValues();
+    void ReadDebuggingValues();
+    void ReadServiceValues();
+    void ReadDisabledAddOnValues();
+    void ReadMiscellaneousValues();
+    void ReadCpuValues();
+    void ReadRendererValues();
+    void ReadScreenshotValues();
+    void ReadSystemValues();
+    void ReadWebServiceValues();
+    void ReadNetworkValues();
+
+    // Read platform specific sections
+    virtual void ReadHidbusValues() = 0;
+    virtual void ReadDebugControlValues() = 0;
+    virtual void ReadPathValues() = 0;
+    virtual void ReadShortcutValues() = 0;
+    virtual void ReadUIValues() = 0;
+    virtual void ReadUIGamelistValues() = 0;
+    virtual void ReadUILayoutValues() = 0;
+    virtual void ReadMultiplayerValues() = 0;
+
+    void SaveValues();
+    void SavePlayerValues(std::size_t player_index);
+    void SaveTouchscreenValues();
+    void SaveMotionTouchValues();
+
+    // Save functions based off the respective config section names.
+    void SaveAudioValues();
+    void SaveControlValues();
+    void SaveCoreValues();
+    void SaveDataStorageValues();
+    void SaveDebuggingValues();
+    void SaveNetworkValues();
+    void SaveDisabledAddOnValues();
+    void SaveMiscellaneousValues();
+    void SaveCpuValues();
+    void SaveRendererValues();
+    void SaveScreenshotValues();
+    void SaveSystemValues();
+    void SaveWebServiceValues();
+
+    // Save platform specific sections
+    virtual void SaveHidbusValues() = 0;
+    virtual void SaveDebugControlValues() = 0;
+    virtual void SavePathValues() = 0;
+    virtual void SaveShortcutValues() = 0;
+    virtual void SaveUIValues() = 0;
+    virtual void SaveUIGamelistValues() = 0;
+    virtual void SaveUILayoutValues() = 0;
+    virtual void SaveMultiplayerValues() = 0;
+
+    virtual std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) = 0;
+
+    /**
+     * Reads a setting from the qt_config.
+     *
+     * @param key The setting's identifier
+     * @param default_value The value to use when the setting is not already present in the config
+     */
+    bool ReadBooleanSetting(const std::string& key,
+                            std::optional<bool> default_value = std::nullopt);
+    s64 ReadIntegerSetting(const std::string& key, std::optional<s64> default_value = std::nullopt);
+    double ReadDoubleSetting(const std::string& key,
+                             std::optional<double> default_value = std::nullopt);
+    std::string ReadStringSetting(const std::string& key,
+                                  std::optional<std::string> default_value = std::nullopt);
+
+    /**
+     * Writes a setting to the qt_config.
+     *
+     * @param key The setting's idetentifier
+     * @param value Value of the setting
+     * @param default_value Default of the setting if not present in config
+     * @param use_global Specifies if the custom or global config should be in use, for custom
+     * configs
+     */
+    template <typename Type = int>
+    void WriteSetting(const std::string& key, const Type& value,
+                      const std::optional<Type>& default_value = std::nullopt,
+                      const std::optional<bool>& use_global = std::nullopt);
+    void WriteSettingInternal(const std::string& key, const std::string& value);
+
+    void ReadCategory(Settings::Category category);
+    void WriteCategory(Settings::Category category);
+    void ReadSettingGeneric(Settings::BasicSetting* setting);
+    void WriteSettingGeneric(const Settings::BasicSetting* setting);
+
+    template <typename T>
+    [[nodiscard]] std::string ToString(const T& value_) {
+        if constexpr (std::is_same_v<T, std::string>) {
+            return value_;
+        } else if constexpr (std::is_same_v<T, std::optional<u32>>) {
+            return value_.has_value() ? std::to_string(*value_) : "none";
+        } else if constexpr (std::is_same_v<T, bool>) {
+            return value_ ? "true" : "false";
+        } else {
+            return std::to_string(static_cast<s64>(value_));
+        }
+    }
+
+    void BeginGroup(const std::string& group);
+    void EndGroup();
+    std::string GetSection();
+    [[nodiscard]] std::string GetGroup() const;
+    static std::string AdjustKey(const std::string& key);
+    static std::string AdjustOutputString(const std::string& string);
+    std::string GetFullKey(const std::string& key, bool skipArrayIndex);
+    int BeginArray(const std::string& array);
+    void EndArray();
+    void SetArrayIndex(int index);
+
+    const ConfigType type;
+    std::unique_ptr<CSimpleIniA> config;
+    std::string config_loc;
+    const bool global;
+
+private:
+    inline static std::array<char, 19> special_characters = {'!', '#', '$',  '%',  '^', '&', '*',
+                                                             '|', ';', '\'', '\"', ',', '<', '.',
+                                                             '>', '?', '`',  '~',  '='};
+
+    struct ConfigArray {
+        std::string name;
+        int size;
+        int index;
+    };
+    std::vector<ConfigArray> array_stack;
+    std::vector<std::string> key_stack;
+};
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 33e1fb6639..b251c9045e 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -38,8 +38,6 @@ add_executable(yuzu
     compatdb.ui
     compatibility_list.cpp
     compatibility_list.h
-    configuration/config.cpp
-    configuration/config.h
     configuration/configuration_shared.cpp
     configuration/configuration_shared.h
     configuration/configure.ui
@@ -147,6 +145,8 @@ add_executable(yuzu
     configuration/shared_translation.h
     configuration/shared_widget.cpp
     configuration/shared_widget.h
+    configuration/qt_config.cpp
+    configuration/qt_config.h
     debugger/console.cpp
     debugger/console.h
     debugger/controller.cpp
@@ -344,7 +344,7 @@ endif()
 
 create_target_directory_groups(yuzu)
 
-target_link_libraries(yuzu PRIVATE common core input_common network video_core)
+target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core)
 target_link_libraries(yuzu PRIVATE Boost::headers glad Qt${QT_MAJOR_VERSION}::Widgets)
 target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
 
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
deleted file mode 100644
index c0ae6468b2..0000000000
--- a/src/yuzu/configuration/config.cpp
+++ /dev/null
@@ -1,1309 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <algorithm>
-#include <array>
-#include <QKeySequence>
-#include <QSettings>
-#include "common/fs/fs.h"
-#include "common/fs/path_util.h"
-#include "common/settings.h"
-#include "common/settings_common.h"
-#include "common/settings_enums.h"
-#include "core/core.h"
-#include "core/hle/service/acc/profile_manager.h"
-#include "core/hle/service/hid/controllers/npad.h"
-#include "input_common/main.h"
-#include "network/network.h"
-#include "yuzu/configuration/config.h"
-
-namespace FS = Common::FS;
-
-Config::Config(const std::string& config_name, ConfigType config_type)
-    : type(config_type), global{config_type == ConfigType::GlobalConfig} {
-    Initialize(config_name);
-}
-
-Config::~Config() {
-    if (global) {
-        Save();
-    }
-}
-
-const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
-    Qt::Key_C,    Qt::Key_X, Qt::Key_V,    Qt::Key_Z,  Qt::Key_F,
-    Qt::Key_G,    Qt::Key_Q, Qt::Key_E,    Qt::Key_R,  Qt::Key_T,
-    Qt::Key_M,    Qt::Key_N, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right,
-    Qt::Key_Down, Qt::Key_Q, Qt::Key_E,    0,          0,
-    Qt::Key_Q,    Qt::Key_E,
-};
-
-const std::array<int, Settings::NativeMotion::NumMotions> Config::default_motions = {
-    Qt::Key_7,
-    Qt::Key_8,
-};
-
-const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{
-    {
-        Qt::Key_W,
-        Qt::Key_S,
-        Qt::Key_A,
-        Qt::Key_D,
-    },
-    {
-        Qt::Key_I,
-        Qt::Key_K,
-        Qt::Key_J,
-        Qt::Key_L,
-    },
-}};
-
-const std::array<int, 2> Config::default_stick_mod = {
-    Qt::Key_Shift,
-    0,
-};
-
-const std::array<int, 2> Config::default_ringcon_analogs{{
-    Qt::Key_A,
-    Qt::Key_D,
-}};
-
-const std::map<Settings::AntiAliasing, QString> Config::anti_aliasing_texts_map = {
-    {Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))},
-    {Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))},
-    {Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))},
-};
-
-const std::map<Settings::ScalingFilter, QString> Config::scaling_filter_texts_map = {
-    {Settings::ScalingFilter::NearestNeighbor,
-     QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Nearest"))},
-    {Settings::ScalingFilter::Bilinear,
-     QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bilinear"))},
-    {Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))},
-    {Settings::ScalingFilter::Gaussian,
-     QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))},
-    {Settings::ScalingFilter::ScaleForce,
-     QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))},
-    {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
-};
-
-const std::map<Settings::ConsoleMode, QString> Config::use_docked_mode_texts_map = {
-    {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
-    {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
-};
-
-const std::map<Settings::GpuAccuracy, QString> Config::gpu_accuracy_texts_map = {
-    {Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
-    {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
-    {Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
-};
-
-const std::map<Settings::RendererBackend, QString> Config::renderer_backend_texts_map = {
-    {Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Vulkan"))},
-    {Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "OpenGL"))},
-    {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
-};
-
-const std::map<Settings::ShaderBackend, QString> Config::shader_backend_texts_map = {
-    {Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
-    {Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
-    {Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
-};
-
-// This shouldn't have anything except static initializers (no functions). So
-// QKeySequence(...).toString() is NOT ALLOWED HERE.
-// This must be in alphabetical order according to action name as it must have the same order as
-// UISetting::values.shortcuts, which is alphabetically ordered.
-// clang-format off
-const std::array<UISettings::Shortcut, 23> Config::default_hotkeys{{
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+M"),  QStringLiteral("Home+Dpad_Right"), Qt::WindowShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("-"),       QStringLiteral("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")),          QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("="),       QStringLiteral("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")),       QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+P"),  QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")),   QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F8"),      QStringLiteral("Home+L"), Qt::ApplicationShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")),       QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F10"),     QStringLiteral("Home+X"), Qt::ApplicationShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")),      QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F9"),      QStringLiteral("Home+R"), Qt::ApplicationShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F4"),      QStringLiteral("Home+Plus"), Qt::WindowShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")),          QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Esc"),     QStringLiteral(""), Qt::WindowShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")),                QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+Q"),  QStringLiteral("Home+Minus"), Qt::WindowShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")),               QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"),     QStringLiteral("Home+B"), Qt::WindowShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")),                QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"),  QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")),       QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"),      QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"),      QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")),           QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"),      QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")),               QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")),                QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")),           QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F"),  QStringLiteral(""), Qt::WindowShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")),   QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+U"),  QStringLiteral("Home+Y"), Qt::ApplicationShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")),     QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral(""),        QStringLiteral(""), Qt::ApplicationShortcut, false}},
-    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+S"),  QStringLiteral(""), Qt::WindowShortcut, false}},
-}};
-// clang-format on
-
-void Config::Initialize(const std::string& config_name) {
-    const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
-    const auto config_file = fmt::format("{}.ini", config_name);
-
-    switch (type) {
-    case ConfigType::GlobalConfig:
-        qt_config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
-        void(FS::CreateParentDir(qt_config_loc));
-        qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
-                                                QSettings::IniFormat);
-        Reload();
-        break;
-    case ConfigType::PerGameConfig:
-        qt_config_loc =
-            FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
-        void(FS::CreateParentDir(qt_config_loc));
-        qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
-                                                QSettings::IniFormat);
-        Reload();
-        break;
-    case ConfigType::InputProfile:
-        qt_config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
-        void(FS::CreateParentDir(qt_config_loc));
-        qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
-                                                QSettings::IniFormat);
-        break;
-    }
-}
-
-bool Config::IsCustomConfig() {
-    return type == ConfigType::PerGameConfig;
-}
-
-void Config::ReadPlayerValue(std::size_t player_index) {
-    const QString player_prefix = [this, player_index] {
-        if (type == ConfigType::InputProfile) {
-            return QString{};
-        } else {
-            return QStringLiteral("player_%1_").arg(player_index);
-        }
-    }();
-
-    auto& player = Settings::values.players.GetValue()[player_index];
-    if (IsCustomConfig()) {
-        const auto profile_name =
-            qt_config->value(QStringLiteral("%1profile_name").arg(player_prefix), QString{})
-                .toString()
-                .toStdString();
-        if (profile_name.empty()) {
-            // Use the global input config
-            player = Settings::values.players.GetValue(true)[player_index];
-            return;
-        }
-        player.profile_name = profile_name;
-    }
-
-    if (player_prefix.isEmpty() && Settings::IsConfiguringGlobal()) {
-        const auto controller = static_cast<Settings::ControllerType>(
-            qt_config
-                ->value(QStringLiteral("%1type").arg(player_prefix),
-                        static_cast<u8>(Settings::ControllerType::ProController))
-                .toUInt());
-
-        if (controller == Settings::ControllerType::LeftJoycon ||
-            controller == Settings::ControllerType::RightJoycon) {
-            player.controller_type = controller;
-        }
-    } else {
-        player.connected =
-            ReadSetting(QStringLiteral("%1connected").arg(player_prefix), player_index == 0)
-                .toBool();
-
-        player.controller_type = static_cast<Settings::ControllerType>(
-            qt_config
-                ->value(QStringLiteral("%1type").arg(player_prefix),
-                        static_cast<u8>(Settings::ControllerType::ProController))
-                .toUInt());
-
-        player.vibration_enabled =
-            qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true)
-                .toBool();
-
-        player.vibration_strength =
-            qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100)
-                .toInt();
-
-        player.body_color_left = qt_config
-                                     ->value(QStringLiteral("%1body_color_left").arg(player_prefix),
-                                             Settings::JOYCON_BODY_NEON_BLUE)
-                                     .toUInt();
-        player.body_color_right =
-            qt_config
-                ->value(QStringLiteral("%1body_color_right").arg(player_prefix),
-                        Settings::JOYCON_BODY_NEON_RED)
-                .toUInt();
-        player.button_color_left =
-            qt_config
-                ->value(QStringLiteral("%1button_color_left").arg(player_prefix),
-                        Settings::JOYCON_BUTTONS_NEON_BLUE)
-                .toUInt();
-        player.button_color_right =
-            qt_config
-                ->value(QStringLiteral("%1button_color_right").arg(player_prefix),
-                        Settings::JOYCON_BUTTONS_NEON_RED)
-                .toUInt();
-    }
-
-    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
-        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
-        auto& player_buttons = player.buttons[i];
-
-        player_buttons = qt_config
-                             ->value(QStringLiteral("%1").arg(player_prefix) +
-                                         QString::fromUtf8(Settings::NativeButton::mapping[i]),
-                                     QString::fromStdString(default_param))
-                             .toString()
-                             .toStdString();
-        if (player_buttons.empty()) {
-            player_buttons = default_param;
-        }
-    }
-
-    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
-        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
-            default_analogs[i][3], default_stick_mod[i], 0.5f);
-        auto& player_analogs = player.analogs[i];
-
-        player_analogs = qt_config
-                             ->value(QStringLiteral("%1").arg(player_prefix) +
-                                         QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
-                                     QString::fromStdString(default_param))
-                             .toString()
-                             .toStdString();
-        if (player_analogs.empty()) {
-            player_analogs = default_param;
-        }
-    }
-
-    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
-        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
-        auto& player_motions = player.motions[i];
-
-        player_motions = qt_config
-                             ->value(QStringLiteral("%1").arg(player_prefix) +
-                                         QString::fromUtf8(Settings::NativeMotion::mapping[i]),
-                                     QString::fromStdString(default_param))
-                             .toString()
-                             .toStdString();
-        if (player_motions.empty()) {
-            player_motions = default_param;
-        }
-    }
-}
-
-void Config::ReadDebugValues() {
-    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
-        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
-        auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
-
-        debug_pad_buttons = qt_config
-                                ->value(QStringLiteral("debug_pad_") +
-                                            QString::fromUtf8(Settings::NativeButton::mapping[i]),
-                                        QString::fromStdString(default_param))
-                                .toString()
-                                .toStdString();
-        if (debug_pad_buttons.empty()) {
-            debug_pad_buttons = default_param;
-        }
-    }
-
-    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
-        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
-            default_analogs[i][3], default_stick_mod[i], 0.5f);
-        auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
-
-        debug_pad_analogs = qt_config
-                                ->value(QStringLiteral("debug_pad_") +
-                                            QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
-                                        QString::fromStdString(default_param))
-                                .toString()
-                                .toStdString();
-        if (debug_pad_analogs.empty()) {
-            debug_pad_analogs = default_param;
-        }
-    }
-}
-
-void Config::ReadTouchscreenValues() {
-    Settings::values.touchscreen.enabled =
-        ReadSetting(QStringLiteral("touchscreen_enabled"), true).toBool();
-
-    Settings::values.touchscreen.rotation_angle =
-        ReadSetting(QStringLiteral("touchscreen_angle"), 0).toUInt();
-    Settings::values.touchscreen.diameter_x =
-        ReadSetting(QStringLiteral("touchscreen_diameter_x"), 15).toUInt();
-    Settings::values.touchscreen.diameter_y =
-        ReadSetting(QStringLiteral("touchscreen_diameter_y"), 15).toUInt();
-}
-
-void Config::ReadHidbusValues() {
-    const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-        0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
-    auto& ringcon_analogs = Settings::values.ringcon_analogs;
-
-    ringcon_analogs =
-        qt_config->value(QStringLiteral("ring_controller"), QString::fromStdString(default_param))
-            .toString()
-            .toStdString();
-    if (ringcon_analogs.empty()) {
-        ringcon_analogs = default_param;
-    }
-}
-
-void Config::ReadAudioValues() {
-    qt_config->beginGroup(QStringLiteral("Audio"));
-
-    ReadCategory(Settings::Category::Audio);
-    ReadCategory(Settings::Category::UiAudio);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadControlValues() {
-    qt_config->beginGroup(QStringLiteral("Controls"));
-
-    ReadCategory(Settings::Category::Controls);
-
-    Settings::values.players.SetGlobal(!IsCustomConfig());
-    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
-        ReadPlayerValue(p);
-    }
-
-    // Disable docked mode if handheld is selected
-    const auto controller_type = Settings::values.players.GetValue()[0].controller_type;
-    if (controller_type == Settings::ControllerType::Handheld) {
-        Settings::values.use_docked_mode.SetGlobal(!IsCustomConfig());
-        Settings::values.use_docked_mode.SetValue(Settings::ConsoleMode::Handheld);
-    }
-
-    if (IsCustomConfig()) {
-        qt_config->endGroup();
-        return;
-    }
-    ReadDebugValues();
-    ReadTouchscreenValues();
-    ReadMotionTouchValues();
-    ReadHidbusValues();
-
-    qt_config->endGroup();
-}
-
-void Config::ReadMotionTouchValues() {
-    int num_touch_from_button_maps =
-        qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
-
-    if (num_touch_from_button_maps > 0) {
-        const auto append_touch_from_button_map = [this] {
-            Settings::TouchFromButtonMap map;
-            map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default"))
-                           .toString()
-                           .toStdString();
-            const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries"));
-            map.buttons.reserve(num_touch_maps);
-            for (int i = 0; i < num_touch_maps; i++) {
-                qt_config->setArrayIndex(i);
-                std::string touch_mapping =
-                    ReadSetting(QStringLiteral("bind")).toString().toStdString();
-                map.buttons.emplace_back(std::move(touch_mapping));
-            }
-            qt_config->endArray(); // entries
-            Settings::values.touch_from_button_maps.emplace_back(std::move(map));
-        };
-
-        for (int i = 0; i < num_touch_from_button_maps; ++i) {
-            qt_config->setArrayIndex(i);
-            append_touch_from_button_map();
-        }
-    } else {
-        Settings::values.touch_from_button_maps.emplace_back(
-            Settings::TouchFromButtonMap{"default", {}});
-        num_touch_from_button_maps = 1;
-    }
-    qt_config->endArray();
-
-    Settings::values.touch_from_button_map_index = std::clamp(
-        Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
-}
-
-void Config::ReadCoreValues() {
-    qt_config->beginGroup(QStringLiteral("Core"));
-
-    ReadCategory(Settings::Category::Core);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadDataStorageValues() {
-    qt_config->beginGroup(QStringLiteral("Data Storage"));
-
-    FS::SetYuzuPath(
-        FS::YuzuPath::NANDDir,
-        qt_config
-            ->value(QStringLiteral("nand_directory"),
-                    QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)))
-            .toString()
-            .toStdString());
-    FS::SetYuzuPath(
-        FS::YuzuPath::SDMCDir,
-        qt_config
-            ->value(QStringLiteral("sdmc_directory"),
-                    QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)))
-            .toString()
-            .toStdString());
-    FS::SetYuzuPath(
-        FS::YuzuPath::LoadDir,
-        qt_config
-            ->value(QStringLiteral("load_directory"),
-                    QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)))
-            .toString()
-            .toStdString());
-    FS::SetYuzuPath(
-        FS::YuzuPath::DumpDir,
-        qt_config
-            ->value(QStringLiteral("dump_directory"),
-                    QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)))
-            .toString()
-            .toStdString());
-    FS::SetYuzuPath(FS::YuzuPath::TASDir,
-                    qt_config
-                        ->value(QStringLiteral("tas_directory"),
-                                QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)))
-                        .toString()
-                        .toStdString());
-
-    ReadCategory(Settings::Category::DataStorage);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadDebuggingValues() {
-    qt_config->beginGroup(QStringLiteral("Debugging"));
-
-    // 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();
-
-    ReadCategory(Settings::Category::Debugging);
-    ReadCategory(Settings::Category::DebuggingGraphics);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadServiceValues() {
-    qt_config->beginGroup(QStringLiteral("Services"));
-
-    ReadCategory(Settings::Category::Services);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadDisabledAddOnValues() {
-    const auto size = qt_config->beginReadArray(QStringLiteral("DisabledAddOns"));
-
-    for (int i = 0; i < size; ++i) {
-        qt_config->setArrayIndex(i);
-        const auto title_id = ReadSetting(QStringLiteral("title_id"), 0).toULongLong();
-        std::vector<std::string> out;
-        const auto d_size = qt_config->beginReadArray(QStringLiteral("disabled"));
-        for (int j = 0; j < d_size; ++j) {
-            qt_config->setArrayIndex(j);
-            out.push_back(ReadSetting(QStringLiteral("d"), QString{}).toString().toStdString());
-        }
-        qt_config->endArray();
-        Settings::values.disabled_addons.insert_or_assign(title_id, out);
-    }
-
-    qt_config->endArray();
-}
-
-void Config::ReadMiscellaneousValues() {
-    qt_config->beginGroup(QStringLiteral("Miscellaneous"));
-
-    ReadCategory(Settings::Category::Miscellaneous);
-
-    qt_config->endGroup();
-}
-
-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.game_dir_deprecated =
-        ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
-    UISettings::values.game_dir_deprecated_deepscan =
-        ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
-    const int gamedirs_size = qt_config->beginReadArray(QStringLiteral("gamedirs"));
-    for (int i = 0; i < gamedirs_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("SDMC");
-        game_dir.expanded = true;
-        UISettings::values.game_dirs.append(game_dir);
-        game_dir.path = QStringLiteral("UserNAND");
-        UISettings::values.game_dirs.append(game_dir);
-        game_dir.path = QStringLiteral("SysNAND");
-        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();
-
-    qt_config->endGroup();
-}
-
-void Config::ReadCpuValues() {
-    qt_config->beginGroup(QStringLiteral("Cpu"));
-
-    ReadCategory(Settings::Category::Cpu);
-    ReadCategory(Settings::Category::CpuDebug);
-    ReadCategory(Settings::Category::CpuUnsafe);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadRendererValues() {
-    qt_config->beginGroup(QStringLiteral("Renderer"));
-
-    ReadCategory(Settings::Category::Renderer);
-    ReadCategory(Settings::Category::RendererAdvanced);
-    ReadCategory(Settings::Category::RendererDebug);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadScreenshotValues() {
-    qt_config->beginGroup(QStringLiteral("Screenshots"));
-
-    ReadCategory(Settings::Category::Screenshots);
-    FS::SetYuzuPath(
-        FS::YuzuPath::ScreenshotsDir,
-        qt_config
-            ->value(QStringLiteral("screenshot_path"),
-                    QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)))
-            .toString()
-            .toStdString());
-
-    qt_config->endGroup();
-}
-
-void Config::ReadShortcutValues() {
-    qt_config->beginGroup(QStringLiteral("Shortcuts"));
-
-    for (const auto& [name, group, shortcut] : default_hotkeys) {
-        qt_config->beginGroup(group);
-        qt_config->beginGroup(name);
-        // No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1
-        // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
-        // a file dialog in windowed mode
-        UISettings::values.shortcuts.push_back(
-            {name,
-             group,
-             {ReadSetting(QStringLiteral("KeySeq"), shortcut.keyseq).toString(),
-              ReadSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq)
-                  .toString(),
-              shortcut.context, ReadSetting(QStringLiteral("Repeat"), shortcut.repeat).toBool()}});
-        qt_config->endGroup();
-        qt_config->endGroup();
-    }
-
-    qt_config->endGroup();
-}
-
-void Config::ReadSystemValues() {
-    qt_config->beginGroup(QStringLiteral("System"));
-
-    ReadCategory(Settings::Category::System);
-    ReadCategory(Settings::Category::SystemAudio);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadUIValues() {
-    qt_config->beginGroup(QStringLiteral("UI"));
-
-    UISettings::values.theme =
-        ReadSetting(
-            QStringLiteral("theme"),
-            QString::fromUtf8(UISettings::themes[static_cast<size_t>(default_theme)].second))
-            .toString();
-
-    ReadUIGamelistValues();
-    ReadUILayoutValues();
-    ReadPathValues();
-    ReadScreenshotValues();
-    ReadShortcutValues();
-    ReadMultiplayerValues();
-
-    ReadCategory(Settings::Category::Ui);
-    ReadCategory(Settings::Category::UiGeneral);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadUIGamelistValues() {
-    qt_config->beginGroup(QStringLiteral("UIGameList"));
-
-    ReadCategory(Settings::Category::UiGameList);
-
-    const int favorites_size = qt_config->beginReadArray(QStringLiteral("favorites"));
-    for (int i = 0; i < favorites_size; i++) {
-        qt_config->setArrayIndex(i);
-        UISettings::values.favorited_ids.append(
-            ReadSetting(QStringLiteral("program_id")).toULongLong());
-    }
-    qt_config->endArray();
-
-    qt_config->endGroup();
-}
-
-void Config::ReadUILayoutValues() {
-    qt_config->beginGroup(QStringLiteral("UILayout"));
-
-    UISettings::values.geometry = ReadSetting(QStringLiteral("geometry")).toByteArray();
-    UISettings::values.state = ReadSetting(QStringLiteral("state")).toByteArray();
-    UISettings::values.renderwindow_geometry =
-        ReadSetting(QStringLiteral("geometryRenderWindow")).toByteArray();
-    UISettings::values.gamelist_header_state =
-        ReadSetting(QStringLiteral("gameListHeaderState")).toByteArray();
-    UISettings::values.microprofile_geometry =
-        ReadSetting(QStringLiteral("microProfileDialogGeometry")).toByteArray();
-
-    ReadCategory(Settings::Category::UiLayout);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadWebServiceValues() {
-    qt_config->beginGroup(QStringLiteral("WebService"));
-
-    ReadCategory(Settings::Category::WebService);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadMultiplayerValues() {
-    qt_config->beginGroup(QStringLiteral("Multiplayer"));
-
-    ReadCategory(Settings::Category::Multiplayer);
-
-    // Read ban list back
-    int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
-    UISettings::values.multiplayer_ban_list.first.resize(size);
-    for (int i = 0; i < size; ++i) {
-        qt_config->setArrayIndex(i);
-        UISettings::values.multiplayer_ban_list.first[i] =
-            ReadSetting(QStringLiteral("username")).toString().toStdString();
-    }
-    qt_config->endArray();
-    size = qt_config->beginReadArray(QStringLiteral("ip_ban_list"));
-    UISettings::values.multiplayer_ban_list.second.resize(size);
-    for (int i = 0; i < size; ++i) {
-        qt_config->setArrayIndex(i);
-        UISettings::values.multiplayer_ban_list.second[i] =
-            ReadSetting(QStringLiteral("ip")).toString().toStdString();
-    }
-    qt_config->endArray();
-
-    qt_config->endGroup();
-}
-
-void Config::ReadNetworkValues() {
-    qt_config->beginGroup(QString::fromStdString("Services"));
-
-    ReadCategory(Settings::Category::Network);
-
-    qt_config->endGroup();
-}
-
-void Config::ReadValues() {
-    if (global) {
-        ReadDataStorageValues();
-        ReadDebuggingValues();
-        ReadDisabledAddOnValues();
-        ReadNetworkValues();
-        ReadServiceValues();
-        ReadUIValues();
-        ReadWebServiceValues();
-        ReadMiscellaneousValues();
-    }
-    ReadControlValues();
-    ReadCoreValues();
-    ReadCpuValues();
-    ReadRendererValues();
-    ReadAudioValues();
-    ReadSystemValues();
-}
-
-void Config::SavePlayerValue(std::size_t player_index) {
-    const QString player_prefix = [this, player_index] {
-        if (type == ConfigType::InputProfile) {
-            return QString{};
-        } else {
-            return QStringLiteral("player_%1_").arg(player_index);
-        }
-    }();
-
-    const auto& player = Settings::values.players.GetValue()[player_index];
-    if (IsCustomConfig()) {
-        if (player.profile_name.empty()) {
-            // No custom profile selected
-            return;
-        }
-        WriteSetting(QStringLiteral("%1profile_name").arg(player_prefix),
-                     QString::fromStdString(player.profile_name), QString{});
-    }
-
-    WriteSetting(QStringLiteral("%1type").arg(player_prefix),
-                 static_cast<u8>(player.controller_type),
-                 static_cast<u8>(Settings::ControllerType::ProController));
-
-    if (!player_prefix.isEmpty() || !Settings::IsConfiguringGlobal()) {
-        WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected,
-                     player_index == 0);
-        WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix),
-                     player.vibration_enabled, true);
-        WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix),
-                     player.vibration_strength, 100);
-        WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left,
-                     Settings::JOYCON_BODY_NEON_BLUE);
-        WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix),
-                     player.body_color_right, Settings::JOYCON_BODY_NEON_RED);
-        WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix),
-                     player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE);
-        WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix),
-                     player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);
-    }
-
-    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
-        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
-        WriteSetting(QStringLiteral("%1").arg(player_prefix) +
-                         QString::fromStdString(Settings::NativeButton::mapping[i]),
-                     QString::fromStdString(player.buttons[i]),
-                     QString::fromStdString(default_param));
-    }
-    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
-        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
-            default_analogs[i][3], default_stick_mod[i], 0.5f);
-        WriteSetting(QStringLiteral("%1").arg(player_prefix) +
-                         QString::fromStdString(Settings::NativeAnalog::mapping[i]),
-                     QString::fromStdString(player.analogs[i]),
-                     QString::fromStdString(default_param));
-    }
-    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
-        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
-        WriteSetting(QStringLiteral("%1").arg(player_prefix) +
-                         QString::fromStdString(Settings::NativeMotion::mapping[i]),
-                     QString::fromStdString(player.motions[i]),
-                     QString::fromStdString(default_param));
-    }
-}
-
-void Config::SaveDebugValues() {
-    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
-        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
-        WriteSetting(QStringLiteral("debug_pad_") +
-                         QString::fromStdString(Settings::NativeButton::mapping[i]),
-                     QString::fromStdString(Settings::values.debug_pad_buttons[i]),
-                     QString::fromStdString(default_param));
-    }
-    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
-        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
-            default_analogs[i][3], default_stick_mod[i], 0.5f);
-        WriteSetting(QStringLiteral("debug_pad_") +
-                         QString::fromStdString(Settings::NativeAnalog::mapping[i]),
-                     QString::fromStdString(Settings::values.debug_pad_analogs[i]),
-                     QString::fromStdString(default_param));
-    }
-}
-
-void Config::SaveTouchscreenValues() {
-    const auto& touchscreen = Settings::values.touchscreen;
-
-    WriteSetting(QStringLiteral("touchscreen_enabled"), touchscreen.enabled, true);
-
-    WriteSetting(QStringLiteral("touchscreen_angle"), touchscreen.rotation_angle, 0);
-    WriteSetting(QStringLiteral("touchscreen_diameter_x"), touchscreen.diameter_x, 15);
-    WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
-}
-
-void Config::SaveMotionTouchValues() {
-    qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
-    for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
-        qt_config->setArrayIndex(static_cast<int>(p));
-        WriteSetting(QStringLiteral("name"),
-                     QString::fromStdString(Settings::values.touch_from_button_maps[p].name),
-                     QStringLiteral("default"));
-        qt_config->beginWriteArray(QStringLiteral("entries"));
-        for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
-             ++q) {
-            qt_config->setArrayIndex(static_cast<int>(q));
-            WriteSetting(
-                QStringLiteral("bind"),
-                QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q]));
-        }
-        qt_config->endArray();
-    }
-    qt_config->endArray();
-}
-
-void Config::SaveHidbusValues() {
-    const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-        0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
-    WriteSetting(QStringLiteral("ring_controller"),
-                 QString::fromStdString(Settings::values.ringcon_analogs),
-                 QString::fromStdString(default_param));
-}
-
-void Config::SaveValues() {
-    if (global) {
-        SaveDataStorageValues();
-        SaveDebuggingValues();
-        SaveDisabledAddOnValues();
-        SaveNetworkValues();
-        SaveUIValues();
-        SaveWebServiceValues();
-        SaveMiscellaneousValues();
-    }
-    SaveControlValues();
-    SaveCoreValues();
-    SaveCpuValues();
-    SaveRendererValues();
-    SaveAudioValues();
-    SaveSystemValues();
-
-    qt_config->sync();
-}
-
-void Config::SaveAudioValues() {
-    qt_config->beginGroup(QStringLiteral("Audio"));
-
-    WriteCategory(Settings::Category::Audio);
-    WriteCategory(Settings::Category::UiAudio);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveControlValues() {
-    qt_config->beginGroup(QStringLiteral("Controls"));
-
-    WriteCategory(Settings::Category::Controls);
-
-    Settings::values.players.SetGlobal(!IsCustomConfig());
-    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
-        SavePlayerValue(p);
-    }
-    if (IsCustomConfig()) {
-        qt_config->endGroup();
-        return;
-    }
-    SaveDebugValues();
-    SaveTouchscreenValues();
-    SaveMotionTouchValues();
-    SaveHidbusValues();
-
-    qt_config->endGroup();
-}
-
-void Config::SaveCoreValues() {
-    qt_config->beginGroup(QStringLiteral("Core"));
-
-    WriteCategory(Settings::Category::Core);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveDataStorageValues() {
-    qt_config->beginGroup(QStringLiteral("Data Storage"));
-
-    WriteSetting(QStringLiteral("nand_directory"),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
-    WriteSetting(QStringLiteral("sdmc_directory"),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
-    WriteSetting(QStringLiteral("load_directory"),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
-    WriteSetting(QStringLiteral("dump_directory"),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
-    WriteSetting(QStringLiteral("tas_directory"),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
-
-    WriteCategory(Settings::Category::DataStorage);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveDebuggingValues() {
-    qt_config->beginGroup(QStringLiteral("Debugging"));
-
-    // 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);
-
-    WriteCategory(Settings::Category::Debugging);
-    WriteCategory(Settings::Category::DebuggingGraphics);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveNetworkValues() {
-    qt_config->beginGroup(QStringLiteral("Services"));
-
-    WriteCategory(Settings::Category::Network);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveDisabledAddOnValues() {
-    qt_config->beginWriteArray(QStringLiteral("DisabledAddOns"));
-
-    int i = 0;
-    for (const auto& elem : Settings::values.disabled_addons) {
-        qt_config->setArrayIndex(i);
-        WriteSetting(QStringLiteral("title_id"), QVariant::fromValue<u64>(elem.first), 0);
-        qt_config->beginWriteArray(QStringLiteral("disabled"));
-        for (std::size_t j = 0; j < elem.second.size(); ++j) {
-            qt_config->setArrayIndex(static_cast<int>(j));
-            WriteSetting(QStringLiteral("d"), QString::fromStdString(elem.second[j]), QString{});
-        }
-        qt_config->endArray();
-        ++i;
-    }
-
-    qt_config->endArray();
-}
-
-void Config::SaveMiscellaneousValues() {
-    qt_config->beginGroup(QStringLiteral("Miscellaneous"));
-
-    WriteCategory(Settings::Category::Miscellaneous);
-
-    qt_config->endGroup();
-}
-
-void Config::SavePathValues() {
-    qt_config->beginGroup(QStringLiteral("Paths"));
-
-    WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
-    WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_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->endGroup();
-}
-
-void Config::SaveCpuValues() {
-    qt_config->beginGroup(QStringLiteral("Cpu"));
-
-    WriteCategory(Settings::Category::Cpu);
-    WriteCategory(Settings::Category::CpuDebug);
-    WriteCategory(Settings::Category::CpuUnsafe);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveRendererValues() {
-    qt_config->beginGroup(QStringLiteral("Renderer"));
-
-    WriteCategory(Settings::Category::Renderer);
-    WriteCategory(Settings::Category::RendererAdvanced);
-    WriteCategory(Settings::Category::RendererDebug);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveScreenshotValues() {
-    qt_config->beginGroup(QStringLiteral("Screenshots"));
-
-    WriteSetting(QStringLiteral("screenshot_path"),
-                 QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir)));
-    WriteCategory(Settings::Category::Screenshots);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveShortcutValues() {
-    qt_config->beginGroup(QStringLiteral("Shortcuts"));
-
-    // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
-    // However, their ordering must also be the same.
-    for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
-        const auto& [name, group, shortcut] = UISettings::values.shortcuts[i];
-        const auto& default_hotkey = default_hotkeys[i].shortcut;
-
-        qt_config->beginGroup(group);
-        qt_config->beginGroup(name);
-        WriteSetting(QStringLiteral("KeySeq"), shortcut.keyseq, default_hotkey.keyseq);
-        WriteSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq,
-                     default_hotkey.controller_keyseq);
-        WriteSetting(QStringLiteral("Context"), shortcut.context, default_hotkey.context);
-        WriteSetting(QStringLiteral("Repeat"), shortcut.repeat, default_hotkey.repeat);
-        qt_config->endGroup();
-        qt_config->endGroup();
-    }
-
-    qt_config->endGroup();
-}
-
-void Config::SaveSystemValues() {
-    qt_config->beginGroup(QStringLiteral("System"));
-
-    WriteCategory(Settings::Category::System);
-    WriteCategory(Settings::Category::SystemAudio);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveUIValues() {
-    qt_config->beginGroup(QStringLiteral("UI"));
-
-    WriteCategory(Settings::Category::Ui);
-    WriteCategory(Settings::Category::UiGeneral);
-
-    WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
-                 QString::fromUtf8(UISettings::themes[static_cast<size_t>(default_theme)].second));
-
-    SaveUIGamelistValues();
-    SaveUILayoutValues();
-    SavePathValues();
-    SaveScreenshotValues();
-    SaveShortcutValues();
-    SaveMultiplayerValues();
-
-    qt_config->endGroup();
-}
-
-void Config::SaveUIGamelistValues() {
-    qt_config->beginGroup(QStringLiteral("UIGameList"));
-
-    WriteCategory(Settings::Category::UiGameList);
-
-    qt_config->beginWriteArray(QStringLiteral("favorites"));
-    for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
-        qt_config->setArrayIndex(i);
-        WriteSetting(QStringLiteral("program_id"),
-                     QVariant::fromValue(UISettings::values.favorited_ids[i]));
-    }
-    qt_config->endArray();
-
-    qt_config->endGroup();
-}
-
-void Config::SaveUILayoutValues() {
-    qt_config->beginGroup(QStringLiteral("UILayout"));
-
-    WriteSetting(QStringLiteral("geometry"), UISettings::values.geometry);
-    WriteSetting(QStringLiteral("state"), UISettings::values.state);
-    WriteSetting(QStringLiteral("geometryRenderWindow"), UISettings::values.renderwindow_geometry);
-    WriteSetting(QStringLiteral("gameListHeaderState"), UISettings::values.gamelist_header_state);
-    WriteSetting(QStringLiteral("microProfileDialogGeometry"),
-                 UISettings::values.microprofile_geometry);
-
-    WriteCategory(Settings::Category::UiLayout);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveWebServiceValues() {
-    qt_config->beginGroup(QStringLiteral("WebService"));
-
-    WriteCategory(Settings::Category::WebService);
-
-    qt_config->endGroup();
-}
-
-void Config::SaveMultiplayerValues() {
-    qt_config->beginGroup(QStringLiteral("Multiplayer"));
-
-    WriteCategory(Settings::Category::Multiplayer);
-
-    // Write ban list
-    qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
-    for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
-        qt_config->setArrayIndex(static_cast<int>(i));
-        WriteSetting(QStringLiteral("username"),
-                     QString::fromStdString(UISettings::values.multiplayer_ban_list.first[i]));
-    }
-    qt_config->endArray();
-    qt_config->beginWriteArray(QStringLiteral("ip_ban_list"));
-    for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
-        qt_config->setArrayIndex(static_cast<int>(i));
-        WriteSetting(QStringLiteral("ip"),
-                     QString::fromStdString(UISettings::values.multiplayer_ban_list.second[i]));
-    }
-    qt_config->endArray();
-
-    qt_config->endGroup();
-}
-
-QVariant Config::ReadSetting(const QString& name) const {
-    return qt_config->value(name);
-}
-
-QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {
-    QVariant result;
-    if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) {
-        result = default_value;
-    } else {
-        result = qt_config->value(name, default_value);
-    }
-    return result;
-}
-
-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) {
-    qt_config->setValue(name + QStringLiteral("/default"), value == default_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();
-}
-
-void Config::Save() {
-    SaveValues();
-}
-
-void Config::ReadControlPlayerValue(std::size_t player_index) {
-    qt_config->beginGroup(QStringLiteral("Controls"));
-    ReadPlayerValue(player_index);
-    qt_config->endGroup();
-}
-
-void Config::SaveControlPlayerValue(std::size_t player_index) {
-    qt_config->beginGroup(QStringLiteral("Controls"));
-    SavePlayerValue(player_index);
-    qt_config->endGroup();
-}
-
-void Config::ClearControlPlayerValues() {
-    qt_config->beginGroup(QStringLiteral("Controls"));
-    // If key is an empty string, all keys in the current group() are removed.
-    qt_config->remove(QString{});
-    qt_config->endGroup();
-}
-
-const std::string& Config::GetConfigFilePath() const {
-    return qt_config_loc;
-}
-
-static auto FindRelevantList(Settings::Category category) {
-    auto& map = Settings::values.linkage.by_category;
-    if (map.contains(category)) {
-        return Settings::values.linkage.by_category[category];
-    }
-    return UISettings::values.linkage.by_category[category];
-}
-
-void Config::ReadCategory(Settings::Category category) {
-    const auto& settings = FindRelevantList(category);
-    std::for_each(settings.begin(), settings.end(),
-                  [&](const auto& setting) { ReadSettingGeneric(setting); });
-}
-
-void Config::WriteCategory(Settings::Category category) {
-    const auto& settings = FindRelevantList(category);
-    std::for_each(settings.begin(), settings.end(),
-                  [&](const auto& setting) { WriteSettingGeneric(setting); });
-}
-
-void Config::ReadSettingGeneric(Settings::BasicSetting* const setting) {
-    if (!setting->Save() || (!setting->Switchable() && !global)) {
-        return;
-    }
-    const QString name = QString::fromStdString(setting->GetLabel());
-    const auto default_value =
-        QVariant::fromValue<QString>(QString::fromStdString(setting->DefaultToString()));
-
-    bool use_global = true;
-    if (setting->Switchable() && !global) {
-        use_global = qt_config->value(name + QStringLiteral("/use_global"), true).value<bool>();
-        setting->SetGlobal(use_global);
-    }
-
-    if (global || !use_global) {
-        const bool is_default =
-            qt_config->value(name + QStringLiteral("/default"), true).value<bool>();
-        if (!is_default) {
-            setting->LoadString(
-                qt_config->value(name, default_value).value<QString>().toStdString());
-        } else {
-            // Empty string resets the Setting to default
-            setting->LoadString("");
-        }
-    }
-}
-
-void Config::WriteSettingGeneric(Settings::BasicSetting* const setting) const {
-    if (!setting->Save()) {
-        return;
-    }
-    const QVariant value = QVariant::fromValue(QString::fromStdString(setting->ToString()));
-    const QVariant default_value =
-        QVariant::fromValue(QString::fromStdString(setting->DefaultToString()));
-    const QString label = QString::fromStdString(setting->GetLabel());
-    if (setting->Switchable()) {
-        if (!global) {
-            qt_config->setValue(label + QStringLiteral("/use_global"), setting->UsingGlobal());
-        }
-        if (global || !setting->UsingGlobal()) {
-            qt_config->setValue(label + QStringLiteral("/default"), value == default_value);
-            qt_config->setValue(label, value);
-        }
-    } else if (global) {
-        qt_config->setValue(label + QStringLiteral("/default"), value == default_value);
-        qt_config->setValue(label, value);
-    }
-}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
deleted file mode 100644
index 1589ba0577..0000000000
--- a/src/yuzu/configuration/config.h
+++ /dev/null
@@ -1,179 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <memory>
-#include <string>
-#include <QMetaType>
-#include <QVariant>
-#include "common/settings.h"
-#include "common/settings_enums.h"
-#include "yuzu/uisettings.h"
-
-class QSettings;
-
-namespace Core {
-class System;
-}
-
-class Config {
-public:
-    enum class ConfigType {
-        GlobalConfig,
-        PerGameConfig,
-        InputProfile,
-    };
-
-    explicit Config(const std::string& config_name = "qt-config",
-                    ConfigType config_type = ConfigType::GlobalConfig);
-    ~Config();
-
-    void Reload();
-    void Save();
-
-    void ReadControlPlayerValue(std::size_t player_index);
-    void SaveControlPlayerValue(std::size_t player_index);
-    void ClearControlPlayerValues();
-
-    const std::string& GetConfigFilePath() const;
-
-    static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
-    static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
-    static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
-    static const std::array<int, 2> default_stick_mod;
-    static const std::array<int, 2> default_ringcon_analogs;
-    static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
-        default_mouse_buttons;
-    static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
-    static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
-    static const std::array<UISettings::Shortcut, 23> default_hotkeys;
-
-    static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map;
-    static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map;
-    static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map;
-    static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map;
-    static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map;
-    static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map;
-
-    static constexpr UISettings::Theme default_theme{
-#ifdef _WIN32
-        UISettings::Theme::DarkColorful
-#else
-        UISettings::Theme::DefaultColorful
-#endif
-    };
-
-private:
-    void Initialize(const std::string& config_name);
-    bool IsCustomConfig();
-
-    void ReadValues();
-    void ReadPlayerValue(std::size_t player_index);
-    void ReadDebugValues();
-    void ReadKeyboardValues();
-    void ReadMouseValues();
-    void ReadTouchscreenValues();
-    void ReadMotionTouchValues();
-    void ReadHidbusValues();
-    void ReadIrCameraValues();
-
-    // Read functions bases off the respective config section names.
-    void ReadAudioValues();
-    void ReadControlValues();
-    void ReadCoreValues();
-    void ReadDataStorageValues();
-    void ReadDebuggingValues();
-    void ReadServiceValues();
-    void ReadDisabledAddOnValues();
-    void ReadMiscellaneousValues();
-    void ReadPathValues();
-    void ReadCpuValues();
-    void ReadRendererValues();
-    void ReadScreenshotValues();
-    void ReadShortcutValues();
-    void ReadSystemValues();
-    void ReadUIValues();
-    void ReadUIGamelistValues();
-    void ReadUILayoutValues();
-    void ReadWebServiceValues();
-    void ReadMultiplayerValues();
-    void ReadNetworkValues();
-
-    void SaveValues();
-    void SavePlayerValue(std::size_t player_index);
-    void SaveDebugValues();
-    void SaveMouseValues();
-    void SaveTouchscreenValues();
-    void SaveMotionTouchValues();
-    void SaveHidbusValues();
-    void SaveIrCameraValues();
-
-    // Save functions based off the respective config section names.
-    void SaveAudioValues();
-    void SaveControlValues();
-    void SaveCoreValues();
-    void SaveDataStorageValues();
-    void SaveDebuggingValues();
-    void SaveNetworkValues();
-    void SaveDisabledAddOnValues();
-    void SaveMiscellaneousValues();
-    void SavePathValues();
-    void SaveCpuValues();
-    void SaveRendererValues();
-    void SaveScreenshotValues();
-    void SaveShortcutValues();
-    void SaveSystemValues();
-    void SaveUIValues();
-    void SaveUIGamelistValues();
-    void SaveUILayoutValues();
-    void SaveWebServiceValues();
-    void SaveMultiplayerValues();
-
-    /**
-     * 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;
-
-    /**
-     * 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);
-
-    void ReadCategory(Settings::Category category);
-    void WriteCategory(Settings::Category category);
-    void ReadSettingGeneric(Settings::BasicSetting* const setting);
-    void WriteSettingGeneric(Settings::BasicSetting* const setting) const;
-
-    const ConfigType type;
-    std::unique_ptr<QSettings> qt_config;
-    std::string qt_config_loc;
-    const bool global;
-};
-
-// These metatype declarations cannot be in common/settings.h because core is devoid of QT
-Q_DECLARE_METATYPE(Settings::CpuAccuracy);
-Q_DECLARE_METATYPE(Settings::GpuAccuracy);
-Q_DECLARE_METATYPE(Settings::FullscreenMode);
-Q_DECLARE_METATYPE(Settings::NvdecEmulation);
-Q_DECLARE_METATYPE(Settings::ResolutionSetup);
-Q_DECLARE_METATYPE(Settings::ScalingFilter);
-Q_DECLARE_METATYPE(Settings::AntiAliasing);
-Q_DECLARE_METATYPE(Settings::RendererBackend);
-Q_DECLARE_METATYPE(Settings::ShaderBackend);
-Q_DECLARE_METATYPE(Settings::AstcRecompression);
-Q_DECLARE_METATYPE(Settings::AstcDecodeMode);
diff --git a/src/yuzu/configuration/configure_camera.cpp b/src/yuzu/configuration/configure_camera.cpp
index d95e96696e..3368f53f30 100644
--- a/src/yuzu/configuration/configure_camera.cpp
+++ b/src/yuzu/configuration/configure_camera.cpp
@@ -10,10 +10,10 @@
 #include <QStandardItemModel>
 #include <QTimer>
 
+#include "common/settings.h"
 #include "input_common/drivers/camera.h"
 #include "input_common/main.h"
 #include "ui_configure_camera.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_camera.h"
 
 ConfigureCamera::ConfigureCamera(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_)
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 0ad95cc02a..aab54a1ccb 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -8,7 +8,6 @@
 #include "core/core.h"
 #include "ui_configure.h"
 #include "vk_device_info.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_audio.h"
 #include "yuzu/configuration/configure_cpu.h"
 #include "yuzu/configuration/configure_debug_tab.h"
diff --git a/src/yuzu/configuration/configure_hotkeys.cpp b/src/yuzu/configuration/configure_hotkeys.cpp
index 68e21cd84c..76fc33e499 100644
--- a/src/yuzu/configuration/configure_hotkeys.cpp
+++ b/src/yuzu/configuration/configure_hotkeys.cpp
@@ -9,10 +9,11 @@
 #include "core/hid/emulated_controller.h"
 #include "core/hid/hid_core.h"
 
+#include "frontend_common/config.h"
 #include "ui_configure_hotkeys.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_hotkeys.h"
 #include "yuzu/hotkeys.h"
+#include "yuzu/uisettings.h"
 #include "yuzu/util/sequence_dialog/sequence_dialog.h"
 
 constexpr int name_column = 0;
@@ -62,18 +63,21 @@ ConfigureHotkeys::~ConfigureHotkeys() = default;
 
 void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
     for (const auto& group : registry.hotkey_groups) {
+        QString parent_item_data = QString::fromStdString(group.first);
         auto* parent_item =
-            new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(group.first)));
+            new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(parent_item_data)));
         parent_item->setEditable(false);
-        parent_item->setData(group.first);
+        parent_item->setData(parent_item_data);
         for (const auto& hotkey : group.second) {
-            auto* action =
-                new QStandardItem(QCoreApplication::translate("Hotkeys", qPrintable(hotkey.first)));
+            QString hotkey_action_data = QString::fromStdString(hotkey.first);
+            auto* action = new QStandardItem(
+                QCoreApplication::translate("Hotkeys", qPrintable(hotkey_action_data)));
             auto* keyseq =
                 new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
-            auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq);
+            auto* controller_keyseq =
+                new QStandardItem(QString::fromStdString(hotkey.second.controller_keyseq));
             action->setEditable(false);
-            action->setData(hotkey.first);
+            action->setData(hotkey_action_data);
             keyseq->setEditable(false);
             controller_keyseq->setEditable(false);
             parent_item->appendRow({action, keyseq, controller_keyseq});
@@ -301,13 +305,13 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
             const QStandardItem* controller_keyseq =
                 parent->child(key_column_id, controller_column);
             for (auto& [group, sub_actions] : registry.hotkey_groups) {
-                if (group != parent->data())
+                if (group != parent->data().toString().toStdString())
                     continue;
                 for (auto& [action_name, hotkey] : sub_actions) {
-                    if (action_name != action->data())
+                    if (action_name != action->data().toString().toStdString())
                         continue;
                     hotkey.keyseq = QKeySequence(keyseq->text());
-                    hotkey.controller_keyseq = controller_keyseq->text();
+                    hotkey.controller_keyseq = controller_keyseq->text().toStdString();
                 }
             }
         }
@@ -319,7 +323,7 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
 void ConfigureHotkeys::RestoreDefaults() {
     for (int r = 0; r < model->rowCount(); ++r) {
         const QStandardItem* parent = model->item(r, 0);
-        const int hotkey_size = static_cast<int>(Config::default_hotkeys.size());
+        const int hotkey_size = static_cast<int>(UISettings::default_hotkeys.size());
 
         if (hotkey_size != parent->rowCount()) {
             QMessageBox::warning(this, tr("Invalid hotkey settings"),
@@ -330,10 +334,11 @@ void ConfigureHotkeys::RestoreDefaults() {
         for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
             model->item(r, 0)
                 ->child(r2, hotkey_column)
-                ->setText(Config::default_hotkeys[r2].shortcut.keyseq);
+                ->setText(QString::fromStdString(UISettings::default_hotkeys[r2].shortcut.keyseq));
             model->item(r, 0)
                 ->child(r2, controller_column)
-                ->setText(Config::default_hotkeys[r2].shortcut.controller_keyseq);
+                ->setText(QString::fromStdString(
+                    UISettings::default_hotkeys[r2].shortcut.controller_keyseq));
         }
     }
 }
@@ -379,7 +384,7 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) {
 
 void ConfigureHotkeys::RestoreControllerHotkey(QModelIndex index) {
     const QString& default_key_sequence =
-        Config::default_hotkeys[index.row()].shortcut.controller_keyseq;
+        QString::fromStdString(UISettings::default_hotkeys[index.row()].shortcut.controller_keyseq);
     const auto [key_sequence_used, used_action] = IsUsedControllerKey(default_key_sequence);
 
     if (key_sequence_used && default_key_sequence != model->data(index).toString()) {
@@ -393,7 +398,8 @@ void ConfigureHotkeys::RestoreControllerHotkey(QModelIndex index) {
 
 void ConfigureHotkeys::RestoreHotkey(QModelIndex index) {
     const QKeySequence& default_key_sequence = QKeySequence::fromString(
-        Config::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText);
+        QString::fromStdString(UISettings::default_hotkeys[index.row()].shortcut.keyseq),
+        QKeySequence::NativeText);
     const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence);
 
     if (key_sequence_used && default_key_sequence != QKeySequence(model->data(index).toString())) {
diff --git a/src/yuzu/configuration/configure_input_per_game.cpp b/src/yuzu/configuration/configure_input_per_game.cpp
index 78e65d4689..8d9f65a057 100644
--- a/src/yuzu/configuration/configure_input_per_game.cpp
+++ b/src/yuzu/configuration/configure_input_per_game.cpp
@@ -5,12 +5,12 @@
 #include "core/core.h"
 #include "core/hid/emulated_controller.h"
 #include "core/hid/hid_core.h"
+#include "frontend_common/config.h"
 #include "ui_configure_input_per_game.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_input_per_game.h"
 #include "yuzu/configuration/input_profiles.h"
 
-ConfigureInputPerGame::ConfigureInputPerGame(Core::System& system_, Config* config_,
+ConfigureInputPerGame::ConfigureInputPerGame(Core::System& system_, QtConfig* config_,
                                              QWidget* parent)
     : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPerGame>()),
       profiles(std::make_unique<InputProfiles>()), system{system_}, config{config_} {
@@ -110,6 +110,6 @@ void ConfigureInputPerGame::SaveConfiguration() {
     // Clear all controls from the config in case the user reverted back to globals
     config->ClearControlPlayerValues();
     for (size_t index = 0; index < Settings::values.players.GetValue().size(); ++index) {
-        config->SaveControlPlayerValue(index);
+        config->SaveQtControlPlayerValues(index);
     }
 }
diff --git a/src/yuzu/configuration/configure_input_per_game.h b/src/yuzu/configuration/configure_input_per_game.h
index 660faf5743..4420e856cb 100644
--- a/src/yuzu/configuration/configure_input_per_game.h
+++ b/src/yuzu/configuration/configure_input_per_game.h
@@ -9,6 +9,7 @@
 
 #include "ui_configure_input_per_game.h"
 #include "yuzu/configuration/input_profiles.h"
+#include "yuzu/configuration/qt_config.h"
 
 class QComboBox;
 
@@ -22,7 +23,7 @@ class ConfigureInputPerGame : public QWidget {
     Q_OBJECT
 
 public:
-    explicit ConfigureInputPerGame(Core::System& system_, Config* config_,
+    explicit ConfigureInputPerGame(Core::System& system_, QtConfig* config_,
                                    QWidget* parent = nullptr);
 
     /// Load and Save configurations to settings file.
@@ -41,5 +42,5 @@ private:
     std::array<QComboBox*, 8> profile_comboboxes;
 
     Core::System& system;
-    Config* config;
+    QtConfig* config;
 };
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 9259e2a5d0..0f7b3714e7 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -12,15 +12,16 @@
 #include <QTimer>
 #include "common/assert.h"
 #include "common/param_package.h"
+#include "configuration/qt_config.h"
 #include "core/hid/emulated_controller.h"
 #include "core/hid/hid_core.h"
 #include "core/hid/hid_types.h"
+#include "frontend_common/config.h"
 #include "input_common/drivers/keyboard.h"
 #include "input_common/drivers/mouse.h"
 #include "input_common/main.h"
 #include "ui_configure_input_player.h"
 #include "yuzu/bootmanager.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_input_player.h"
 #include "yuzu/configuration/configure_input_player_widget.h"
 #include "yuzu/configuration/configure_mouse_panning.h"
@@ -1397,25 +1398,25 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
         for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
             emulated_controller->SetButtonParam(
                 button_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
-                               Config::default_buttons[button_id])});
+                               QtConfig::default_buttons[button_id])});
         }
         for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
             Common::ParamPackage analog_param{};
             for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
                 Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
-                    Config::default_analogs[analog_id][sub_button_id])};
+                    QtConfig::default_analogs[analog_id][sub_button_id])};
                 SetAnalogParam(params, analog_param, analog_sub_buttons[sub_button_id]);
             }
 
             analog_param.Set("modifier", InputCommon::GenerateKeyboardParam(
-                                             Config::default_stick_mod[analog_id]));
+                                             QtConfig::default_stick_mod[analog_id]));
             emulated_controller->SetStickParam(analog_id, analog_param);
         }
 
         for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
             emulated_controller->SetMotionParam(
                 motion_id, Common::ParamPackage{InputCommon::GenerateKeyboardParam(
-                               Config::default_motions[motion_id])});
+                               QtConfig::default_motions[motion_id])});
         }
 
         // If mouse is selected we want to override with mappings from the driver
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index b91d6ad4a6..b274a3321d 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -25,8 +25,8 @@
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/xts_archive.h"
 #include "core/loader/loader.h"
+#include "frontend_common/config.h"
 #include "ui_configure_per_game.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configuration_shared.h"
 #include "yuzu/configuration/configure_audio.h"
 #include "yuzu/configuration/configure_cpu.h"
@@ -50,8 +50,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
     const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
     const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
                                                 : fmt::format("{:016X}", title_id);
-    game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
-
+    game_config = std::make_unique<QtConfig>(config_file_name, Config::ConfigType::PerGameConfig);
     addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
     audio_tab = std::make_unique<ConfigureAudio>(system_, tab_group, *builder, this);
     cpu_tab = std::make_unique<ConfigureCpu>(system_, tab_group, *builder, this);
@@ -108,7 +107,7 @@ void ConfigurePerGame::ApplyConfiguration() {
     system.ApplySettings();
     Settings::LogSettings();
 
-    game_config->Save();
+    game_config->SaveAllValues();
 }
 
 void ConfigurePerGame::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
index cc2513001d..c8ee46c041 100644
--- a/src/yuzu/configuration/configure_per_game.h
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -12,9 +12,10 @@
 
 #include "configuration/shared_widget.h"
 #include "core/file_sys/vfs_types.h"
+#include "frontend_common/config.h"
 #include "vk_device_info.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/qt_config.h"
 #include "yuzu/configuration/shared_translation.h"
 
 namespace Core {
@@ -72,7 +73,7 @@ private:
 
     QGraphicsScene* scene;
 
-    std::unique_ptr<Config> game_config;
+    std::unique_ptr<QtConfig> game_config;
 
     Core::System& system;
     std::unique_ptr<ConfigurationShared::Builder> builder;
diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp
index 674a75a624..140a7fe5db 100644
--- a/src/yuzu/configuration/configure_per_game_addons.cpp
+++ b/src/yuzu/configuration/configure_per_game_addons.cpp
@@ -19,7 +19,6 @@
 #include "core/file_sys/xts_archive.h"
 #include "core/loader/loader.h"
 #include "ui_configure_per_game_addons.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_input.h"
 #include "yuzu/configuration/configure_per_game_addons.h"
 #include "yuzu/uisettings.h"
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
index f837055447..9572ff43c0 100644
--- a/src/yuzu/configuration/configure_ringcon.cpp
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -8,6 +8,7 @@
 #include <QTimer>
 #include <fmt/format.h>
 
+#include "configuration/qt_config.h"
 #include "core/hid/emulated_controller.h"
 #include "core/hid/hid_core.h"
 #include "input_common/drivers/keyboard.h"
@@ -15,7 +16,6 @@
 #include "input_common/main.h"
 #include "ui_configure_ringcon.h"
 #include "yuzu/bootmanager.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_ringcon.h"
 
 const std::array<std::string, ConfigureRingController::ANALOG_SUB_BUTTONS_NUM>
@@ -270,7 +270,7 @@ void ConfigureRingController::LoadConfiguration() {
 
 void ConfigureRingController::RestoreDefaults() {
     const std::string default_ring_string = InputCommon::GenerateAnalogParamFromKeys(
-        0, 0, Config::default_ringcon_analogs[0], Config::default_ringcon_analogs[1], 0, 0.05f);
+        0, 0, QtConfig::default_ringcon_analogs[0], QtConfig::default_ringcon_analogs[1], 0, 0.05f);
     emulated_controller->SetRingParam(Common::ParamPackage(default_ring_string));
     UpdateUI();
 }
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 0c8e5c8b4a..7cbf43775f 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -16,7 +16,6 @@
 #include "core/core.h"
 #include "core/hle/service/time/time_manager.h"
 #include "ui_configure_system.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configuration_shared.h"
 #include "yuzu/configuration/configure_system.h"
 #include "yuzu/configuration/shared_widget.h"
diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
index 5a03e48df6..94df6d9d3d 100644
--- a/src/yuzu/configuration/configure_touchscreen_advanced.cpp
+++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp
@@ -2,8 +2,8 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include <memory>
+#include "common/settings.h"
 #include "ui_configure_touchscreen_advanced.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_touchscreen_advanced.h"
 
 ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent)
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 82f3b6e782..dd43f0a0ef 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -164,7 +164,7 @@ ConfigureUi::~ConfigureUi() = default;
 
 void ConfigureUi::ApplyConfiguration() {
     UISettings::values.theme =
-        ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
+        ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString().toStdString();
     UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
     UISettings::values.show_compat = ui->show_compat->isChecked();
     UISettings::values.show_size = ui->show_size->isChecked();
@@ -191,9 +191,10 @@ void ConfigureUi::RequestGameListUpdate() {
 }
 
 void ConfigureUi::SetConfiguration() {
-    ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
+    ui->theme_combobox->setCurrentIndex(
+        ui->theme_combobox->findData(QString::fromStdString(UISettings::values.theme)));
     ui->language_combobox->setCurrentIndex(
-        ui->language_combobox->findData(UISettings::values.language));
+        ui->language_combobox->findData(QString::fromStdString(UISettings::values.language)));
     ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue());
     ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
     ui->show_size->setChecked(UISettings::values.show_size.GetValue());
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp
index 41ef4250a1..716efbccd3 100644
--- a/src/yuzu/configuration/input_profiles.cpp
+++ b/src/yuzu/configuration/input_profiles.cpp
@@ -5,7 +5,7 @@
 
 #include "common/fs/fs.h"
 #include "common/fs/path_util.h"
-#include "yuzu/configuration/config.h"
+#include "frontend_common/config.h"
 #include "yuzu/configuration/input_profiles.h"
 
 namespace FS = Common::FS;
@@ -44,7 +44,7 @@ InputProfiles::InputProfiles() {
             if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
                 map_profiles.insert_or_assign(
                     name_without_ext,
-                    std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile));
+                    std::make_unique<QtConfig>(name_without_ext, Config::ConfigType::InputProfile));
             }
 
             return true;
@@ -85,7 +85,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p
     }
 
     map_profiles.insert_or_assign(
-        profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
+        profile_name, std::make_unique<QtConfig>(profile_name, Config::ConfigType::InputProfile));
 
     return SaveProfile(profile_name, player_index);
 }
@@ -113,7 +113,7 @@ bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t pla
         return false;
     }
 
-    map_profiles[profile_name]->ReadControlPlayerValue(player_index);
+    map_profiles[profile_name]->ReadQtControlPlayerValues(player_index);
     return true;
 }
 
@@ -122,7 +122,7 @@ bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t pla
         return false;
     }
 
-    map_profiles[profile_name]->SaveControlPlayerValue(player_index);
+    map_profiles[profile_name]->SaveQtControlPlayerValues(player_index);
     return true;
 }
 
diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h
index 2bf3e42508..023ec74a63 100644
--- a/src/yuzu/configuration/input_profiles.h
+++ b/src/yuzu/configuration/input_profiles.h
@@ -6,6 +6,8 @@
 #include <string>
 #include <unordered_map>
 
+#include "configuration/qt_config.h"
+
 namespace Core {
 class System;
 }
@@ -30,5 +32,5 @@ public:
 private:
     bool ProfileExistsInMap(const std::string& profile_name) const;
 
-    std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
+    std::unordered_map<std::string, std::unique_ptr<QtConfig>> map_profiles;
 };
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp
new file mode 100644
index 0000000000..82402ec70b
--- /dev/null
+++ b/src/yuzu/configuration/qt_config.cpp
@@ -0,0 +1,548 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "input_common/main.h"
+#include "qt_config.h"
+#include "uisettings.h"
+
+const std::array<int, Settings::NativeButton::NumButtons> QtConfig::default_buttons = {
+    Qt::Key_C,    Qt::Key_X, Qt::Key_V,    Qt::Key_Z,  Qt::Key_F,
+    Qt::Key_G,    Qt::Key_Q, Qt::Key_E,    Qt::Key_R,  Qt::Key_T,
+    Qt::Key_M,    Qt::Key_N, Qt::Key_Left, Qt::Key_Up, Qt::Key_Right,
+    Qt::Key_Down, Qt::Key_Q, Qt::Key_E,    0,          0,
+    Qt::Key_Q,    Qt::Key_E,
+};
+
+const std::array<int, Settings::NativeMotion::NumMotions> QtConfig::default_motions = {
+    Qt::Key_7,
+    Qt::Key_8,
+};
+
+const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> QtConfig::default_analogs{{
+    {
+        Qt::Key_W,
+        Qt::Key_S,
+        Qt::Key_A,
+        Qt::Key_D,
+    },
+    {
+        Qt::Key_I,
+        Qt::Key_K,
+        Qt::Key_J,
+        Qt::Key_L,
+    },
+}};
+
+const std::array<int, 2> QtConfig::default_stick_mod = {
+    Qt::Key_Shift,
+    0,
+};
+
+const std::array<int, 2> QtConfig::default_ringcon_analogs{{
+    Qt::Key_A,
+    Qt::Key_D,
+}};
+
+QtConfig::QtConfig(const std::string& config_name, const ConfigType config_type)
+    : Config(config_type) {
+    Initialize(config_name);
+    if (config_type != ConfigType::InputProfile) {
+        ReadQtValues();
+        SaveQtValues();
+    }
+}
+
+QtConfig::~QtConfig() {
+    if (global) {
+        QtConfig::SaveAllValues();
+    }
+}
+
+void QtConfig::ReloadAllValues() {
+    Reload();
+    ReadQtValues();
+    SaveQtValues();
+}
+
+void QtConfig::SaveAllValues() {
+    Save();
+    SaveQtValues();
+}
+
+void QtConfig::ReadQtValues() {
+    if (global) {
+        ReadUIValues();
+    }
+    ReadQtControlValues();
+}
+
+void QtConfig::ReadQtPlayerValues(const std::size_t player_index) {
+    std::string player_prefix;
+    if (type != ConfigType::InputProfile) {
+        player_prefix.append("player_").append(ToString(player_index)).append("_");
+    }
+
+    auto& player = Settings::values.players.GetValue()[player_index];
+    if (IsCustomConfig()) {
+        const auto profile_name =
+            ReadStringSetting(std::string(player_prefix).append("profile_name"));
+        if (profile_name.empty()) {
+            // Use the global input config
+            player = Settings::values.players.GetValue(true)[player_index];
+            return;
+        }
+    }
+
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        auto& player_buttons = player.buttons[i];
+
+        player_buttons = ReadStringSetting(
+            std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param);
+        if (player_buttons.empty()) {
+            player_buttons = default_param;
+        }
+    }
+
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        auto& player_analogs = player.analogs[i];
+
+        player_analogs = ReadStringSetting(
+            std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param);
+        if (player_analogs.empty()) {
+            player_analogs = default_param;
+        }
+    }
+
+    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+        auto& player_motions = player.motions[i];
+
+        player_motions = ReadStringSetting(
+            std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param);
+        if (player_motions.empty()) {
+            player_motions = default_param;
+        }
+    }
+}
+
+void QtConfig::ReadHidbusValues() {
+    const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+        0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+    auto& ringcon_analogs = Settings::values.ringcon_analogs;
+
+    ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param);
+    if (ringcon_analogs.empty()) {
+        ringcon_analogs = default_param;
+    }
+}
+
+void QtConfig::ReadDebugControlValues() {
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
+
+        debug_pad_buttons = ReadStringSetting(
+            std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), default_param);
+        if (debug_pad_buttons.empty()) {
+            debug_pad_buttons = default_param;
+        }
+    }
+
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
+
+        debug_pad_analogs = ReadStringSetting(
+            std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), default_param);
+        if (debug_pad_analogs.empty()) {
+            debug_pad_analogs = default_param;
+        }
+    }
+}
+
+void QtConfig::ReadQtControlValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+    Settings::values.players.SetGlobal(!IsCustomConfig());
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+        ReadQtPlayerValues(p);
+    }
+    if (IsCustomConfig()) {
+        EndGroup();
+        return;
+    }
+    ReadDebugControlValues();
+    ReadHidbusValues();
+
+    EndGroup();
+}
+
+void QtConfig::ReadPathValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
+
+    UISettings::values.roms_path = ReadStringSetting(std::string("romsPath"));
+    UISettings::values.symbols_path = ReadStringSetting(std::string("symbolsPath"));
+    UISettings::values.game_dir_deprecated =
+        ReadStringSetting(std::string("gameListRootDir"), std::string("."));
+    UISettings::values.game_dir_deprecated_deepscan =
+        ReadBooleanSetting(std::string("gameListDeepScan"), std::make_optional(false));
+
+    const int gamedirs_size = BeginArray(std::string("gamedirs"));
+    for (int i = 0; i < gamedirs_size; ++i) {
+        SetArrayIndex(i);
+        UISettings::GameDir game_dir;
+        game_dir.path = ReadStringSetting(std::string("path"));
+        game_dir.deep_scan =
+            ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false));
+        game_dir.expanded = ReadBooleanSetting(std::string("expanded"), std::make_optional(true));
+        UISettings::values.game_dirs.append(game_dir);
+    }
+    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.empty()) {
+        UISettings::GameDir game_dir;
+        game_dir.path = std::string("SDMC");
+        game_dir.expanded = true;
+        UISettings::values.game_dirs.append(game_dir);
+        game_dir.path = std::string("UserNAND");
+        UISettings::values.game_dirs.append(game_dir);
+        game_dir.path = std::string("SysNAND");
+        UISettings::values.game_dirs.append(game_dir);
+        if (UISettings::values.game_dir_deprecated != std::string(".")) {
+            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 =
+        QString::fromStdString(ReadStringSetting(std::string("recentFiles")))
+            .split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive);
+    UISettings::values.language = ReadStringSetting(std::string("language"), std::string(""));
+
+    EndGroup();
+}
+
+void QtConfig::ReadShortcutValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
+
+    for (const auto& [name, group, shortcut] : UISettings::default_hotkeys) {
+        BeginGroup(group);
+        BeginGroup(name);
+
+        // No longer using ReadSetting for shortcut.second as it inaccurately returns a value of 1
+        // for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
+        // a file dialog in windowed mode
+        UISettings::values.shortcuts.push_back(
+            {name,
+             group,
+             {ReadStringSetting(std::string("KeySeq"), shortcut.keyseq),
+              ReadStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq),
+              shortcut.context,
+              ReadBooleanSetting(std::string("Repeat"), std::optional(shortcut.repeat))}});
+
+        EndGroup(); // name
+        EndGroup(); // group
+    }
+
+    EndGroup();
+}
+
+void QtConfig::ReadUIValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
+
+    UISettings::values.theme = ReadStringSetting(
+        std::string("theme"),
+        std::string(UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second));
+
+    ReadUIGamelistValues();
+    ReadUILayoutValues();
+    ReadPathValues();
+    ReadScreenshotValues();
+    ReadShortcutValues();
+    ReadMultiplayerValues();
+
+    ReadCategory(Settings::Category::Ui);
+    ReadCategory(Settings::Category::UiGeneral);
+
+    EndGroup();
+}
+
+void QtConfig::ReadUIGamelistValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
+
+    ReadCategory(Settings::Category::UiGameList);
+
+    const int favorites_size = BeginArray("favorites");
+    for (int i = 0; i < favorites_size; i++) {
+        SetArrayIndex(i);
+        UISettings::values.favorited_ids.append(ReadIntegerSetting(std::string("program_id")));
+    }
+    EndArray();
+
+    EndGroup();
+}
+
+void QtConfig::ReadUILayoutValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
+
+    ReadCategory(Settings::Category::UiLayout);
+
+    EndGroup();
+}
+
+void QtConfig::ReadMultiplayerValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Multiplayer));
+
+    ReadCategory(Settings::Category::Multiplayer);
+
+    // Read ban list back
+    int size = BeginArray(std::string("username_ban_list"));
+    UISettings::values.multiplayer_ban_list.first.resize(size);
+    for (int i = 0; i < size; ++i) {
+        SetArrayIndex(i);
+        UISettings::values.multiplayer_ban_list.first[i] =
+            ReadStringSetting(std::string("username"), std::string(""));
+    }
+    EndArray();
+
+    size = BeginArray(std::string("ip_ban_list"));
+    UISettings::values.multiplayer_ban_list.second.resize(size);
+    for (int i = 0; i < size; ++i) {
+        UISettings::values.multiplayer_ban_list.second[i] =
+            ReadStringSetting("username", std::string(""));
+    }
+    EndArray();
+
+    EndGroup();
+}
+
+void QtConfig::SaveQtValues() {
+    if (global) {
+        SaveUIValues();
+    }
+    SaveQtControlValues();
+
+    WriteToIni();
+}
+
+void QtConfig::SaveQtPlayerValues(const std::size_t player_index) {
+    std::string player_prefix;
+    if (type != ConfigType::InputProfile) {
+        player_prefix = std::string("player_").append(ToString(player_index)).append("_");
+    }
+
+    const auto& player = Settings::values.players.GetValue()[player_index];
+    if (IsCustomConfig() && player.profile_name.empty()) {
+        // No custom profile selected
+        return;
+    }
+
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
+                     player.buttons[i], std::make_optional(default_param));
+    }
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
+                     player.analogs[i], std::make_optional(default_param));
+    }
+    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+        WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
+                     player.motions[i], std::make_optional(default_param));
+    }
+}
+
+void QtConfig::SaveDebugControlValues() {
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
+                     Settings::values.debug_pad_buttons[i], std::make_optional(default_param));
+    }
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
+                     Settings::values.debug_pad_analogs[i], std::make_optional(default_param));
+    }
+}
+
+void QtConfig::SaveHidbusValues() {
+    const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+        0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+    WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
+                 std::make_optional(default_param));
+}
+
+void QtConfig::SaveQtControlValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+    Settings::values.players.SetGlobal(!IsCustomConfig());
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+        SaveQtPlayerValues(p);
+    }
+    if (IsCustomConfig()) {
+        EndGroup();
+        return;
+    }
+    SaveDebugControlValues();
+    SaveHidbusValues();
+
+    EndGroup();
+}
+
+void QtConfig::SavePathValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
+
+    WriteSetting(std::string("romsPath"), UISettings::values.roms_path);
+    WriteSetting(std::string("symbolsPath"), UISettings::values.symbols_path);
+    BeginArray(std::string("gamedirs"));
+    for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
+        SetArrayIndex(i);
+        const auto& game_dir = UISettings::values.game_dirs[i];
+        WriteSetting(std::string("path"), game_dir.path);
+        WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false));
+        WriteSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true));
+    }
+    EndArray();
+
+    WriteSetting(std::string("recentFiles"),
+                 UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
+    WriteSetting(std::string("language"), UISettings::values.language);
+
+    EndGroup();
+}
+
+void QtConfig::SaveShortcutValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Shortcuts));
+
+    // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
+    // However, their ordering must also be the same.
+    for (std::size_t i = 0; i < UISettings::default_hotkeys.size(); i++) {
+        const auto& [name, group, shortcut] = UISettings::values.shortcuts[i];
+        const auto& default_hotkey = UISettings::default_hotkeys[i].shortcut;
+
+        BeginGroup(group);
+        BeginGroup(name);
+
+        WriteSetting(std::string("KeySeq"), shortcut.keyseq,
+                     std::make_optional(default_hotkey.keyseq));
+        WriteSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq,
+                     std::make_optional(default_hotkey.controller_keyseq));
+        WriteSetting(std::string("Context"), shortcut.context,
+                     std::make_optional(default_hotkey.context));
+        WriteSetting(std::string("Repeat"), shortcut.repeat,
+                     std::make_optional(default_hotkey.repeat));
+
+        EndGroup(); // name
+        EndGroup(); // group
+    }
+
+    EndGroup();
+}
+
+void QtConfig::SaveUIValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
+
+    WriteCategory(Settings::Category::Ui);
+    WriteCategory(Settings::Category::UiGeneral);
+
+    WriteSetting(std::string("theme"), UISettings::values.theme,
+                 std::make_optional(std::string(
+                     UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second)));
+
+    SaveUIGamelistValues();
+    SaveUILayoutValues();
+    SavePathValues();
+    SaveScreenshotValues();
+    SaveShortcutValues();
+    SaveMultiplayerValues();
+
+    EndGroup();
+}
+
+void QtConfig::SaveUIGamelistValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::UiGameList));
+
+    WriteCategory(Settings::Category::UiGameList);
+
+    BeginArray(std::string("favorites"));
+    for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
+        SetArrayIndex(i);
+        WriteSetting(std::string("program_id"), UISettings::values.favorited_ids[i]);
+    }
+    EndArray(); // favorites
+
+    EndGroup();
+}
+
+void QtConfig::SaveUILayoutValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::UiLayout));
+
+    WriteCategory(Settings::Category::UiLayout);
+
+    EndGroup();
+}
+
+void QtConfig::SaveMultiplayerValues() {
+    BeginGroup(std::string("Multiplayer"));
+
+    WriteCategory(Settings::Category::Multiplayer);
+
+    // Write ban list
+    BeginArray(std::string("username_ban_list"));
+    for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.first.size(); ++i) {
+        SetArrayIndex(static_cast<int>(i));
+        WriteSetting(std::string("username"), UISettings::values.multiplayer_ban_list.first[i]);
+    }
+    EndArray(); // username_ban_list
+
+    BeginArray(std::string("ip_ban_list"));
+    for (std::size_t i = 0; i < UISettings::values.multiplayer_ban_list.second.size(); ++i) {
+        SetArrayIndex(static_cast<int>(i));
+        WriteSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]);
+    }
+    EndArray(); // ip_ban_list
+
+    EndGroup();
+}
+
+std::vector<Settings::BasicSetting*>& QtConfig::FindRelevantList(Settings::Category category) {
+    auto& map = Settings::values.linkage.by_category;
+    if (map.contains(category)) {
+        return Settings::values.linkage.by_category[category];
+    }
+    return UISettings::values.linkage.by_category[category];
+}
+
+void QtConfig::ReadQtControlPlayerValues(std::size_t player_index) {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+    ReadPlayerValues(player_index);
+    ReadQtPlayerValues(player_index);
+
+    EndGroup();
+}
+
+void QtConfig::SaveQtControlPlayerValues(std::size_t player_index) {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+    SavePlayerValues(player_index);
+    SaveQtPlayerValues(player_index);
+
+    EndGroup();
+
+    WriteToIni();
+}
diff --git a/src/yuzu/configuration/qt_config.h b/src/yuzu/configuration/qt_config.h
new file mode 100644
index 0000000000..dc2dceb4d7
--- /dev/null
+++ b/src/yuzu/configuration/qt_config.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QMetaType>
+
+#include "frontend_common/config.h"
+
+class QtConfig final : public Config {
+public:
+    explicit QtConfig(const std::string& config_name = "qt-config",
+                      ConfigType config_type = ConfigType::GlobalConfig);
+    ~QtConfig() override;
+
+    void ReloadAllValues() override;
+    void SaveAllValues() override;
+
+    void ReadQtControlPlayerValues(std::size_t player_index);
+    void SaveQtControlPlayerValues(std::size_t player_index);
+
+protected:
+    void ReadQtValues();
+    void ReadQtPlayerValues(std::size_t player_index);
+    void ReadQtControlValues();
+    void ReadHidbusValues() override;
+    void ReadDebugControlValues() override;
+    void ReadPathValues() override;
+    void ReadShortcutValues() override;
+    void ReadUIValues() override;
+    void ReadUIGamelistValues() override;
+    void ReadUILayoutValues() override;
+    void ReadMultiplayerValues() override;
+
+    void SaveQtValues();
+    void SaveQtPlayerValues(std::size_t player_index);
+    void SaveQtControlValues();
+    void SaveHidbusValues() override;
+    void SaveDebugControlValues() override;
+    void SavePathValues() override;
+    void SaveShortcutValues() override;
+    void SaveUIValues() override;
+    void SaveUIGamelistValues() override;
+    void SaveUILayoutValues() override;
+    void SaveMultiplayerValues() override;
+
+    std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;
+
+public:
+    static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
+    static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
+    static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
+    static const std::array<int, 2> default_stick_mod;
+    static const std::array<int, 2> default_ringcon_analogs;
+};
diff --git a/src/yuzu/configuration/shared_translation.h b/src/yuzu/configuration/shared_translation.h
index 99a0e808ca..d5fc3b8def 100644
--- a/src/yuzu/configuration/shared_translation.h
+++ b/src/yuzu/configuration/shared_translation.h
@@ -10,6 +10,7 @@
 #include <vector>
 #include <QString>
 #include "common/common_types.h"
+#include "common/settings.h"
 
 class QWidget;
 
@@ -22,4 +23,46 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent);
 
 std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent);
 
+static const std::map<Settings::AntiAliasing, QString> anti_aliasing_texts_map = {
+    {Settings::AntiAliasing::None, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "None"))},
+    {Settings::AntiAliasing::Fxaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FXAA"))},
+    {Settings::AntiAliasing::Smaa, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SMAA"))},
+};
+
+static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map = {
+    {Settings::ScalingFilter::NearestNeighbor,
+     QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Nearest"))},
+    {Settings::ScalingFilter::Bilinear,
+     QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bilinear"))},
+    {Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))},
+    {Settings::ScalingFilter::Gaussian,
+     QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Gaussian"))},
+    {Settings::ScalingFilter::ScaleForce,
+     QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))},
+    {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
+};
+
+static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map = {
+    {Settings::ConsoleMode::Docked, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Docked"))},
+    {Settings::ConsoleMode::Handheld, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Handheld"))},
+};
+
+static const std::map<Settings::GpuAccuracy, QString> gpu_accuracy_texts_map = {
+    {Settings::GpuAccuracy::Normal, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Normal"))},
+    {Settings::GpuAccuracy::High, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "High"))},
+    {Settings::GpuAccuracy::Extreme, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Extreme"))},
+};
+
+static const std::map<Settings::RendererBackend, QString> renderer_backend_texts_map = {
+    {Settings::RendererBackend::Vulkan, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Vulkan"))},
+    {Settings::RendererBackend::OpenGL, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "OpenGL"))},
+    {Settings::RendererBackend::Null, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Null"))},
+};
+
+static const std::map<Settings::ShaderBackend, QString> shader_backend_texts_map = {
+    {Settings::ShaderBackend::Glsl, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLSL"))},
+    {Settings::ShaderBackend::Glasm, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "GLASM"))},
+    {Settings::ShaderBackend::SpirV, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "SPIRV"))},
+};
+
 } // namespace ConfigurationShared
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 7049c57b6e..6d227ef8dd 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -36,10 +36,8 @@ constexpr std::array<std::array<Qt::GlobalColor, 2>, 10> WaitTreeColors{{
 
 bool IsDarkTheme() {
     const auto& theme = UISettings::values.theme;
-    return theme == QStringLiteral("qdarkstyle") ||
-           theme == QStringLiteral("qdarkstyle_midnight_blue") ||
-           theme == QStringLiteral("colorful_dark") ||
-           theme == QStringLiteral("colorful_midnight_blue");
+    return theme == std::string("qdarkstyle") || theme == std::string("qdarkstyle_midnight_blue") ||
+           theme == std::string("colorful_dark") || theme == std::string("colorful_midnight_blue");
 }
 
 } // namespace
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index f294dc23d5..59b317135c 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -278,7 +278,7 @@ void GameList::OnUpdateThemedIcons() {
         case GameListItemType::CustomDir: {
             const UISettings::GameDir& game_dir =
                 UISettings::values.game_dirs[child->data(GameListDir::GameDirRole).toInt()];
-            const QString icon_name = QFileInfo::exists(game_dir.path)
+            const QString icon_name = QFileInfo::exists(QString::fromStdString(game_dir.path))
                                           ? QStringLiteral("folder")
                                           : QStringLiteral("bad_folder");
             child->setData(
@@ -727,7 +727,8 @@ void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
     });
 
     connect(open_directory_location, &QAction::triggered, [this, game_dir_index] {
-        emit OpenDirectory(UISettings::values.game_dirs[game_dir_index].path);
+        emit OpenDirectory(
+            QString::fromStdString(UISettings::values.game_dirs[game_dir_index].path));
     });
 }
 
@@ -869,7 +870,7 @@ const QStringList GameList::supported_file_extensions = {
     QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
 
 void GameList::RefreshGameDirectory() {
-    if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) {
+    if (!UISettings::values.game_dirs.empty() && current_worker != nullptr) {
         LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
         PopulateAsync(UISettings::values.game_dirs);
     }
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 86a0c41d99..c330b574f9 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -286,13 +286,13 @@ public:
             setData(QObject::tr("System Titles"), Qt::DisplayRole);
             break;
         case GameListItemType::CustomDir: {
-            const QString icon_name = QFileInfo::exists(game_dir->path)
-                                          ? QStringLiteral("folder")
-                                          : QStringLiteral("bad_folder");
+            const QString path = QString::fromStdString(game_dir->path);
+            const QString icon_name =
+                QFileInfo::exists(path) ? QStringLiteral("folder") : QStringLiteral("bad_folder");
             setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
                         icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
                     Qt::DecorationRole);
-            setData(game_dir->path, Qt::DisplayRole);
+            setData(path, Qt::DisplayRole);
             break;
         }
         default:
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 69be210277..ad3eada947 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -456,26 +456,26 @@ void GameListWorker::run() {
             break;
         }
 
-        if (game_dir.path == QStringLiteral("SDMC")) {
+        if (game_dir.path == std::string("SDMC")) {
             auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
             DirEntryReady(game_list_dir);
             AddTitlesToGameList(game_list_dir);
-        } else if (game_dir.path == QStringLiteral("UserNAND")) {
+        } else if (game_dir.path == std::string("UserNAND")) {
             auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
             DirEntryReady(game_list_dir);
             AddTitlesToGameList(game_list_dir);
-        } else if (game_dir.path == QStringLiteral("SysNAND")) {
+        } else if (game_dir.path == std::string("SysNAND")) {
             auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
             DirEntryReady(game_list_dir);
             AddTitlesToGameList(game_list_dir);
         } else {
-            watch_list.append(game_dir.path);
+            watch_list.append(QString::fromStdString(game_dir.path));
             auto* const game_list_dir = new GameListDir(game_dir);
             DirEntryReady(game_list_dir);
-            ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(),
-                           game_dir.deep_scan, game_list_dir);
-            ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
-                           game_dir.deep_scan, game_list_dir);
+            ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan,
+                           game_list_dir);
+            ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan,
+                           game_list_dir);
         }
     }
 
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index 6530186c10..eebfbf1555 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -19,7 +19,7 @@ void HotkeyRegistry::SaveHotkeys() {
         for (const auto& hotkey : group.second) {
             UISettings::values.shortcuts.push_back(
                 {hotkey.first, group.first,
-                 UISettings::ContextualShortcut({hotkey.second.keyseq.toString(),
+                 UISettings::ContextualShortcut({hotkey.second.keyseq.toString().toStdString(),
                                                  hotkey.second.controller_keyseq,
                                                  hotkey.second.context, hotkey.second.repeat})});
         }
@@ -31,12 +31,12 @@ void HotkeyRegistry::LoadHotkeys() {
     // beginGroup()
     for (auto shortcut : UISettings::values.shortcuts) {
         Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
-        if (!shortcut.shortcut.keyseq.isEmpty()) {
-            hk.keyseq =
-                QKeySequence::fromString(shortcut.shortcut.keyseq, QKeySequence::NativeText);
+        if (!shortcut.shortcut.keyseq.empty()) {
+            hk.keyseq = QKeySequence::fromString(QString::fromStdString(shortcut.shortcut.keyseq),
+                                                 QKeySequence::NativeText);
             hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.context);
         }
-        if (!shortcut.shortcut.controller_keyseq.isEmpty()) {
+        if (!shortcut.shortcut.controller_keyseq.empty()) {
             hk.controller_keyseq = shortcut.shortcut.controller_keyseq;
         }
         if (hk.shortcut) {
@@ -51,7 +51,8 @@ void HotkeyRegistry::LoadHotkeys() {
     }
 }
 
-QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
+QShortcut* HotkeyRegistry::GetHotkey(const std::string& group, const std::string& action,
+                                     QWidget* widget) {
     Hotkey& hk = hotkey_groups[group][action];
 
     if (!hk.shortcut) {
@@ -62,7 +63,8 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action
     return hk.shortcut;
 }
 
-ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const QString& group, const QString& action,
+ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const std::string& group,
+                                                        const std::string& action,
                                                         Core::HID::EmulatedController* controller) {
     Hotkey& hk = hotkey_groups[group][action];
 
@@ -74,12 +76,12 @@ ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const QString& group, co
     return hk.controller_shortcut;
 }
 
-QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
+QKeySequence HotkeyRegistry::GetKeySequence(const std::string& group, const std::string& action) {
     return hotkey_groups[group][action].keyseq;
 }
 
-Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
-                                                       const QString& action) {
+Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const std::string& group,
+                                                       const std::string& action) {
     return hotkey_groups[group][action].context;
 }
 
@@ -101,10 +103,10 @@ void ControllerShortcut::SetKey(const ControllerButtonSequence& buttons) {
     button_sequence = buttons;
 }
 
-void ControllerShortcut::SetKey(const QString& buttons_shortcut) {
+void ControllerShortcut::SetKey(const std::string& buttons_shortcut) {
     ControllerButtonSequence sequence{};
-    name = buttons_shortcut.toStdString();
-    std::istringstream command_line(buttons_shortcut.toStdString());
+    name = buttons_shortcut;
+    std::istringstream command_line(buttons_shortcut);
     std::string line;
     while (std::getline(command_line, line, '+')) {
         if (line.empty()) {
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 56eee8d821..e11332d2e4 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -33,7 +33,7 @@ public:
     ~ControllerShortcut();
 
     void SetKey(const ControllerButtonSequence& buttons);
-    void SetKey(const QString& buttons_shortcut);
+    void SetKey(const std::string& buttons_shortcut);
 
     ControllerButtonSequence ButtonSequence() const;
 
@@ -88,8 +88,8 @@ public:
      *          will be the same. Thus, you shouldn't rely on the caller really being the
      *          QShortcut's parent.
      */
-    QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
-    ControllerShortcut* GetControllerHotkey(const QString& group, const QString& action,
+    QShortcut* GetHotkey(const std::string& group, const std::string& action, QWidget* widget);
+    ControllerShortcut* GetControllerHotkey(const std::string& group, const std::string& action,
                                             Core::HID::EmulatedController* controller);
 
     /**
@@ -98,7 +98,7 @@ public:
      * @param group  General group this hotkey belongs to (e.g. "Main Window", "Debugger").
      * @param action Name of the action (e.g. "Start Emulation", "Load Image").
      */
-    QKeySequence GetKeySequence(const QString& group, const QString& action);
+    QKeySequence GetKeySequence(const std::string& group, const std::string& action);
 
     /**
      * Returns a Qt::ShortcutContext object who can be connected to other
@@ -108,20 +108,20 @@ public:
      * "Debugger").
      * @param action Name of the action (e.g. "Start Emulation", "Load Image").
      */
-    Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);
+    Qt::ShortcutContext GetShortcutContext(const std::string& group, const std::string& action);
 
 private:
     struct Hotkey {
         QKeySequence keyseq;
-        QString controller_keyseq;
+        std::string controller_keyseq;
         QShortcut* shortcut = nullptr;
         ControllerShortcut* controller_shortcut = nullptr;
         Qt::ShortcutContext context = Qt::WindowShortcut;
         bool repeat;
     };
 
-    using HotkeyMap = std::map<QString, Hotkey>;
-    using HotkeyGroupMap = std::map<QString, HotkeyMap>;
+    using HotkeyMap = std::map<std::string, Hotkey>;
+    using HotkeyGroupMap = std::map<std::string, HotkeyMap>;
 
     HotkeyGroupMap hotkey_groups;
 };
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index f22db233b1..defe45198e 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -128,6 +128,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
 #include "core/loader/loader.h"
 #include "core/perf_stats.h"
 #include "core/telemetry_session.h"
+#include "frontend_common/config.h"
 #include "input_common/drivers/tas_input.h"
 #include "input_common/drivers/virtual_amiibo.h"
 #include "input_common/main.h"
@@ -140,9 +141,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
 #include "yuzu/bootmanager.h"
 #include "yuzu/compatdb.h"
 #include "yuzu/compatibility_list.h"
-#include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_dialog.h"
 #include "yuzu/configuration/configure_input_per_game.h"
+#include "yuzu/configuration/qt_config.h"
 #include "yuzu/debugger/console.h"
 #include "yuzu/debugger/controller.h"
 #include "yuzu/debugger/profiler.h"
@@ -311,7 +312,7 @@ bool GMainWindow::CheckDarkMode() {
 #endif // __unix__
 }
 
-GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
+GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulkan)
     : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
       input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
       vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
@@ -676,7 +677,7 @@ void GMainWindow::ControllerSelectorReconfigureControllers(
     // Don't forget to apply settings.
     system->HIDCore().DisableAllControllerConfiguration();
     system->ApplySettings();
-    config->Save();
+    config->SaveAllValues();
 
     UpdateStatusButtons();
 
@@ -1129,7 +1130,7 @@ void GMainWindow::InitializeWidgets() {
     connect(aa_status_button, &QPushButton::customContextMenuRequested,
             [this](const QPoint& menu_location) {
                 QMenu context_menu;
-                for (auto const& aa_text_pair : Config::anti_aliasing_texts_map) {
+                for (auto const& aa_text_pair : ConfigurationShared::anti_aliasing_texts_map) {
                     context_menu.addAction(aa_text_pair.second, [this, aa_text_pair] {
                         Settings::values.anti_aliasing.SetValue(aa_text_pair.first);
                         UpdateAAText();
@@ -1153,7 +1154,7 @@ void GMainWindow::InitializeWidgets() {
     connect(filter_status_button, &QPushButton::customContextMenuRequested,
             [this](const QPoint& menu_location) {
                 QMenu context_menu;
-                for (auto const& filter_text_pair : Config::scaling_filter_texts_map) {
+                for (auto const& filter_text_pair : ConfigurationShared::scaling_filter_texts_map) {
                     context_menu.addAction(filter_text_pair.second, [this, filter_text_pair] {
                         Settings::values.scaling_filter.SetValue(filter_text_pair.first);
                         UpdateFilterText();
@@ -1176,7 +1177,7 @@ void GMainWindow::InitializeWidgets() {
             [this](const QPoint& menu_location) {
                 QMenu context_menu;
 
-                for (auto const& pair : Config::use_docked_mode_texts_map) {
+                for (auto const& pair : ConfigurationShared::use_docked_mode_texts_map) {
                     context_menu.addAction(pair.second, [this, &pair] {
                         if (pair.first != Settings::values.use_docked_mode.GetValue()) {
                             OnToggleDockedMode();
@@ -1200,7 +1201,7 @@ void GMainWindow::InitializeWidgets() {
             [this](const QPoint& menu_location) {
                 QMenu context_menu;
 
-                for (auto const& gpu_accuracy_pair : Config::gpu_accuracy_texts_map) {
+                for (auto const& gpu_accuracy_pair : ConfigurationShared::gpu_accuracy_texts_map) {
                     if (gpu_accuracy_pair.first == Settings::GpuAccuracy::Extreme) {
                         continue;
                     }
@@ -1229,7 +1230,8 @@ void GMainWindow::InitializeWidgets() {
             [this](const QPoint& menu_location) {
                 QMenu context_menu;
 
-                for (auto const& renderer_backend_pair : Config::renderer_backend_texts_map) {
+                for (auto const& renderer_backend_pair :
+                     ConfigurationShared::renderer_backend_texts_map) {
                     if (renderer_backend_pair.first == Settings::RendererBackend::Null) {
                         continue;
                     }
@@ -1294,16 +1296,17 @@ void GMainWindow::InitializeRecentFileMenuActions() {
 
 void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name,
                                      const bool tas_allowed) {
-    static const QString main_window = QStringLiteral("Main Window");
-    action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name));
-    action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name));
+    static const auto main_window = std::string("Main Window");
+    action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name.toStdString()));
+    action->setShortcutContext(
+        hotkey_registry.GetShortcutContext(main_window, action_name.toStdString()));
     action->setAutoRepeat(false);
 
     this->addAction(action);
 
     auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
     const auto* controller_hotkey =
-        hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
+        hotkey_registry.GetControllerHotkey(main_window, action_name.toStdString(), controller);
     connect(
         controller_hotkey, &ControllerShortcut::Activated, this,
         [action, tas_allowed, this] {
@@ -1335,10 +1338,11 @@ void GMainWindow::InitializeHotkeys() {
 
     static const QString main_window = QStringLiteral("Main Window");
     const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) {
-        const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this);
+        const auto* hotkey =
+            hotkey_registry.GetHotkey(main_window.toStdString(), action_name.toStdString(), this);
         auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
-        const auto* controller_hotkey =
-            hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
+        const auto* controller_hotkey = hotkey_registry.GetControllerHotkey(
+            main_window.toStdString(), action_name.toStdString(), controller);
         connect(hotkey, &QShortcut::activated, this, function);
         connect(controller_hotkey, &ControllerShortcut::Activated, this, function,
                 Qt::QueuedConnection);
@@ -1918,7 +1922,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
     // Save configurations
     UpdateUISettings();
     game_list->SaveInterfaceLayout();
-    config->Save();
+    config->SaveAllValues();
 
     u64 title_id{0};
 
@@ -1936,7 +1940,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
         const auto config_file_name = title_id == 0
                                           ? Common::FS::PathToUTF8String(file_path.filename())
                                           : fmt::format("{:016X}", title_id);
-        Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
+        QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
         system->HIDCore().ReloadInputDevices();
         system->ApplySettings();
     }
@@ -3135,7 +3139,7 @@ void GMainWindow::OnGameListAddDirectory() {
         return;
     }
 
-    UISettings::GameDir game_dir{dir_path, false, true};
+    UISettings::GameDir game_dir{dir_path.toStdString(), false, true};
     if (!UISettings::values.game_dirs.contains(game_dir)) {
         UISettings::values.game_dirs.append(game_dir);
         game_list->PopulateAsync(UISettings::values.game_dirs);
@@ -3181,14 +3185,14 @@ void GMainWindow::OnMenuLoadFile() {
                                    "%1 is an identifier for the Switch executable file extensions.")
                                     .arg(extensions);
     const QString filename = QFileDialog::getOpenFileName(
-        this, tr("Load File"), UISettings::values.roms_path, file_filter);
+        this, tr("Load File"), QString::fromStdString(UISettings::values.roms_path), file_filter);
     is_load_file_select_active = false;
 
     if (filename.isEmpty()) {
         return;
     }
 
-    UISettings::values.roms_path = QFileInfo(filename).path();
+    UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
     BootGame(filename);
 }
 
@@ -3221,7 +3225,8 @@ void GMainWindow::OnMenuInstallToNAND() {
            "Image (*.xci)");
 
     QStringList filenames = QFileDialog::getOpenFileNames(
-        this, tr("Install Files"), UISettings::values.roms_path, file_filter);
+        this, tr("Install Files"), QString::fromStdString(UISettings::values.roms_path),
+        file_filter);
 
     if (filenames.isEmpty()) {
         return;
@@ -3239,7 +3244,7 @@ void GMainWindow::OnMenuInstallToNAND() {
     }
 
     // Save folder location of the first selected file
-    UISettings::values.roms_path = QFileInfo(filenames[0]).path();
+    UISettings::values.roms_path = QFileInfo(filenames[0]).path().toStdString();
 
     int remaining = filenames.size();
 
@@ -3584,7 +3589,7 @@ void GMainWindow::OnExit() {
 
 void GMainWindow::OnSaveConfig() {
     system->ApplySettings();
-    config->Save();
+    config->SaveAllValues();
 }
 
 void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) {
@@ -3840,7 +3845,7 @@ void GMainWindow::OnConfigure() {
 
         Settings::values.disabled_addons.clear();
 
-        config = std::make_unique<Config>();
+        config = std::make_unique<QtConfig>();
         UISettings::values.reset_to_defaults = false;
 
         UISettings::values.game_dirs = std::move(old_game_dirs);
@@ -3875,7 +3880,7 @@ void GMainWindow::OnConfigure() {
 
     UISettings::values.configuration_applied = false;
 
-    config->Save();
+    config->SaveAllValues();
 
     if ((UISettings::values.hide_mouse || Settings::values.mouse_panning) && emulation_running) {
         render_window->installEventFilter(render_window);
@@ -4091,7 +4096,7 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file
     UISettings::values.configuration_applied = false;
 
     if (!is_powered_on) {
-        config->Save();
+        config->SaveAllValues();
     }
 }
 
@@ -4324,7 +4329,7 @@ void GMainWindow::OnAlbum() {
     system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer);
 
     const auto filename = QString::fromStdString(album_nca->GetFullPath());
-    UISettings::values.roms_path = QFileInfo(filename).path();
+    UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
     BootGame(filename, AlbumId);
 }
 
@@ -4348,7 +4353,7 @@ void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
     system->GetAppletManager().SetCabinetMode(mode);
 
     const auto filename = QString::fromStdString(cabinet_nca->GetFullPath());
-    UISettings::values.roms_path = QFileInfo(filename).path();
+    UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
     BootGame(filename, CabinetId);
 }
 
@@ -4371,7 +4376,7 @@ void GMainWindow::OnMiiEdit() {
     system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::MiiEdit);
 
     const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath()));
-    UISettings::values.roms_path = QFileInfo(filename).path();
+    UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
     BootGame(filename, MiiEditId);
 }
 
@@ -4396,7 +4401,7 @@ void GMainWindow::OnOpenControllerMenu() {
     system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Controller);
 
     const auto filename = QString::fromStdString((controller_applet_nca->GetFullPath()));
-    UISettings::values.roms_path = QFileInfo(filename).path();
+    UISettings::values.roms_path = QFileInfo(filename).path().toStdString();
     BootGame(filename, ControllerAppletId);
 }
 
@@ -4590,7 +4595,8 @@ void GMainWindow::UpdateStatusBar() {
 
 void GMainWindow::UpdateGPUAccuracyButton() {
     const auto gpu_accuracy = Settings::values.gpu_accuracy.GetValue();
-    const auto gpu_accuracy_text = Config::gpu_accuracy_texts_map.find(gpu_accuracy)->second;
+    const auto gpu_accuracy_text =
+        ConfigurationShared::gpu_accuracy_texts_map.find(gpu_accuracy)->second;
     gpu_accuracy_button->setText(gpu_accuracy_text.toUpper());
     gpu_accuracy_button->setChecked(gpu_accuracy != Settings::GpuAccuracy::Normal);
 }
@@ -4599,31 +4605,32 @@ void GMainWindow::UpdateDockedButton() {
     const auto console_mode = Settings::values.use_docked_mode.GetValue();
     dock_status_button->setChecked(Settings::IsDockedMode());
     dock_status_button->setText(
-        Config::use_docked_mode_texts_map.find(console_mode)->second.toUpper());
+        ConfigurationShared::use_docked_mode_texts_map.find(console_mode)->second.toUpper());
 }
 
 void GMainWindow::UpdateAPIText() {
     const auto api = Settings::values.renderer_backend.GetValue();
-    const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second;
+    const auto renderer_status_text =
+        ConfigurationShared::renderer_backend_texts_map.find(api)->second;
     renderer_status_button->setText(
         api == Settings::RendererBackend::OpenGL
-            ? tr("%1 %2").arg(
-                  renderer_status_text.toUpper(),
-                  Config::shader_backend_texts_map.find(Settings::values.shader_backend.GetValue())
-                      ->second)
+            ? tr("%1 %2").arg(renderer_status_text.toUpper(),
+                              ConfigurationShared::shader_backend_texts_map
+                                  .find(Settings::values.shader_backend.GetValue())
+                                  ->second)
             : renderer_status_text.toUpper());
 }
 
 void GMainWindow::UpdateFilterText() {
     const auto filter = Settings::values.scaling_filter.GetValue();
-    const auto filter_text = Config::scaling_filter_texts_map.find(filter)->second;
+    const auto filter_text = ConfigurationShared::scaling_filter_texts_map.find(filter)->second;
     filter_status_button->setText(filter == Settings::ScalingFilter::Fsr ? tr("FSR")
                                                                          : filter_text.toUpper());
 }
 
 void GMainWindow::UpdateAAText() {
     const auto aa_mode = Settings::values.anti_aliasing.GetValue();
-    const auto aa_text = Config::anti_aliasing_texts_map.find(aa_mode)->second;
+    const auto aa_text = ConfigurationShared::anti_aliasing_texts_map.find(aa_mode)->second;
     aa_status_button->setText(aa_mode == Settings::AntiAliasing::None
                                   ? QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "NO AA"))
                                   : aa_text.toUpper());
@@ -4926,6 +4933,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
 
     UpdateUISettings();
     game_list->SaveInterfaceLayout();
+    UISettings::SaveWindowState();
     hotkey_registry.SaveHotkeys();
 
     // Unload controllers early
@@ -5080,9 +5088,9 @@ static void AdjustLinkColor() {
 }
 
 void GMainWindow::UpdateUITheme() {
-    const QString default_theme =
-        QString::fromUtf8(UISettings::themes[static_cast<size_t>(Config::default_theme)].second);
-    QString current_theme = UISettings::values.theme;
+    const QString default_theme = QString::fromUtf8(
+        UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second);
+    QString current_theme = QString::fromStdString(UISettings::values.theme);
 
     if (current_theme.isEmpty()) {
         current_theme = default_theme;
@@ -5110,7 +5118,7 @@ void GMainWindow::UpdateUITheme() {
         QFile f(theme_uri);
         if (!f.open(QFile::ReadOnly | QFile::Text)) {
             LOG_ERROR(Frontend, "Unable to open style \"{}\", fallback to the default theme",
-                      UISettings::values.theme.toStdString());
+                      UISettings::values.theme);
             current_theme = default_theme;
         }
     }
@@ -5123,7 +5131,7 @@ void GMainWindow::UpdateUITheme() {
         setStyleSheet(ts.readAll());
     } else {
         LOG_ERROR(Frontend, "Unable to set style \"{}\", stylesheet file not found",
-                  UISettings::values.theme.toStdString());
+                  UISettings::values.theme);
         qApp->setStyleSheet({});
         setStyleSheet({});
     }
@@ -5132,27 +5140,28 @@ void GMainWindow::UpdateUITheme() {
 void GMainWindow::LoadTranslation() {
     bool loaded;
 
-    if (UISettings::values.language.isEmpty()) {
+    if (UISettings::values.language.empty()) {
         // If the selected language is empty, use system locale
         loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/"));
     } else {
         // Otherwise load from the specified file
-        loaded = translator.load(UISettings::values.language, QStringLiteral(":/languages/"));
+        loaded = translator.load(QString::fromStdString(UISettings::values.language),
+                                 QStringLiteral(":/languages/"));
     }
 
     if (loaded) {
         qApp->installTranslator(&translator);
     } else {
-        UISettings::values.language = QStringLiteral("en");
+        UISettings::values.language = std::string("en");
     }
 }
 
 void GMainWindow::OnLanguageChanged(const QString& locale) {
-    if (UISettings::values.language != QStringLiteral("en")) {
+    if (UISettings::values.language != std::string("en")) {
         qApp->removeTranslator(&translator);
     }
 
-    UISettings::values.language = locale;
+    UISettings::values.language = locale.toStdString();
     LoadTranslation();
     ui->retranslateUi(this);
     multiplayer_state->retranslateUi();
@@ -5178,7 +5187,7 @@ void GMainWindow::changeEvent(QEvent* event) {
     // UpdateUITheme is a decent work around
     if (event->type() == QEvent::PaletteChange) {
         const QPalette test_palette(qApp->palette());
-        const QString current_theme = UISettings::values.theme;
+        const QString current_theme = QString::fromStdString(UISettings::values.theme);
         // Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too
         static QColor last_window_color;
         const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
@@ -5272,7 +5281,8 @@ static void SetHighDPIAttributes() {
 }
 
 int main(int argc, char* argv[]) {
-    std::unique_ptr<Config> config = std::make_unique<Config>();
+    std::unique_ptr<QtConfig> config = std::make_unique<QtConfig>();
+    UISettings::RestoreWindowState(config);
     bool has_broken_vulkan = false;
     bool is_child = false;
     if (CheckEnvVars(&is_child)) {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 49ee1e1d2e..c989c079d5 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -15,6 +15,7 @@
 
 #include "common/announce_multiplayer_room.h"
 #include "common/common_types.h"
+#include "configuration/qt_config.h"
 #include "input_common/drivers/tas_input.h"
 #include "yuzu/compatibility_list.h"
 #include "yuzu/hotkeys.h"
@@ -26,7 +27,7 @@
 #include <QtDBus/QtDBus>
 #endif
 
-class Config;
+class QtConfig;
 class ClickableLabel;
 class EmuThread;
 class GameList;
@@ -185,7 +186,7 @@ class GMainWindow : public QMainWindow {
 public:
     void filterBarSetChecked(bool state);
     void UpdateUITheme();
-    explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan);
+    explicit GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulkan);
     ~GMainWindow() override;
 
     bool DropAction(QDropEvent* event);
@@ -521,7 +522,7 @@ private:
     QSlider* volume_slider = nullptr;
     QTimer status_bar_update_timer;
 
-    std::unique_ptr<Config> config;
+    std::unique_ptr<QtConfig> config;
 
     // Whether emulation is currently running in yuzu.
     bool emulation_running = false;
diff --git a/src/yuzu/uisettings.cpp b/src/yuzu/uisettings.cpp
index 1c833767b4..7bb7e95af5 100644
--- a/src/yuzu/uisettings.cpp
+++ b/src/yuzu/uisettings.cpp
@@ -1,6 +1,9 @@
 // SPDX-FileCopyrightText: 2016 Citra Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#include <QSettings>
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
 #include "yuzu/uisettings.h"
 
 #ifndef CANNOT_EXPLICITLY_INSTANTIATE
@@ -15,6 +18,8 @@ template class Setting<unsigned long long>;
 } // namespace Settings
 #endif
 
+namespace FS = Common::FS;
+
 namespace UISettings {
 
 const Themes themes{{
@@ -28,10 +33,8 @@ const Themes themes{{
 
 bool IsDarkTheme() {
     const auto& theme = UISettings::values.theme;
-    return theme == QStringLiteral("qdarkstyle") ||
-           theme == QStringLiteral("qdarkstyle_midnight_blue") ||
-           theme == QStringLiteral("colorful_dark") ||
-           theme == QStringLiteral("colorful_midnight_blue");
+    return theme == std::string("qdarkstyle") || theme == std::string("qdarkstyle_midnight_blue") ||
+           theme == std::string("colorful_dark") || theme == std::string("colorful_midnight_blue");
 }
 
 Values values = {};
@@ -52,4 +55,58 @@ u32 CalculateWidth(u32 height, Settings::AspectRatio ratio) {
     return height * 16 / 9;
 }
 
+void SaveWindowState() {
+    const auto window_state_config_loc =
+        FS::PathToUTF8String(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "window_state.ini");
+
+    void(FS::CreateParentDir(window_state_config_loc));
+    QSettings config(QString::fromStdString(window_state_config_loc), QSettings::IniFormat);
+
+    config.setValue(QStringLiteral("geometry"), values.geometry);
+    config.setValue(QStringLiteral("state"), values.state);
+    config.setValue(QStringLiteral("geometryRenderWindow"), values.renderwindow_geometry);
+    config.setValue(QStringLiteral("gameListHeaderState"), values.gamelist_header_state);
+    config.setValue(QStringLiteral("microProfileDialogGeometry"), values.microprofile_geometry);
+
+    config.sync();
+}
+
+void RestoreWindowState(std::unique_ptr<QtConfig>& qtConfig) {
+    const auto window_state_config_loc =
+        FS::PathToUTF8String(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "window_state.ini");
+
+    // Migrate window state from old location
+    if (!FS::Exists(window_state_config_loc) && qtConfig->Exists("UI", "UILayout\\geometry")) {
+        const auto config_loc =
+            FS::PathToUTF8String(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "qt-config.ini");
+        QSettings config(QString::fromStdString(config_loc), QSettings::IniFormat);
+
+        config.beginGroup(QStringLiteral("UI"));
+        config.beginGroup(QStringLiteral("UILayout"));
+        values.geometry = config.value(QStringLiteral("geometry")).toByteArray();
+        values.state = config.value(QStringLiteral("state")).toByteArray();
+        values.renderwindow_geometry =
+            config.value(QStringLiteral("geometryRenderWindow")).toByteArray();
+        values.gamelist_header_state =
+            config.value(QStringLiteral("gameListHeaderState")).toByteArray();
+        values.microprofile_geometry =
+            config.value(QStringLiteral("microProfileDialogGeometry")).toByteArray();
+        config.endGroup();
+        config.endGroup();
+        return;
+    }
+
+    void(FS::CreateParentDir(window_state_config_loc));
+    const QSettings config(QString::fromStdString(window_state_config_loc), QSettings::IniFormat);
+
+    values.geometry = config.value(QStringLiteral("geometry")).toByteArray();
+    values.state = config.value(QStringLiteral("state")).toByteArray();
+    values.renderwindow_geometry =
+        config.value(QStringLiteral("geometryRenderWindow")).toByteArray();
+    values.gamelist_header_state =
+        config.value(QStringLiteral("gameListHeaderState")).toByteArray();
+    values.microprofile_geometry =
+        config.value(QStringLiteral("microProfileDialogGeometry")).toByteArray();
+}
+
 } // namespace UISettings
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 3485a6347b..549a39e1bd 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -14,6 +14,7 @@
 #include "common/common_types.h"
 #include "common/settings.h"
 #include "common/settings_enums.h"
+#include "configuration/qt_config.h"
 
 using Settings::Category;
 using Settings::ConfirmStop;
@@ -37,15 +38,15 @@ namespace UISettings {
 bool IsDarkTheme();
 
 struct ContextualShortcut {
-    QString keyseq;
-    QString controller_keyseq;
+    std::string keyseq;
+    std::string controller_keyseq;
     int context;
     bool repeat;
 };
 
 struct Shortcut {
-    QString name;
-    QString group;
+    std::string name;
+    std::string group;
     ContextualShortcut shortcut;
 };
 
@@ -58,11 +59,19 @@ enum class Theme {
     MidnightBlueColorful,
 };
 
+static constexpr Theme default_theme{
+#ifdef _WIN32
+    Theme::DarkColorful
+#else
+    Theme::DefaultColorful
+#endif
+};
+
 using Themes = std::array<std::pair<const char*, const char*>, 6>;
 extern const Themes themes;
 
 struct GameDir {
-    QString path;
+    std::string path;
     bool deep_scan = false;
     bool expanded = false;
     bool operator==(const GameDir& rhs) const {
@@ -144,15 +153,15 @@ struct Values {
                                             Category::Screenshots};
     Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots};
 
-    QString roms_path;
-    QString symbols_path;
-    QString game_dir_deprecated;
+    std::string roms_path;
+    std::string symbols_path;
+    std::string game_dir_deprecated;
     bool game_dir_deprecated_deepscan;
-    QVector<UISettings::GameDir> game_dirs;
+    QVector<GameDir> game_dirs;
     QStringList recent_files;
-    QString language;
+    std::string language;
 
-    QString theme;
+    std::string theme;
 
     // Shortcut name <Shortcut, context>
     std::vector<Shortcut> shortcuts;
@@ -206,6 +215,54 @@ extern Values values;
 
 u32 CalculateWidth(u32 height, Settings::AspectRatio ratio);
 
+void SaveWindowState();
+void RestoreWindowState(std::unique_ptr<QtConfig>& qtConfig);
+
+// This shouldn't have anything except static initializers (no functions). So
+// QKeySequence(...).toString() is NOT ALLOWED HERE.
+// This must be in alphabetical order according to action name as it must have the same order as
+// UISetting::values.shortcuts, which is alphabetically ordered.
+// clang-format off
+const std::array<Shortcut, 23> default_hotkeys{{
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Mute/Unmute")).toStdString(),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+M"),  std::string("Home+Dpad_Right"), Qt::WindowShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Down")).toStdString(),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("-"),       std::string("Home+Dpad_Down"), Qt::ApplicationShortcut, true}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Audio Volume Up")).toStdString(),          QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("="),       std::string("Home+Dpad_Up"), Qt::ApplicationShortcut, true}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Capture Screenshot")).toStdString(),       QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+P"),  std::string("Screenshot"), Qt::WidgetWithChildrenShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Adapting Filter")).toStdString(),   QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F8"),      std::string("Home+L"), Qt::ApplicationShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change Docked Mode")).toStdString(),       QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F10"),     std::string("Home+X"), Qt::ApplicationShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Change GPU Accuracy")).toStdString(),      QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F9"),      std::string("Home+R"), Qt::ApplicationShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Continue/Pause Emulation")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F4"),      std::string("Home+Plus"), Qt::WindowShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit Fullscreen")).toStdString(),          QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Esc"),     std::string(""), Qt::WindowShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Exit yuzu")).toStdString(),                QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+Q"),  std::string("Home+Minus"), Qt::WindowShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")).toStdString(),               QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F11"),     std::string("Home+B"), Qt::WindowShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")).toStdString(),                QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+O"),  std::string(""), Qt::WidgetWithChildrenShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")).toStdString(),       QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F2"),      std::string("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")).toStdString(),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F6"),      std::string("R+Plus+Minus"), Qt::WindowShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")).toStdString(),           QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("F5"),      std::string("L+Plus+Minus"), Qt::WindowShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")).toStdString(),               QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F7"), std::string(""), Qt::ApplicationShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")).toStdString(),                QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F6"), std::string(""), Qt::ApplicationShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")).toStdString(),           QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F5"), std::string(""), Qt::ApplicationShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Filter Bar")).toStdString(),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F"),  std::string(""), Qt::WindowShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Framerate Limit")).toStdString(),   QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+U"),  std::string("Home+Y"), Qt::ApplicationShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Mouse Panning")).toStdString(),     QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+F9"), std::string(""), Qt::ApplicationShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Renderdoc Capture")).toStdString(), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string(""),        std::string(""), Qt::ApplicationShortcut, false}},
+    {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Toggle Status Bar")).toStdString(),        QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")).toStdString(), {std::string("Ctrl+S"),  std::string(""), Qt::WindowShortcut, false}},
+}};
+// clang-format on
+
 } // namespace UISettings
 
 Q_DECLARE_METATYPE(UISettings::GameDir*);
+
+// These metatype declarations cannot be in common/settings.h because core is devoid of QT
+Q_DECLARE_METATYPE(Settings::CpuAccuracy);
+Q_DECLARE_METATYPE(Settings::GpuAccuracy);
+Q_DECLARE_METATYPE(Settings::FullscreenMode);
+Q_DECLARE_METATYPE(Settings::NvdecEmulation);
+Q_DECLARE_METATYPE(Settings::ResolutionSetup);
+Q_DECLARE_METATYPE(Settings::ScalingFilter);
+Q_DECLARE_METATYPE(Settings::AntiAliasing);
+Q_DECLARE_METATYPE(Settings::RendererBackend);
+Q_DECLARE_METATYPE(Settings::ShaderBackend);
+Q_DECLARE_METATYPE(Settings::AstcRecompression);
+Q_DECLARE_METATYPE(Settings::AstcDecodeMode);
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index 46eddf423f..281e0658e2 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -13,9 +13,6 @@ function(create_resource file output filename)
 endfunction()
 
 add_executable(yuzu-cmd
-    config.cpp
-    config.h
-    default_ini.h
     emu_window/emu_window_sdl2.cpp
     emu_window/emu_window_sdl2.h
     emu_window/emu_window_sdl2_gl.cpp
@@ -25,14 +22,16 @@ add_executable(yuzu-cmd
     emu_window/emu_window_sdl2_vk.cpp
     emu_window/emu_window_sdl2_vk.h
     precompiled_headers.h
+    sdl_config.cpp
+    sdl_config.h
     yuzu.cpp
     yuzu.rc
 )
 
 create_target_directory_groups(yuzu-cmd)
 
-target_link_libraries(yuzu-cmd PRIVATE common core input_common)
 target_link_libraries(yuzu-cmd PRIVATE inih::INIReader glad)
+target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common)
 if (MSVC)
     target_link_libraries(yuzu-cmd PRIVATE getopt)
 endif()
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
deleted file mode 100644
index 0d25ff4001..0000000000
--- a/src/yuzu_cmd/config.cpp
+++ /dev/null
@@ -1,279 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <memory>
-#include <optional>
-#include <sstream>
-#include <INIReader.h>
-#include <SDL.h>
-#include "common/fs/file.h"
-#include "common/fs/fs.h"
-#include "common/fs/path_util.h"
-#include "common/logging/log.h"
-#include "common/settings.h"
-#include "core/hle/service/acc/profile_manager.h"
-#include "input_common/main.h"
-#include "yuzu_cmd/config.h"
-#include "yuzu_cmd/default_ini.h"
-
-namespace FS = Common::FS;
-
-const std::filesystem::path default_config_path =
-    FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "sdl2-config.ini";
-
-Config::Config(std::optional<std::filesystem::path> config_path)
-    : sdl2_config_loc{config_path.value_or(default_config_path)},
-      sdl2_config{std::make_unique<INIReader>(FS::PathToUTF8String(sdl2_config_loc))} {
-    Reload();
-}
-
-Config::~Config() = default;
-
-bool Config::LoadINI(const std::string& default_contents, bool retry) {
-    const auto config_loc_str = FS::PathToUTF8String(sdl2_config_loc);
-    if (sdl2_config->ParseError() < 0) {
-        if (retry) {
-            LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...",
-                        config_loc_str);
-
-            void(FS::CreateParentDir(sdl2_config_loc));
-            void(FS::WriteStringToFile(sdl2_config_loc, FS::FileType::TextFile, default_contents));
-
-            sdl2_config = std::make_unique<INIReader>(config_loc_str);
-
-            return LoadINI(default_contents, false);
-        }
-        LOG_ERROR(Config, "Failed.");
-        return false;
-    }
-    LOG_INFO(Config, "Successfully loaded {}", config_loc_str);
-    return true;
-}
-
-static const std::array<int, Settings::NativeButton::NumButtons> default_buttons = {
-    SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T,
-    SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W,
-    SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
-};
-
-static const std::array<int, Settings::NativeMotion::NumMotions> default_motions = {
-    SDL_SCANCODE_7,
-    SDL_SCANCODE_8,
-};
-
-static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{
-    {
-        SDL_SCANCODE_UP,
-        SDL_SCANCODE_DOWN,
-        SDL_SCANCODE_LEFT,
-        SDL_SCANCODE_RIGHT,
-        SDL_SCANCODE_D,
-    },
-    {
-        SDL_SCANCODE_I,
-        SDL_SCANCODE_K,
-        SDL_SCANCODE_J,
-        SDL_SCANCODE_L,
-        SDL_SCANCODE_D,
-    },
-}};
-
-template <>
-void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
-    std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault());
-    if (setting_value.empty()) {
-        setting_value = setting.GetDefault();
-    }
-    setting = std::move(setting_value);
-}
-
-template <>
-void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
-    setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
-}
-
-template <typename Type, bool ranged>
-void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
-    setting = static_cast<Type>(sdl2_config->GetInteger(group, setting.GetLabel(),
-                                                        static_cast<long>(setting.GetDefault())));
-}
-
-void Config::ReadCategory(Settings::Category category) {
-    for (const auto setting : Settings::values.linkage.by_category[category]) {
-        const char* category_name = [&]() {
-            if (category == Settings::Category::Controls) {
-                // For compatibility with older configs
-                return "ControlsGeneral";
-            } else {
-                return Settings::TranslateCategory(category);
-            }
-        }();
-        std::string setting_value =
-            sdl2_config->Get(category_name, setting->GetLabel(), setting->DefaultToString());
-        setting->LoadString(setting_value);
-    }
-}
-
-void Config::ReadValues() {
-    // Controls
-    ReadCategory(Settings::Category::Controls);
-
-    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
-        auto& player = Settings::values.players.GetValue()[p];
-
-        const auto group = fmt::format("ControlsP{}", p);
-        for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
-            std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
-            player.buttons[i] =
-                sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param);
-            if (player.buttons[i].empty()) {
-                player.buttons[i] = default_param;
-            }
-        }
-
-        for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
-            std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-                default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
-                default_analogs[i][3], default_analogs[i][4], 0.5f);
-            player.analogs[i] =
-                sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param);
-            if (player.analogs[i].empty()) {
-                player.analogs[i] = default_param;
-            }
-        }
-
-        for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
-            const std::string default_param =
-                InputCommon::GenerateKeyboardParam(default_motions[i]);
-            auto& player_motions = player.motions[i];
-
-            player_motions =
-                sdl2_config->Get(group, Settings::NativeMotion::mapping[i], default_param);
-            if (player_motions.empty()) {
-                player_motions = default_param;
-            }
-        }
-
-        player.connected = sdl2_config->GetBoolean(group, "connected", false);
-    }
-
-    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
-        std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
-        Settings::values.debug_pad_buttons[i] = sdl2_config->Get(
-            "ControlsGeneral", std::string("debug_pad_") + Settings::NativeButton::mapping[i],
-            default_param);
-        if (Settings::values.debug_pad_buttons[i].empty())
-            Settings::values.debug_pad_buttons[i] = default_param;
-    }
-
-    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
-        std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
-            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
-            default_analogs[i][3], default_analogs[i][4], 0.5f);
-        Settings::values.debug_pad_analogs[i] = sdl2_config->Get(
-            "ControlsGeneral", std::string("debug_pad_") + Settings::NativeAnalog::mapping[i],
-            default_param);
-        if (Settings::values.debug_pad_analogs[i].empty())
-            Settings::values.debug_pad_analogs[i] = default_param;
-    }
-
-    Settings::values.touchscreen.enabled =
-        sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
-    Settings::values.touchscreen.rotation_angle =
-        sdl2_config->GetInteger("ControlsGeneral", "touch_angle", 0);
-    Settings::values.touchscreen.diameter_x =
-        sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
-    Settings::values.touchscreen.diameter_y =
-        sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
-
-    int num_touch_from_button_maps =
-        sdl2_config->GetInteger("ControlsGeneral", "touch_from_button_map", 0);
-    if (num_touch_from_button_maps > 0) {
-        for (int i = 0; i < num_touch_from_button_maps; ++i) {
-            Settings::TouchFromButtonMap map;
-            map.name = sdl2_config->Get("ControlsGeneral",
-                                        std::string("touch_from_button_maps_") + std::to_string(i) +
-                                            std::string("_name"),
-                                        "default");
-            const int num_touch_maps = sdl2_config->GetInteger(
-                "ControlsGeneral",
-                std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"),
-                0);
-            map.buttons.reserve(num_touch_maps);
-
-            for (int j = 0; j < num_touch_maps; ++j) {
-                std::string touch_mapping =
-                    sdl2_config->Get("ControlsGeneral",
-                                     std::string("touch_from_button_maps_") + std::to_string(i) +
-                                         std::string("_bind_") + std::to_string(j),
-                                     "");
-                map.buttons.emplace_back(std::move(touch_mapping));
-            }
-
-            Settings::values.touch_from_button_maps.emplace_back(std::move(map));
-        }
-    } else {
-        Settings::values.touch_from_button_maps.emplace_back(
-            Settings::TouchFromButtonMap{"default", {}});
-        num_touch_from_button_maps = 1;
-    }
-    Settings::values.touch_from_button_map_index = std::clamp(
-        Settings::values.touch_from_button_map_index.GetValue(), 0, num_touch_from_button_maps - 1);
-
-    ReadCategory(Settings::Category::Audio);
-    ReadCategory(Settings::Category::Core);
-    ReadCategory(Settings::Category::Cpu);
-    ReadCategory(Settings::Category::CpuDebug);
-    ReadCategory(Settings::Category::CpuUnsafe);
-    ReadCategory(Settings::Category::Renderer);
-    ReadCategory(Settings::Category::RendererAdvanced);
-    ReadCategory(Settings::Category::RendererDebug);
-    ReadCategory(Settings::Category::System);
-    ReadCategory(Settings::Category::SystemAudio);
-    ReadCategory(Settings::Category::DataStorage);
-    ReadCategory(Settings::Category::Debugging);
-    ReadCategory(Settings::Category::DebuggingGraphics);
-    ReadCategory(Settings::Category::Miscellaneous);
-    ReadCategory(Settings::Category::Network);
-    ReadCategory(Settings::Category::WebService);
-
-    // Data Storage
-    FS::SetYuzuPath(FS::YuzuPath::NANDDir,
-                    sdl2_config->Get("Data Storage", "nand_directory",
-                                     FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
-    FS::SetYuzuPath(FS::YuzuPath::SDMCDir,
-                    sdl2_config->Get("Data Storage", "sdmc_directory",
-                                     FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
-    FS::SetYuzuPath(FS::YuzuPath::LoadDir,
-                    sdl2_config->Get("Data Storage", "load_directory",
-                                     FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
-    FS::SetYuzuPath(FS::YuzuPath::DumpDir,
-                    sdl2_config->Get("Data Storage", "dump_directory",
-                                     FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
-
-    // Debugging
-    Settings::values.record_frame_times =
-        sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
-
-    const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
-    std::stringstream ss(title_list);
-    std::string line;
-    while (std::getline(ss, line, '|')) {
-        const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
-        const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
-
-        std::stringstream inner_ss(disabled_list);
-        std::string inner_line;
-        std::vector<std::string> out;
-        while (std::getline(inner_ss, inner_line, '|')) {
-            out.push_back(inner_line);
-        }
-
-        Settings::values.disabled_addons.insert_or_assign(title_id, out);
-    }
-}
-
-void Config::Reload() {
-    LoadINI(DefaultINI::sdl2_config_file);
-    ReadValues();
-}
diff --git a/src/yuzu_cmd/config.h b/src/yuzu_cmd/config.h
deleted file mode 100644
index 512591a39c..0000000000
--- a/src/yuzu_cmd/config.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <filesystem>
-#include <memory>
-#include <optional>
-#include <string>
-
-#include "common/settings.h"
-
-class INIReader;
-
-class Config {
-    std::filesystem::path sdl2_config_loc;
-    std::unique_ptr<INIReader> sdl2_config;
-
-    bool LoadINI(const std::string& default_contents = "", bool retry = true);
-    void ReadValues();
-
-public:
-    explicit Config(std::optional<std::filesystem::path> config_path);
-    ~Config();
-
-    void Reload();
-
-private:
-    /**
-     * Applies a value read from the sdl2_config to a Setting.
-     *
-     * @param group The name of the INI group
-     * @param setting The yuzu setting to modify
-     */
-    template <typename Type, bool ranged>
-    void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
-    void ReadCategory(Settings::Category category);
-};
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
deleted file mode 100644
index 119e22183e..0000000000
--- a/src/yuzu_cmd/default_ini.h
+++ /dev/null
@@ -1,553 +0,0 @@
-// SPDX-FileCopyrightText: 2014 Citra Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-namespace DefaultINI {
-
-const char* sdl2_config_file =
-    R"(
-[ControlsP0]
-# The input devices and parameters for each Switch native input
-# The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ...
-# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
-# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
-
-# Indicates if this player should be connected at boot
-# 0 (default): Disabled, 1: Enabled
-connected=
-
-# for button input, the following devices are available:
-#  - "keyboard" (default) for keyboard input. Required parameters:
-#      - "code": the code of the key to bind
-#  - "sdl" for joystick input using SDL. Required parameters:
-#      - "guid": SDL identification GUID of the joystick
-#      - "port": the index of the joystick to bind
-#      - "button"(optional): the index of the button to bind
-#      - "hat"(optional): the index of the hat to bind as direction buttons
-#      - "axis"(optional): the index of the axis to bind
-#      - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
-#      - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
-#          triggered if the axis value crosses
-#      - "direction"(only used for axis): "+" means the button is triggered when the axis value
-#          is greater than the threshold; "-" means the button is triggered when the axis value
-#          is smaller than the threshold
-button_a=
-button_b=
-button_x=
-button_y=
-button_lstick=
-button_rstick=
-button_l=
-button_r=
-button_zl=
-button_zr=
-button_plus=
-button_minus=
-button_dleft=
-button_dup=
-button_dright=
-button_ddown=
-button_lstick_left=
-button_lstick_up=
-button_lstick_right=
-button_lstick_down=
-button_sl=
-button_sr=
-button_home=
-button_screenshot=
-
-# for analog input, the following devices are available:
-#  - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
-#      - "up", "down", "left", "right": sub-devices for each direction.
-#          Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
-#      - "modifier": sub-devices as a modifier.
-#      - "modifier_scale": a float number representing the applied modifier scale to the analog input.
-#          Must be in range of 0.0-1.0. Defaults to 0.5
-#  - "sdl" for joystick input using SDL. Required parameters:
-#      - "guid": SDL identification GUID of the joystick
-#      - "port": the index of the joystick to bind
-#      - "axis_x": the index of the axis to bind as x-axis (default to 0)
-#      - "axis_y": the index of the axis to bind as y-axis (default to 1)
-lstick=
-rstick=
-
-# for motion input, the following devices are available:
-#  - "keyboard" (default) for emulating random motion input from buttons. Required parameters:
-#      - "code": the code of the key to bind
-#  - "sdl" for motion input using SDL. Required parameters:
-#      - "guid": SDL identification GUID of the joystick
-#      - "port": the index of the joystick to bind
-#      - "motion": the index of the motion sensor to bind
-#  - "cemuhookudp" for motion input using Cemu Hook protocol. Required parameters:
-#      - "guid": the IP address of the cemu hook server encoded to a hex string. for example 192.168.0.1 = "c0a80001"
-#      - "port": the port of the cemu hook server
-#      - "pad": the index of the joystick
-#      - "motion": the index of the motion sensor of the joystick to bind
-motionleft=
-motionright=
-
-[ControlsGeneral]
-# To use the debug_pad, prepend `debug_pad_` before each button setting above.
-# i.e. debug_pad_button_a=
-
-# Enable debug pad inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-debug_pad_enabled =
-
-# Enable sdl raw input. Allows to configure up to 8 xinput controllers.
-# 0 (default): Disabled, 1: Enabled
-enable_raw_input =
-
-# Enable yuzu joycon driver instead of SDL drive.
-# 0: Disabled, 1 (default): Enabled
-enable_joycon_driver =
-
-# Emulates an analog input from buttons. Allowing to dial any angle.
-# 0 (default): Disabled, 1: Enabled
-emulate_analog_keyboard =
-
-# Whether to enable or disable vibration
-# 0: Disabled, 1 (default): Enabled
-vibration_enabled=
-
-# Whether to enable or disable accurate vibrations
-# 0 (default): Disabled, 1: Enabled
-enable_accurate_vibrations=
-
-# Enables controller motion inputs
-# 0: Disabled, 1 (default): Enabled
-motion_enabled =
-
-# Defines the udp device's touch screen coordinate system for cemuhookudp devices
-#  - "min_x", "min_y", "max_x", "max_y"
-touch_device=
-
-# for mapping buttons to touch inputs.
-#touch_from_button_map=1
-#touch_from_button_maps_0_name=default
-#touch_from_button_maps_0_count=2
-#touch_from_button_maps_0_bind_0=foo
-#touch_from_button_maps_0_bind_1=bar
-# etc.
-
-# List of Cemuhook UDP servers, delimited by ','.
-# Default: 127.0.0.1:26760
-# Example: 127.0.0.1:26760,123.4.5.67:26761
-udp_input_servers =
-
-# Enable controlling an axis via a mouse input.
-# 0 (default): Off, 1: On
-mouse_panning =
-
-# Set mouse panning horizontal sensitivity.
-# Default: 50.0
-mouse_panning_x_sensitivity =
-
-# Set mouse panning vertical sensitivity.
-# Default: 50.0
-mouse_panning_y_sensitivity =
-
-# Set mouse panning deadzone horizontal counterweight.
-# Default: 0.0
-mouse_panning_deadzone_x_counterweight =
-
-# Set mouse panning deadzone vertical counterweight.
-# Default: 0.0
-mouse_panning_deadzone_y_counterweight =
-
-# Set mouse panning stick decay strength.
-# Default: 22.0
-mouse_panning_decay_strength =
-
-# Set mouse panning stick minimum decay.
-# Default: 5.0
-mouse_panning_minimum_decay =
-
-# Emulate an analog control stick from keyboard inputs.
-# 0 (default): Disabled, 1: Enabled
-emulate_analog_keyboard =
-
-# Enable mouse inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-mouse_enabled =
-
-# Enable keyboard inputs to the guest
-# 0 (default): Disabled, 1: Enabled
-keyboard_enabled =
-
-)"
-    R"(
-[Core]
-# Whether to use multi-core for CPU emulation
-# 0: Disabled, 1 (default): Enabled
-use_multi_core =
-
-# Enable unsafe extended guest system memory layout (8GB DRAM)
-# 0 (default): Disabled, 1: Enabled
-use_unsafe_extended_memory_layout =
-
-[Cpu]
-# Adjusts various optimizations.
-# Auto-select mode enables choice unsafe optimizations.
-# Accurate enables only safe optimizations.
-# Unsafe allows any unsafe optimizations.
-# 0 (default): Auto-select, 1: Accurate, 2: Enable unsafe optimizations
-cpu_accuracy =
-
-# Allow disabling safe optimizations.
-# 0 (default): Disabled, 1: Enabled
-cpu_debug_mode =
-
-# Enable inline page tables optimization (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_page_tables =
-
-# Enable block linking CPU optimization (reduce block dispatcher use during predictable jumps)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_block_linking =
-
-# Enable return stack buffer CPU optimization (reduce block dispatcher use during predictable returns)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_return_stack_buffer =
-
-# Enable fast dispatcher CPU optimization (use a two-tiered dispatcher architecture)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fast_dispatcher =
-
-# Enable context elimination CPU Optimization (reduce host memory use for guest context)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_context_elimination =
-
-# Enable constant propagation CPU optimization (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_const_prop =
-
-# Enable miscellaneous CPU optimizations (basic IR optimization)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_misc_ir =
-
-# Enable reduction of memory misalignment checks (reduce memory fallbacks for misaligned access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_reduce_misalign_checks =
-
-# Enable Host MMU Emulation (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fastmem =
-
-# Enable Host MMU Emulation for exclusive memory instructions (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_fastmem_exclusives =
-
-# Enable fallback on failure of fastmem of exclusive memory instructions (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_recompile_exclusives =
-
-# Enable optimization to ignore invalid memory accesses (faster guest memory access)
-# 0: Disabled, 1 (default): Enabled
-cpuopt_ignore_memory_aborts =
-
-# Enable unfuse FMA (improve performance on CPUs without FMA)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_unfuse_fma =
-
-# Enable faster FRSQRTE and FRECPE
-# Only enabled if cpu_accuracy is set to Unsafe.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_reduce_fp_error =
-
-# Enable faster ASIMD instructions (32 bits only)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_ignore_standard_fpcr =
-
-# Enable inaccurate NaN handling
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_inaccurate_nan =
-
-# Disable address space checks (64 bits only)
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_fastmem_check =
-
-# Enable faster exclusive instructions
-# Only enabled if cpu_accuracy is set to Unsafe. Automatically chosen with cpu_accuracy = Auto-select.
-# 0: Disabled, 1 (default): Enabled
-cpuopt_unsafe_ignore_global_monitor =
-
-)"
-    R"(
-[Renderer]
-# Which backend API to use.
-# 0: OpenGL, 1 (default): Vulkan
-backend =
-
-# Whether to enable asynchronous presentation (Vulkan only)
-# 0 (default): Off, 1: On
-async_presentation =
-
-# Enable graphics API debugging mode.
-# 0 (default): Disabled, 1: Enabled
-debug =
-
-# Enable shader feedback.
-# 0 (default): Disabled, 1: Enabled
-renderer_shader_feedback =
-
-# Enable Nsight Aftermath crash dumps
-# 0 (default): Disabled, 1: Enabled
-nsight_aftermath =
-
-# Disable shader loop safety checks, executing the shader without loop logic changes
-# 0 (default): Disabled, 1: Enabled
-disable_shader_loop_safety_checks =
-
-# Which Vulkan physical device to use (defaults to 0)
-vulkan_device =
-
-# 0: 0.5x (360p/540p) [EXPERIMENTAL]
-# 1: 0.75x (540p/810p) [EXPERIMENTAL]
-# 2 (default): 1x (720p/1080p)
-# 3: 1.5x (1080p/1620p) [EXPERIMENTAL]
-# 4: 2x (1440p/2160p)
-# 5: 3x (2160p/3240p)
-# 6: 4x (2880p/4320p)
-# 7: 5x (3600p/5400p)
-# 8: 6x (4320p/6480p)
-# 9: 7x (5040p/7560p)
-# 10: 8x (5760/8640p)
-resolution_setup =
-
-# Pixel filter to use when up- or down-sampling rendered frames.
-# 0: Nearest Neighbor
-# 1 (default): Bilinear
-# 2: Bicubic
-# 3: Gaussian
-# 4: ScaleForce
-# 5: AMD FidelityFX™️ Super Resolution
-scaling_filter =
-
-# Anti-Aliasing (AA)
-# 0 (default): None, 1: FXAA, 2: SMAA
-anti_aliasing =
-
-# Whether to use fullscreen or borderless window mode
-# 0 (Windows default): Borderless window, 1 (All other default): Exclusive fullscreen
-fullscreen_mode =
-
-# Aspect ratio
-# 0: Default (16:9), 1: Force 4:3, 2: Force 21:9, 3: Force 16:10, 4: Stretch to Window
-aspect_ratio =
-
-# Anisotropic filtering
-# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x
-max_anisotropy =
-
-# Whether to enable VSync or not.
-# OpenGL: Values other than 0 enable VSync
-# Vulkan: FIFO is selected if the requested mode is not supported by the driver.
-# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
-# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
-# Mailbox can have lower latency than FIFO and does not tear but may drop frames.
-# Immediate (no synchronization) just presents whatever is available and can exhibit tearing.
-# 0: Immediate (Off), 1: Mailbox, 2 (Default): FIFO (On), 3: FIFO Relaxed
-use_vsync =
-
-# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is
-# not available and GLASM is selected, GLSL will be used.
-# 0: GLSL, 1 (default): GLASM, 2: SPIR-V
-shader_backend =
-
-# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
-# 0: Off, 1 (default): On
-use_reactive_flushing =
-
-# Whether to allow asynchronous shader building.
-# 0 (default): Off, 1: On
-use_asynchronous_shaders =
-
-# NVDEC emulation.
-# 0: Disabled, 1: CPU Decoding, 2 (default): GPU Decoding
-nvdec_emulation =
-
-# Accelerate ASTC texture decoding.
-# 0: Off, 1 (default): On
-accelerate_astc =
-
-# Decode ASTC textures asynchronously.
-# 0 (default): Off, 1: On
-async_astc =
-
-# Recompress ASTC textures to a different format.
-# 0 (default): Uncompressed, 1: BC1 (Low quality), 2: BC3: (Medium quality)
-async_astc =
-
-# Turns on the speed limiter, which will limit the emulation speed to the desired speed limit value
-# 0: Off, 1: On (default)
-use_speed_limit =
-
-# Limits the speed of the game to run no faster than this value as a percentage of target speed
-# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
-speed_limit =
-
-# Whether to use disk based shader cache
-# 0: Off, 1 (default): On
-use_disk_shader_cache =
-
-# Which gpu accuracy level to use
-# 0: Normal, 1 (default): High, 2: Extreme (Very slow)
-gpu_accuracy =
-
-# Whether to use asynchronous GPU emulation
-# 0 : Off (slow), 1 (default): On (fast)
-use_asynchronous_gpu_emulation =
-
-# Inform the guest that GPU operations completed more quickly than they did.
-# 0: Off, 1 (default): On
-use_fast_gpu_time =
-
-# Whether to use garbage collection or not for GPU caches.
-# 0 (default): Off, 1: On
-use_caches_gc =
-
-# The clear color for the renderer. What shows up on the sides of the bottom screen.
-# Must be in range of 0-255. Defaults to 0 for all.
-bg_red =
-bg_blue =
-bg_green =
-
-)"
-    R"(
-[Audio]
-# Which audio output engine to use.
-# auto (default): Auto-select
-# cubeb: Cubeb audio engine (if available)
-# sdl2: SDL2 audio engine (if available)
-# null: No audio output
-output_engine =
-
-# Which audio device to use.
-# auto (default): Auto-select
-output_device =
-
-# Output volume.
-# 100 (default): 100%, 0; mute
-volume =
-
-[Data Storage]
-# Whether to create a virtual SD card.
-# 1 (default): Yes, 0: No
-use_virtual_sd =
-
-# Whether or not to enable gamecard emulation
-# 1: Yes, 0 (default): No
-gamecard_inserted =
-
-# Whether or not the gamecard should be emulated as the current game
-# If 'gamecard_inserted' is 0 this setting is irrelevant
-# 1: Yes, 0 (default): No
-gamecard_current_game =
-
-# Path to an XCI file to use as the gamecard
-# If 'gamecard_inserted' is 0 this setting is irrelevant
-# If 'gamecard_current_game' is 1 this setting is irrelevant
-gamecard_path =
-
-[System]
-# Whether the system is docked
-# 1 (default): Yes, 0: No
-use_docked_mode =
-
-# Sets the seed for the RNG generator built into the switch
-# rng_seed will be ignored and randomly generated if rng_seed_enabled is false
-rng_seed_enabled =
-rng_seed =
-
-# Sets the current time (in seconds since 12:00 AM Jan 1, 1970) that will be used by the time service
-# This will auto-increment, with the time set being the time the game is started
-# This override will only occur if custom_rtc_enabled is true, otherwise the current time is used
-custom_rtc_enabled =
-custom_rtc =
-
-# Sets the systems language index
-# 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese,
-# 7: Korean, 8: Dutch, 9: Portuguese, 10: Russian, 11: Taiwanese, 12: British English, 13: Canadian French,
-# 14: Latin American Spanish, 15: Simplified Chinese, 16: Traditional Chinese, 17: Brazilian Portuguese
-language_index =
-
-# The system region that yuzu will use during emulation
-# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
-region_index =
-
-# The system time zone that yuzu will use during emulation
-# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone
-time_zone_index =
-
-# Sets the sound output mode.
-# 0: Mono, 1 (default): Stereo, 2: Surround
-sound_index =
-
-[Miscellaneous]
-# A filter which removes logs below a certain logging level.
-# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
-log_filter = *:Trace
-
-# Use developer keys
-# 0 (default): Disabled, 1: Enabled
-use_dev_keys =
-
-[Debugging]
-# Record frame time data, can be found in the log directory. Boolean value
-record_frame_times =
-# Determines whether or not yuzu will dump the ExeFS of all games it attempts to load while loading them
-dump_exefs=false
-# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
-dump_nso=false
-# Determines whether or not yuzu will save the filesystem access log.
-enable_fs_access_log=false
-# Enables verbose reporting services
-reporting_services =
-# Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode
-# false: Retail/Normal Mode (default), true: Kiosk Mode
-quest_flag =
-# Determines whether debug asserts should be enabled, which will throw an exception on asserts.
-# false: Disabled (default), true: Enabled
-use_debug_asserts =
-# Determines whether unimplemented HLE service calls should be automatically stubbed.
-# false: Disabled (default), true: Enabled
-use_auto_stub =
-# Enables/Disables the macro JIT compiler
-disable_macro_jit=false
-# Determines whether to enable the GDB stub and wait for the debugger to attach before running.
-# false: Disabled (default), true: Enabled
-use_gdbstub=false
-# The port to use for the GDB server, if it is enabled.
-gdbstub_port=6543
-
-[WebService]
-# Whether or not to enable telemetry
-# 0: No, 1 (default): Yes
-enable_telemetry =
-# URL for Web API
-web_api_url = https://api.yuzu-emu.org
-# Username and token for yuzu Web Service
-# See https://profile.yuzu-emu.org/ for more info
-yuzu_username =
-yuzu_token =
-
-[Network]
-# Name of the network interface device to use with yuzu LAN play.
-# e.g. On *nix: 'enp7s0', 'wlp6s0u1u3u3', 'lo'
-# e.g. On Windows: 'Ethernet', 'Wi-Fi'
-network_interface =
-
-[AddOns]
-# Used to disable add-ons
-# List of title IDs of games that will have add-ons disabled (separated by '|'):
-title_ids =
-# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
-# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
-)";
-} // namespace DefaultINI
diff --git a/src/yuzu_cmd/sdl_config.cpp b/src/yuzu_cmd/sdl_config.cpp
new file mode 100644
index 0000000000..39fd8050c6
--- /dev/null
+++ b/src/yuzu_cmd/sdl_config.cpp
@@ -0,0 +1,257 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// SDL will break our main function in yuzu-cmd if we don't define this before adding SDL.h
+#define SDL_MAIN_HANDLED
+#include <SDL.h>
+
+#include "input_common/main.h"
+#include "sdl_config.h"
+
+const std::array<int, Settings::NativeButton::NumButtons> SdlConfig::default_buttons = {
+    SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T,
+    SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W,
+    SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
+};
+
+const std::array<int, Settings::NativeMotion::NumMotions> SdlConfig::default_motions = {
+    SDL_SCANCODE_7,
+    SDL_SCANCODE_8,
+};
+
+const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> SdlConfig::default_analogs{
+    {
+        {
+            SDL_SCANCODE_UP,
+            SDL_SCANCODE_DOWN,
+            SDL_SCANCODE_LEFT,
+            SDL_SCANCODE_RIGHT,
+        },
+        {
+            SDL_SCANCODE_I,
+            SDL_SCANCODE_K,
+            SDL_SCANCODE_J,
+            SDL_SCANCODE_L,
+        },
+    }};
+
+const std::array<int, 2> SdlConfig::default_stick_mod = {
+    SDL_SCANCODE_D,
+    0,
+};
+
+const std::array<int, 2> SdlConfig::default_ringcon_analogs{{
+    0,
+    0,
+}};
+
+SdlConfig::SdlConfig(const std::optional<std::string> config_path) {
+    Initialize(config_path);
+    ReadSdlValues();
+    SaveSdlValues();
+}
+
+SdlConfig::~SdlConfig() {
+    if (global) {
+        SdlConfig::SaveAllValues();
+    }
+}
+
+void SdlConfig::ReloadAllValues() {
+    Reload();
+    ReadSdlValues();
+    SaveSdlValues();
+}
+
+void SdlConfig::SaveAllValues() {
+    Save();
+    SaveSdlValues();
+}
+
+void SdlConfig::ReadSdlValues() {
+    ReadSdlControlValues();
+}
+
+void SdlConfig::ReadSdlControlValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+    Settings::values.players.SetGlobal(!IsCustomConfig());
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+        ReadSdlPlayerValues(p);
+    }
+    if (IsCustomConfig()) {
+        EndGroup();
+        return;
+    }
+    ReadDebugControlValues();
+    ReadHidbusValues();
+
+    EndGroup();
+}
+
+void SdlConfig::ReadSdlPlayerValues(const std::size_t player_index) {
+    std::string player_prefix;
+    if (type != ConfigType::InputProfile) {
+        player_prefix.append("player_").append(ToString(player_index)).append("_");
+    }
+
+    auto& player = Settings::values.players.GetValue()[player_index];
+    if (IsCustomConfig()) {
+        const auto profile_name =
+            ReadStringSetting(std::string(player_prefix).append("profile_name"));
+        if (profile_name.empty()) {
+            // Use the global input config
+            player = Settings::values.players.GetValue(true)[player_index];
+            return;
+        }
+    }
+
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        auto& player_buttons = player.buttons[i];
+
+        player_buttons = ReadStringSetting(
+            std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param);
+        if (player_buttons.empty()) {
+            player_buttons = default_param;
+        }
+    }
+
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        auto& player_analogs = player.analogs[i];
+
+        player_analogs = ReadStringSetting(
+            std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param);
+        if (player_analogs.empty()) {
+            player_analogs = default_param;
+        }
+    }
+
+    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+        auto& player_motions = player.motions[i];
+
+        player_motions = ReadStringSetting(
+            std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param);
+        if (player_motions.empty()) {
+            player_motions = default_param;
+        }
+    }
+}
+
+void SdlConfig::ReadDebugControlValues() {
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
+        debug_pad_buttons = ReadStringSetting(
+            std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), default_param);
+        if (debug_pad_buttons.empty()) {
+            debug_pad_buttons = default_param;
+        }
+    }
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
+        debug_pad_analogs = ReadStringSetting(
+            std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), default_param);
+        if (debug_pad_analogs.empty()) {
+            debug_pad_analogs = default_param;
+        }
+    }
+}
+
+void SdlConfig::ReadHidbusValues() {
+    const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+        0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+    auto& ringcon_analogs = Settings::values.ringcon_analogs;
+
+    ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param);
+    if (ringcon_analogs.empty()) {
+        ringcon_analogs = default_param;
+    }
+}
+
+void SdlConfig::SaveSdlValues() {
+    SaveSdlControlValues();
+
+    WriteToIni();
+}
+
+void SdlConfig::SaveSdlControlValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
+
+    Settings::values.players.SetGlobal(!IsCustomConfig());
+    for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
+        SaveSdlPlayerValues(p);
+    }
+    if (IsCustomConfig()) {
+        EndGroup();
+        return;
+    }
+    SaveDebugControlValues();
+    SaveHidbusValues();
+
+    EndGroup();
+}
+
+void SdlConfig::SaveSdlPlayerValues(const std::size_t player_index) {
+    std::string player_prefix;
+    if (type != ConfigType::InputProfile) {
+        player_prefix = std::string("player_").append(ToString(player_index)).append("_");
+    }
+
+    const auto& player = Settings::values.players.GetValue()[player_index];
+    if (IsCustomConfig() && player.profile_name.empty()) {
+        // No custom profile selected
+        return;
+    }
+
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        WriteSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
+                     player.buttons[i], std::make_optional(default_param));
+    }
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        WriteSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
+                     player.analogs[i], std::make_optional(default_param));
+    }
+    for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
+        WriteSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
+                     player.motions[i], std::make_optional(default_param));
+    }
+}
+
+void SdlConfig::SaveDebugControlValues() {
+    for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
+        const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+        WriteSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
+                     Settings::values.debug_pad_buttons[i], std::make_optional(default_param));
+    }
+    for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
+        const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+            default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+            default_analogs[i][3], default_stick_mod[i], 0.5f);
+        WriteSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
+                     Settings::values.debug_pad_analogs[i], std::make_optional(default_param));
+    }
+}
+
+void SdlConfig::SaveHidbusValues() {
+    const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+        0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
+    WriteSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
+                 std::make_optional(default_param));
+}
+
+std::vector<Settings::BasicSetting*>& SdlConfig::FindRelevantList(Settings::Category category) {
+    return Settings::values.linkage.by_category[category];
+}
diff --git a/src/yuzu_cmd/sdl_config.h b/src/yuzu_cmd/sdl_config.h
new file mode 100644
index 0000000000..1fd1c692d5
--- /dev/null
+++ b/src/yuzu_cmd/sdl_config.h
@@ -0,0 +1,49 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "frontend_common/config.h"
+
+class SdlConfig final : public Config {
+public:
+    explicit SdlConfig(std::optional<std::string> config_path);
+    ~SdlConfig() override;
+
+    void ReloadAllValues() override;
+    void SaveAllValues() override;
+
+protected:
+    void ReadSdlValues();
+    void ReadSdlPlayerValues(std::size_t player_index);
+    void ReadSdlControlValues();
+    void ReadHidbusValues() override;
+    void ReadDebugControlValues() override;
+    void ReadPathValues() override {}
+    void ReadShortcutValues() override {}
+    void ReadUIValues() override {}
+    void ReadUIGamelistValues() override {}
+    void ReadUILayoutValues() override {}
+    void ReadMultiplayerValues() override {}
+
+    void SaveSdlValues();
+    void SaveSdlPlayerValues(std::size_t player_index);
+    void SaveSdlControlValues();
+    void SaveHidbusValues() override;
+    void SaveDebugControlValues() override;
+    void SavePathValues() override {}
+    void SaveShortcutValues() override {}
+    void SaveUIValues() override {}
+    void SaveUIGamelistValues() override {}
+    void SaveUILayoutValues() override {}
+    void SaveMultiplayerValues() override {}
+
+    std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;
+
+public:
+    static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
+    static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
+    static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
+    static const std::array<int, 2> default_stick_mod;
+    static const std::array<int, 2> default_ringcon_analogs;
+};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 087cfaa26e..0416d59516 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -29,10 +29,11 @@
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/loader/loader.h"
 #include "core/telemetry_session.h"
+#include "frontend_common/config.h"
 #include "input_common/main.h"
 #include "network/network.h"
+#include "sdl_config.h"
 #include "video_core/renderer_base.h"
-#include "yuzu_cmd/config.h"
 #include "yuzu_cmd/emu_window/emu_window_sdl2.h"
 #include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"
 #include "yuzu_cmd/emu_window/emu_window_sdl2_null.h"
@@ -300,7 +301,7 @@ int main(int argc, char** argv) {
         }
     }
 
-    Config config{config_path};
+    SdlConfig config{config_path};
 
     // apply the log_filter setting
     // the logger was initialized before and doesn't pick up the filter on its own