From b2b4742e611a827f7f1c9eeb31ec26717f6f7b94 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Sat, 23 Dec 2023 19:01:22 -0500
Subject: [PATCH 1/7] android: Update Kotlin version to 1.9.20

---
 src/android/app/build.gradle.kts | 2 +-
 src/android/build.gradle.kts     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index f763c657e..53aafa08c 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -10,7 +10,7 @@ plugins {
     id("com.android.application")
     id("org.jetbrains.kotlin.android")
     id("kotlin-parcelize")
-    kotlin("plugin.serialization") version "1.8.21"
+    kotlin("plugin.serialization") version "1.9.20"
     id("androidx.navigation.safeargs.kotlin")
     id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
 }
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 51e559321..b77906ed6 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -5,7 +5,7 @@
 plugins {
     id("com.android.application") version "8.1.2" apply false
     id("com.android.library") version "8.1.2" apply false
-    id("org.jetbrains.kotlin.android") version "1.8.21" apply false
+    id("org.jetbrains.kotlin.android") version "1.9.20" apply false
 }
 
 tasks.register("clean").configure {

From d3f38ce56cb7d9a980f716e65ceb912f89b36f6b Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Sat, 23 Dec 2023 19:01:57 -0500
Subject: [PATCH 2/7] android: Migrate theme settings to ini

---
 .../features/settings/model/BooleanSetting.kt |  3 +-
 .../features/settings/model/IntSetting.kt     |  4 +-
 .../features/settings/model/Settings.kt       |  1 +
 .../settings/ui/SettingsFragmentPresenter.kt  | 76 ++++++++-----------
 .../yuzu_emu/utils/DirectoryInitialization.kt | 33 ++++++++
 .../org/yuzu/yuzu_emu/utils/PreferenceUtil.kt | 37 +++++++++
 .../org/yuzu/yuzu_emu/utils/ThemeHelper.kt    | 31 ++++----
 .../app/src/main/jni/android_settings.h       |  5 ++
 8 files changed, 128 insertions(+), 62 deletions(-)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index 16f06cd0a..110d15f1c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -18,7 +18,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
     RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
     RENDERER_DEBUG("debug"),
     PICTURE_IN_PICTURE("picture_in_picture"),
-    USE_CUSTOM_RTC("custom_rtc_enabled");
+    USE_CUSTOM_RTC("custom_rtc_enabled"),
+    BLACK_BACKGROUNDS("black_backgrounds");
 
     override fun getBoolean(needsGlobal: Boolean): Boolean =
         NativeConfig.getBoolean(key, needsGlobal)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index df760440f..b0193d83e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -19,7 +19,9 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
     RENDERER_SCREEN_LAYOUT("screen_layout"),
     RENDERER_ASPECT_RATIO("aspect_ratio"),
     AUDIO_OUTPUT_ENGINE("output_engine"),
-    MAX_ANISOTROPY("max_anisotropy");
+    MAX_ANISOTROPY("max_anisotropy"),
+    THEME("theme"),
+    THEME_MODE("theme_mode");
 
     override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index 9551fc05e..360bdaf3e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -54,6 +54,7 @@ object Settings {
     const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
     const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
 
+    // Deprecated theme preference keys
     const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
     const val PREF_THEME = "Theme"
     const val PREF_THEME_MODE = "ThemeMode"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index db1a1076c..2ad2f4966 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -3,10 +3,8 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
-import android.content.SharedPreferences
 import android.os.Build
 import android.widget.Toast
-import androidx.preference.PreferenceManager
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
@@ -29,9 +27,6 @@ class SettingsFragmentPresenter(
 ) {
     private var settingsList = ArrayList<SettingsItem>()
 
-    private val preferences: SharedPreferences
-        get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
     // Extension for altering settings list based on each setting's properties
     fun ArrayList<SettingsItem>.add(key: String) {
         val item = SettingsItem.settingsItems[key]!!
@@ -170,25 +165,19 @@ class SettingsFragmentPresenter(
     private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
         sl.apply {
             val theme: AbstractIntSetting = object : AbstractIntSetting {
-                override fun getInt(needsGlobal: Boolean): Int =
-                    preferences.getInt(Settings.PREF_THEME, 0)
-
+                override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME.getInt()
                 override fun setInt(value: Int) {
-                    preferences.edit()
-                        .putInt(Settings.PREF_THEME, value)
-                        .apply()
+                    IntSetting.THEME.setInt(value)
                     settingsViewModel.setShouldRecreate(true)
                 }
 
-                override val key: String = Settings.PREF_THEME
-                override val isRuntimeModifiable: Boolean = false
-                override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
-                override val defaultValue: Int = 0
-                override fun reset() {
-                    preferences.edit()
-                        .putInt(Settings.PREF_THEME, defaultValue)
-                        .apply()
-                }
+                override val key: String = IntSetting.THEME.key
+                override val isRuntimeModifiable: Boolean = IntSetting.THEME.isRuntimeModifiable
+                override fun getValueAsString(needsGlobal: Boolean): String =
+                    IntSetting.THEME.getValueAsString()
+
+                override val defaultValue: Int = IntSetting.THEME.defaultValue
+                override fun reset() = IntSetting.THEME.setInt(defaultValue)
             }
 
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -214,24 +203,22 @@ class SettingsFragmentPresenter(
             }
 
             val themeMode: AbstractIntSetting = object : AbstractIntSetting {
-                override fun getInt(needsGlobal: Boolean): Int =
-                    preferences.getInt(Settings.PREF_THEME_MODE, -1)
-
+                override fun getInt(needsGlobal: Boolean): Int = IntSetting.THEME_MODE.getInt()
                 override fun setInt(value: Int) {
-                    preferences.edit()
-                        .putInt(Settings.PREF_THEME_MODE, value)
-                        .apply()
+                    IntSetting.THEME_MODE.setInt(value)
                     settingsViewModel.setShouldRecreate(true)
                 }
 
-                override val key: String = Settings.PREF_THEME_MODE
-                override val isRuntimeModifiable: Boolean = false
-                override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString()
-                override val defaultValue: Int = -1
+                override val key: String = IntSetting.THEME_MODE.key
+                override val isRuntimeModifiable: Boolean =
+                    IntSetting.THEME_MODE.isRuntimeModifiable
+
+                override fun getValueAsString(needsGlobal: Boolean): String =
+                    IntSetting.THEME_MODE.getValueAsString()
+
+                override val defaultValue: Int = IntSetting.THEME_MODE.defaultValue
                 override fun reset() {
-                    preferences.edit()
-                        .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
-                        .apply()
+                    IntSetting.THEME_MODE.setInt(defaultValue)
                     settingsViewModel.setShouldRecreate(true)
                 }
             }
@@ -248,25 +235,24 @@ class SettingsFragmentPresenter(
 
             val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
                 override fun getBoolean(needsGlobal: Boolean): Boolean =
-                    preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
+                    BooleanSetting.BLACK_BACKGROUNDS.getBoolean()
 
                 override fun setBoolean(value: Boolean) {
-                    preferences.edit()
-                        .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
-                        .apply()
+                    BooleanSetting.BLACK_BACKGROUNDS.setBoolean(value)
                     settingsViewModel.setShouldRecreate(true)
                 }
 
-                override val key: String = Settings.PREF_BLACK_BACKGROUNDS
-                override val isRuntimeModifiable: Boolean = false
-                override fun getValueAsString(needsGlobal: Boolean): String =
-                    getBoolean().toString()
+                override val key: String = BooleanSetting.BLACK_BACKGROUNDS.key
+                override val isRuntimeModifiable: Boolean =
+                    BooleanSetting.BLACK_BACKGROUNDS.isRuntimeModifiable
 
-                override val defaultValue: Boolean = false
+                override fun getValueAsString(needsGlobal: Boolean): String =
+                    BooleanSetting.BLACK_BACKGROUNDS.getValueAsString()
+
+                override val defaultValue: Boolean = BooleanSetting.BLACK_BACKGROUNDS.defaultValue
                 override fun reset() {
-                    preferences.edit()
-                        .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
-                        .apply()
+                    BooleanSetting.BLACK_BACKGROUNDS
+                        .setBoolean(BooleanSetting.BLACK_BACKGROUNDS.defaultValue)
                     settingsViewModel.setShouldRecreate(true)
                 }
             }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index 0197fd712..d4a9da06f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -3,9 +3,14 @@
 
 package org.yuzu.yuzu_emu.utils
 
+import androidx.preference.PreferenceManager
 import java.io.IOException
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference
 
 object DirectoryInitialization {
     private var userPath: String? = null
@@ -17,6 +22,7 @@ object DirectoryInitialization {
             initializeInternalStorage()
             NativeLibrary.initializeSystem(false)
             NativeConfig.initializeGlobalConfig()
+            migrateSettings()
             areDirectoriesReady = true
         }
     }
@@ -35,4 +41,31 @@ object DirectoryInitialization {
             e.printStackTrace()
         }
     }
+
+    private fun migrateSettings() {
+        val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+        var saveConfig = false
+        val theme = preferences.migratePreference<Int>(Settings.PREF_THEME)
+        if (theme != null) {
+            IntSetting.THEME.setInt(theme)
+            saveConfig = true
+        }
+
+        val themeMode = preferences.migratePreference<Int>(Settings.PREF_THEME_MODE)
+        if (themeMode != null) {
+            IntSetting.THEME_MODE.setInt(themeMode)
+            saveConfig = true
+        }
+
+        val blackBackgrounds =
+            preferences.migratePreference<Boolean>(Settings.PREF_BLACK_BACKGROUNDS)
+        if (blackBackgrounds != null) {
+            BooleanSetting.BLACK_BACKGROUNDS.setBoolean(blackBackgrounds)
+            saveConfig = true
+        }
+
+        if (saveConfig) {
+            NativeConfig.saveGlobalConfig()
+        }
+    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt
new file mode 100644
index 000000000..a233ba25c
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PreferenceUtil.kt
@@ -0,0 +1,37 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+import android.content.SharedPreferences
+
+object PreferenceUtil {
+    /**
+     * Retrieves a shared preference value and then deletes the value in storage.
+     * @param key Associated key for the value in this preferences instance
+     * @return Typed value associated with [key]. Null if no such key exists.
+     */
+    inline fun <reified T> SharedPreferences.migratePreference(key: String): T? {
+        if (!this.contains(key)) {
+            return null
+        }
+
+        val value: Any = when (T::class) {
+            String::class -> this.getString(key, "")!!
+
+            Boolean::class -> this.getBoolean(key, false)
+
+            Int::class -> this.getInt(key, 0)
+
+            Float::class -> this.getFloat(key, 0f)
+
+            Long::class -> this.getLong(key, 0)
+
+            else -> throw IllegalStateException("Tried to migrate preference with invalid type!")
+        }
+        deletePreference(key)
+        return value as T
+    }
+
+    fun SharedPreferences.deletePreference(key: String) = this.edit().remove(key).apply()
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
index f312e24cf..792f6a253 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
@@ -10,33 +10,26 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.app.AppCompatDelegate
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsControllerCompat
-import androidx.preference.PreferenceManager
 import kotlin.math.roundToInt
 import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.ui.main.ThemeProvider
 
 object ThemeHelper {
     const val SYSTEM_BAR_ALPHA = 0.9f
 
-    private const val DEFAULT = 0
-    private const val MATERIAL_YOU = 1
-
     fun setTheme(activity: AppCompatActivity) {
-        val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
         setThemeMode(activity)
-        when (preferences.getInt(Settings.PREF_THEME, 0)) {
-            DEFAULT -> activity.setTheme(R.style.Theme_Yuzu_Main)
-            MATERIAL_YOU -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
+        when (Theme.from(IntSetting.THEME.getInt())) {
+            Theme.Default -> activity.setTheme(R.style.Theme_Yuzu_Main)
+            Theme.MaterialYou -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
         }
 
         // Using a specific night mode check because this could apply incorrectly when using the
         // light app mode, dark system mode, and black backgrounds. Launching the settings activity
         // will then show light mode colors/navigation bars but with black backgrounds.
-        if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) &&
-            isNightMode(activity)
-        ) {
+        if (BooleanSetting.BLACK_BACKGROUNDS.getBoolean() && isNightMode(activity)) {
             activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
         }
     }
@@ -60,8 +53,7 @@ object ThemeHelper {
     }
 
     fun setThemeMode(activity: AppCompatActivity) {
-        val themeMode = PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
-            .getInt(Settings.PREF_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
+        val themeMode = IntSetting.THEME_MODE.getInt()
         activity.delegate.localNightMode = themeMode
         val windowController = WindowCompat.getInsetsController(
             activity.window,
@@ -95,3 +87,12 @@ object ThemeHelper {
         windowController.isAppearanceLightNavigationBars = false
     }
 }
+
+enum class Theme(val int: Int) {
+    Default(0),
+    MaterialYou(1);
+
+    companion object {
+        fun from(int: Int): Theme = entries.firstOrNull { it.int == int } ?: Default
+    }
+}
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index 3733f5a3c..1e4906b9a 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -33,6 +33,11 @@ struct Values {
 
     Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path",
                                                                 Settings::Category::GpuDriver};
+
+    Settings::Setting<s32> theme{linkage, 0, "theme", Settings::Category::Android};
+    Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
+    Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
+                                              Settings::Category::Android};
 };
 
 extern Values values;

From 051afd21e730005ea24ce3c8743962beb3085031 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Thu, 28 Dec 2023 23:24:23 -0500
Subject: [PATCH 3/7] frontend_common: config: Refactor WriteSetting to
 stricter types

Previously this could cause problems if a version of the template generated for WriteSetting didn't use the type you needed (e.g. floating point values). Now these are all ready without having to be used within frontend_common first.
---
 .../app/src/main/jni/android_config.cpp       |   5 +-
 src/frontend_common/config.cpp                | 196 ++++++++++--------
 src/frontend_common/config.h                  |  28 ++-
 src/yuzu/configuration/qt_config.cpp          |  69 +++---
 src/yuzu_cmd/sdl_config.cpp                   |  26 +--
 5 files changed, 189 insertions(+), 135 deletions(-)

diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
index 9c3a5a9b2..fb17ab6f6 100644
--- a/src/android/app/src/main/jni/android_config.cpp
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -114,8 +114,9 @@ void AndroidConfig::SavePathValues() {
     for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
         SetArrayIndex(i);
         const auto& game_dir = AndroidSettings::values.game_dirs[i];
-        WriteSetting(std::string("path"), game_dir.path);
-        WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false));
+        WriteStringSetting(std::string("path"), game_dir.path);
+        WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan,
+                            std::make_optional(false));
     }
     EndArray();
 
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp
index d9f99148b..51576b4ee 100644
--- a/src/frontend_common/config.cpp
+++ b/src/frontend_common/config.cpp
@@ -403,59 +403,63 @@ void Config::SavePlayerValues(const std::size_t player_index) {
             // No custom profile selected
             return;
         }
-        WriteSetting(std::string(player_prefix).append("profile_name"), player.profile_name,
-                     std::make_optional(std::string("")));
+        WriteStringSetting(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)));
+    WriteIntegerSetting(
+        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));
+        WriteBooleanSetting(std::string(player_prefix).append("connected"), player.connected,
+                            std::make_optional(player_index == 0));
+        WriteIntegerSetting(std::string(player_prefix).append("vibration_enabled"),
+                            player.vibration_enabled, std::make_optional(true));
+        WriteIntegerSetting(std::string(player_prefix).append("vibration_strength"),
+                            player.vibration_strength, std::make_optional(100));
+        WriteIntegerSetting(std::string(player_prefix).append("body_color_left"),
+                            player.body_color_left,
+                            std::make_optional(Settings::JOYCON_BODY_NEON_BLUE));
+        WriteIntegerSetting(std::string(player_prefix).append("body_color_right"),
+                            player.body_color_right,
+                            std::make_optional(Settings::JOYCON_BODY_NEON_RED));
+        WriteIntegerSetting(std::string(player_prefix).append("button_color_left"),
+                            player.button_color_left,
+                            std::make_optional(Settings::JOYCON_BUTTONS_NEON_BLUE));
+        WriteIntegerSetting(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));
+    WriteBooleanSetting(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)));
+    WriteIntegerSetting(std::string("touchscreen_angle"), touchscreen.rotation_angle,
+                        std::make_optional(static_cast<u32>(0)));
+    WriteIntegerSetting(std::string("touchscreen_diameter_x"), touchscreen.diameter_x,
+                        std::make_optional(static_cast<u32>(15)));
+    WriteIntegerSetting(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")));
+        WriteStringSetting(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]);
+            WriteStringSetting(std::string("bind"),
+                               Settings::values.touch_from_button_maps[p].buttons[q]);
         }
         EndArray(); // entries
     }
@@ -520,16 +524,16 @@ void Config::SaveCoreValues() {
 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)));
+    WriteStringSetting(std::string("nand_directory"), FS::GetYuzuPathString(FS::YuzuPath::NANDDir),
+                       std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::NANDDir)));
+    WriteStringSetting(std::string("sdmc_directory"), FS::GetYuzuPathString(FS::YuzuPath::SDMCDir),
+                       std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::SDMCDir)));
+    WriteStringSetting(std::string("load_directory"), FS::GetYuzuPathString(FS::YuzuPath::LoadDir),
+                       std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::LoadDir)));
+    WriteStringSetting(std::string("dump_directory"), FS::GetYuzuPathString(FS::YuzuPath::DumpDir),
+                       std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
+    WriteStringSetting(std::string("tas_directory"), FS::GetYuzuPathString(FS::YuzuPath::TASDir),
+                       std::make_optional(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
 
     WriteCategory(Settings::Category::DataStorage);
 
@@ -540,7 +544,7 @@ 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);
+    WriteBooleanSetting(std::string("record_frame_times"), Settings::values.record_frame_times);
 
     WriteCategory(Settings::Category::Debugging);
     WriteCategory(Settings::Category::DebuggingGraphics);
@@ -564,11 +568,13 @@ void Config::SaveDisabledAddOnValues() {
     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)));
+        WriteIntegerSetting(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("")));
+            WriteStringSetting(std::string("d"), elem.second[j],
+                               std::make_optional(std::string("")));
         }
         EndArray(); // disabled
         ++i;
@@ -609,8 +615,8 @@ void Config::SaveRendererValues() {
 void Config::SaveScreenshotValues() {
     BeginGroup(Settings::TranslateCategory(Settings::Category::Screenshots));
 
-    WriteSetting(std::string("screenshot_path"),
-                 FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir));
+    WriteStringSetting(std::string("screenshot_path"),
+                       FS::GetYuzuPathString(FS::YuzuPath::ScreenshotsDir));
     WriteCategory(Settings::Category::Screenshots);
 
     EndGroup();
@@ -746,46 +752,70 @@ bool Config::Exists(const std::string& section, const std::string& key) const {
     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()));
-        }
+void Config::WriteBooleanSetting(const std::string& key, const bool& value,
+                                 const std::optional<bool>& default_value,
+                                 const std::optional<bool>& use_global) {
+    std::optional<std::string> string_default = std::nullopt;
+    if (default_value.has_value()) {
+        string_default = std::make_optional(ToString(default_value.value()));
     }
+    WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
+}
 
-    if (default_value.has_value() && use_global.has_value()) {
+template <typename T>
+std::enable_if_t<std::is_integral_v<T>> Config::WriteIntegerSetting(
+    const std::string& key, const T& value, const std::optional<T>& default_value,
+    const std::optional<bool>& use_global) {
+    std::optional<std::string> string_default = std::nullopt;
+    if (default_value.has_value()) {
+        string_default = std::make_optional(ToString(default_value.value()));
+    }
+    WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
+}
+
+void Config::WriteDoubleSetting(const std::string& key, const double& value,
+                                const std::optional<double>& default_value,
+                                const std::optional<bool>& use_global) {
+    std::optional<std::string> string_default = std::nullopt;
+    if (default_value.has_value()) {
+        string_default = std::make_optional(ToString(default_value.value()));
+    }
+    WritePreparedSetting(key, AdjustOutputString(ToString(value)), string_default, use_global);
+}
+
+void Config::WriteStringSetting(const std::string& key, const std::string& value,
+                                const std::optional<std::string>& default_value,
+                                const std::optional<bool>& use_global) {
+    std::optional string_default = default_value;
+    if (default_value.has_value()) {
+        string_default.value().append(AdjustOutputString(default_value.value()));
+    }
+    WritePreparedSetting(key, AdjustOutputString(value), string_default, use_global);
+}
+
+void Config::WritePreparedSetting(const std::string& key, const std::string& adjusted_value,
+                                  const std::optional<std::string>& adjusted_default_value,
+                                  const std::optional<bool>& use_global) {
+    std::string full_key = GetFullKey(key, false);
+    if (adjusted_default_value.has_value() && use_global.has_value()) {
         if (!global) {
-            WriteSettingInternal(std::string(full_key).append("\\global"),
-                                 ToString(use_global.value()));
+            WriteString(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);
+            WriteString(std::string(full_key).append("\\default"),
+                        ToString(adjusted_default_value == adjusted_value));
+            WriteString(full_key, adjusted_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 if (adjusted_default_value.has_value() && !use_global.has_value()) {
+        WriteString(std::string(full_key).append("\\default"),
+                    ToString(adjusted_default_value == adjusted_value));
+        WriteString(full_key, adjusted_value);
     } else {
-        WriteSettingInternal(full_key, saved_value);
+        WriteString(full_key, adjusted_value);
     }
 }
 
-void Config::WriteSettingInternal(const std::string& key, const std::string& value) {
+void Config::WriteString(const std::string& key, const std::string& value) {
     config->SetValue(GetSection().c_str(), key.c_str(), value.c_str());
 }
 
@@ -861,17 +891,17 @@ void Config::WriteSettingGeneric(const Settings::BasicSetting* const setting) {
     std::string key = AdjustKey(setting->GetLabel());
     if (setting->Switchable()) {
         if (!global) {
-            WriteSetting(std::string(key).append("\\use_global"), setting->UsingGlobal());
+            WriteBooleanSetting(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());
+            WriteBooleanSetting(std::string(key).append("\\default"),
+                                setting->ToString() == setting->DefaultToString());
+            WriteStringSetting(key, setting->ToString());
         }
     } else if (global) {
-        WriteSetting(std::string(key).append("\\default"),
-                     setting->ToString() == setting->DefaultToString());
-        WriteSetting(key, setting->ToString());
+        WriteBooleanSetting(std::string(key).append("\\default"),
+                            setting->ToString() == setting->DefaultToString());
+        WriteStringSetting(key, setting->ToString());
     }
 }
 
diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h
index b3812af17..e73cf6929 100644
--- a/src/frontend_common/config.h
+++ b/src/frontend_common/config.h
@@ -154,11 +154,20 @@ protected:
      * @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 WriteBooleanSetting(const std::string& key, const bool& value,
+                             const std::optional<bool>& default_value = std::nullopt,
+                             const std::optional<bool>& use_global = std::nullopt);
+    template <typename T>
+    std::enable_if_t<std::is_integral_v<T>> WriteIntegerSetting(
+        const std::string& key, const T& value,
+        const std::optional<T>& default_value = std::nullopt,
+        const std::optional<bool>& use_global = std::nullopt);
+    void WriteDoubleSetting(const std::string& key, const double& value,
+                            const std::optional<double>& default_value = std::nullopt,
+                            const std::optional<bool>& use_global = std::nullopt);
+    void WriteStringSetting(const std::string& key, const std::string& value,
+                            const std::optional<std::string>& default_value = std::nullopt,
+                            const std::optional<bool>& use_global = std::nullopt);
 
     void ReadCategory(Settings::Category category);
     void WriteCategory(Settings::Category category);
@@ -175,8 +184,10 @@ protected:
             return value_ ? "true" : "false";
         } else if constexpr (std::is_same_v<T, u64>) {
             return std::to_string(static_cast<u64>(value_));
-        } else {
+        } else if constexpr (std::is_same_v<T, s64>) {
             return std::to_string(static_cast<s64>(value_));
+        } else {
+            return std::to_string(value_);
         }
     }
 
@@ -197,6 +208,11 @@ protected:
     const bool global;
 
 private:
+    void WritePreparedSetting(const std::string& key, const std::string& adjusted_value,
+                              const std::optional<std::string>& adjusted_default_value,
+                              const std::optional<bool>& use_global);
+    void WriteString(const std::string& key, const std::string& value);
+
     inline static std::array<char, 19> special_characters = {'!', '#', '$',  '%',  '^', '&', '*',
                                                              '|', ';', '\'', '\"', ',', '<', '.',
                                                              '>', '?', '`',  '~',  '='};
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp
index a71000b72..6aca71d7c 100644
--- a/src/yuzu/configuration/qt_config.cpp
+++ b/src/yuzu/configuration/qt_config.cpp
@@ -348,43 +348,45 @@ void QtConfig::SaveQtPlayerValues(const std::size_t player_index) {
 
     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));
+        WriteStringSetting(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));
+        WriteStringSetting(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));
+        WriteStringSetting(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));
+        WriteStringSetting(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));
+        WriteStringSetting(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));
+    WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
+                       std::make_optional(default_param));
 }
 
 void QtConfig::SaveQtControlValues() {
@@ -409,19 +411,20 @@ void QtConfig::SavePathValues() {
 
     WriteCategory(Settings::Category::Paths);
 
-    WriteSetting(std::string("romsPath"), UISettings::values.roms_path);
+    WriteStringSetting(std::string("romsPath"), UISettings::values.roms_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));
+        WriteStringSetting(std::string("path"), game_dir.path);
+        WriteBooleanSetting(std::string("deep_scan"), game_dir.deep_scan,
+                            std::make_optional(false));
+        WriteBooleanSetting(std::string("expanded"), game_dir.expanded, std::make_optional(true));
     }
     EndArray();
 
-    WriteSetting(std::string("recentFiles"),
-                 UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
+    WriteStringSetting(std::string("recentFiles"),
+                       UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
 
     EndGroup();
 }
@@ -438,14 +441,14 @@ void QtConfig::SaveShortcutValues() {
         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));
+        WriteStringSetting(std::string("KeySeq"), shortcut.keyseq,
+                           std::make_optional(default_hotkey.keyseq));
+        WriteStringSetting(std::string("Controller_KeySeq"), shortcut.controller_keyseq,
+                           std::make_optional(default_hotkey.controller_keyseq));
+        WriteIntegerSetting(std::string("Context"), shortcut.context,
+                            std::make_optional(default_hotkey.context));
+        WriteBooleanSetting(std::string("Repeat"), shortcut.repeat,
+                            std::make_optional(default_hotkey.repeat));
 
         EndGroup(); // name
         EndGroup(); // group
@@ -460,9 +463,10 @@ void QtConfig::SaveUIValues() {
     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)));
+    WriteStringSetting(
+        std::string("theme"), UISettings::values.theme,
+        std::make_optional(std::string(
+            UISettings::themes[static_cast<size_t>(UISettings::default_theme)].second)));
 
     SaveUIGamelistValues();
     SaveUILayoutValues();
@@ -482,7 +486,7 @@ void QtConfig::SaveUIGamelistValues() {
     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]);
+        WriteIntegerSetting(std::string("program_id"), UISettings::values.favorited_ids[i]);
     }
     EndArray(); // favorites
 
@@ -506,14 +510,15 @@ void QtConfig::SaveMultiplayerValues() {
     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]);
+        WriteStringSetting(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]);
+        WriteStringSetting(std::string("ip"), UISettings::values.multiplayer_ban_list.second[i]);
     }
     EndArray(); // ip_ban_list
 
diff --git a/src/yuzu_cmd/sdl_config.cpp b/src/yuzu_cmd/sdl_config.cpp
index 39fd8050c..e81bf5d45 100644
--- a/src/yuzu_cmd/sdl_config.cpp
+++ b/src/yuzu_cmd/sdl_config.cpp
@@ -213,43 +213,45 @@ void SdlConfig::SaveSdlPlayerValues(const std::size_t player_index) {
 
     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));
+        WriteStringSetting(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));
+        WriteStringSetting(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));
+        WriteStringSetting(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));
+        WriteStringSetting(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));
+        WriteStringSetting(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));
+    WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
+                       std::make_optional(default_param));
 }
 
 std::vector<Settings::BasicSetting*>& SdlConfig::FindRelevantList(Settings::Category category) {

From d163b182081401dbdcc5fc92c1692ff3209f2353 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Sun, 24 Dec 2023 15:42:28 -0500
Subject: [PATCH 4/7] android: Migrate in-game overlay settings to ini

---
 .../yuzu_emu/activities/EmulationActivity.kt  |   6 +
 .../features/settings/model/BooleanSetting.kt |   8 +-
 .../features/settings/model/IntSetting.kt     |   4 +-
 .../features/settings/model/Settings.kt       |  39 +-
 .../yuzu_emu/fragments/EmulationFragment.kt   |  89 +-
 .../org/yuzu/yuzu_emu/overlay/InputOverlay.kt | 984 +++++++-----------
 .../overlay/InputOverlayDrawableButton.kt     |   3 +-
 .../overlay/InputOverlayDrawableJoystick.kt   |   4 +-
 .../yuzu_emu/overlay/model/OverlayControl.kt  | 188 ++++
 .../overlay/model/OverlayControlData.kt       |  19 +
 .../overlay/model/OverlayControlDefault.kt    |  13 +
 .../yuzu_emu/overlay/model/OverlayLayout.kt   |  10 +
 .../yuzu_emu/utils/DirectoryInitialization.kt | 142 +++
 .../yuzu_emu/utils/EmulationMenuSettings.kt   |  50 -
 .../org/yuzu/yuzu_emu/utils/NativeConfig.kt   |  18 +
 .../jni/android_common/android_common.cpp     |   9 +
 .../main/jni/android_common/android_common.h  |   3 +
 .../app/src/main/jni/android_config.cpp       |  62 ++
 src/android/app/src/main/jni/android_config.h |   2 +
 .../app/src/main/jni/android_settings.h       |  25 +
 src/android/app/src/main/jni/id_cache.cpp     |  79 ++
 src/android/app/src/main/jni/id_cache.h       |  12 +
 .../app/src/main/jni/native_config.cpp        |  70 ++
 .../app/src/main/res/values/arrays.xml        |  10 +-
 .../app/src/main/res/values/integers.xml      | 204 ++--
 src/common/settings.cpp                       |   2 +
 src/common/settings_common.h                  |   1 +
 27 files changed, 1209 insertions(+), 847 deletions(-)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt
 delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 9b08f008d..93c8ce922 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -49,6 +49,7 @@ import org.yuzu.yuzu_emu.utils.ForegroundService
 import org.yuzu.yuzu_emu.utils.InputHandler
 import org.yuzu.yuzu_emu.utils.Log
 import org.yuzu.yuzu_emu.utils.MemoryUtil
+import org.yuzu.yuzu_emu.utils.NativeConfig
 import org.yuzu.yuzu_emu.utils.NfcReader
 import org.yuzu.yuzu_emu.utils.ThemeHelper
 import java.text.NumberFormat
@@ -170,6 +171,11 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
         stopMotionSensorListener()
     }
 
+    override fun onStop() {
+        super.onStop()
+        NativeConfig.saveGlobalConfig()
+    }
+
     override fun onUserLeaveHint() {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
             if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index 110d15f1c..86bd33672 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -19,7 +19,13 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
     RENDERER_DEBUG("debug"),
     PICTURE_IN_PICTURE("picture_in_picture"),
     USE_CUSTOM_RTC("custom_rtc_enabled"),
-    BLACK_BACKGROUNDS("black_backgrounds");
+    BLACK_BACKGROUNDS("black_backgrounds"),
+    JOYSTICK_REL_CENTER("joystick_rel_center"),
+    DPAD_SLIDE("dpad_slide"),
+    HAPTIC_FEEDBACK("haptic_feedback"),
+    SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),
+    SHOW_INPUT_OVERLAY("show_input_overlay"),
+    TOUCHSCREEN("touchscreen");
 
     override fun getBoolean(needsGlobal: Boolean): Boolean =
         NativeConfig.getBoolean(key, needsGlobal)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
index b0193d83e..16fb87614 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
@@ -21,7 +21,9 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
     AUDIO_OUTPUT_ENGINE("output_engine"),
     MAX_ANISOTROPY("max_anisotropy"),
     THEME("theme"),
-    THEME_MODE("theme_mode");
+    THEME_MODE("theme_mode"),
+    OVERLAY_SCALE("control_scale"),
+    OVERLAY_OPACITY("control_opacity");
 
     override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index 360bdaf3e..43caac989 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -15,18 +15,10 @@ object Settings {
         SECTION_DEBUG(R.string.preferences_debug);
     }
 
+    const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
     const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
 
-    const val PREF_OVERLAY_VERSION = "OverlayVersion"
-    const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
-    const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
-    const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
-    val overlayLayoutPrefs = listOf(
-        PREF_LANDSCAPE_OVERLAY_VERSION,
-        PREF_PORTRAIT_OVERLAY_VERSION,
-        PREF_FOLDABLE_OVERLAY_VERSION
-    )
-
+    // Deprecated input overlay preference keys
     const val PREF_CONTROL_SCALE = "controlScale"
     const val PREF_CONTROL_OPACITY = "controlOpacity"
     const val PREF_TOUCH_ENABLED = "isTouchEnabled"
@@ -47,24 +39,12 @@ object Settings {
     const val PREF_BUTTON_STICK_R = "buttonToggle14"
     const val PREF_BUTTON_HOME = "buttonToggle15"
     const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
-
     const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
     const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
     const val PREF_MENU_SETTINGS_HAPTICS = "EmulationMenuSettings_Haptics"
     const val PREF_MENU_SETTINGS_SHOW_FPS = "EmulationMenuSettings_ShowFps"
     const val PREF_MENU_SETTINGS_SHOW_OVERLAY = "EmulationMenuSettings_ShowOverlay"
-
-    // Deprecated theme preference keys
-    const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
-    const val PREF_THEME = "Theme"
-    const val PREF_THEME_MODE = "ThemeMode"
-    const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
-
     val overlayPreferences = listOf(
-        PREF_OVERLAY_VERSION,
-        PREF_CONTROL_SCALE,
-        PREF_CONTROL_OPACITY,
-        PREF_TOUCH_ENABLED,
         PREF_BUTTON_A,
         PREF_BUTTON_B,
         PREF_BUTTON_X,
@@ -84,6 +64,21 @@ object Settings {
         PREF_BUTTON_STICK_R
     )
 
+    // Deprecated layout preference keys
+    const val PREF_LANDSCAPE_SUFFIX = "_Landscape"
+    const val PREF_PORTRAIT_SUFFIX = "_Portrait"
+    const val PREF_FOLDABLE_SUFFIX = "_Foldable"
+    val overlayLayoutSuffixes = listOf(
+        PREF_LANDSCAPE_SUFFIX,
+        PREF_PORTRAIT_SUFFIX,
+        PREF_FOLDABLE_SUFFIX
+    )
+
+    // Deprecated theme preference keys
+    const val PREF_THEME = "Theme"
+    const val PREF_THEME_MODE = "ThemeMode"
+    const val PREF_BLACK_BACKGROUNDS = "BlackBackgrounds"
+
     const val LayoutOption_Unspecified = 0
     const val LayoutOption_MobilePortrait = 4
     const val LayoutOption_MobileLandscape = 5
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index d7b38f62d..6e5dd1dba 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -7,7 +7,6 @@ import android.annotation.SuppressLint
 import android.app.AlertDialog
 import android.content.Context
 import android.content.DialogInterface
-import android.content.SharedPreferences
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
 import android.net.Uri
@@ -33,7 +32,6 @@ import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.navigation.findNavController
 import androidx.navigation.fragment.navArgs
-import androidx.preference.PreferenceManager
 import androidx.window.layout.FoldingFeature
 import androidx.window.layout.WindowInfoTracker
 import androidx.window.layout.WindowLayoutInfo
@@ -46,22 +44,22 @@ import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.activities.EmulationActivity
 import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
 import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.DriverViewModel
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.model.EmulationViewModel
-import org.yuzu.yuzu_emu.overlay.InputOverlay
+import org.yuzu.yuzu_emu.overlay.model.OverlayControl
+import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
 import org.yuzu.yuzu_emu.utils.*
 import java.lang.NullPointerException
 
 class EmulationFragment : Fragment(), SurfaceHolder.Callback {
-    private lateinit var preferences: SharedPreferences
     private lateinit var emulationState: EmulationState
     private var emulationActivity: EmulationActivity? = null
     private var perfStatsUpdater: (() -> Unit)? = null
@@ -141,7 +139,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
         // So this fragment doesn't restart on configuration changes; i.e. rotation.
         retainInstance = true
-        preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
         emulationState = EmulationState(game.path)
     }
 
@@ -382,24 +379,25 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         }
 
         updateScreenLayout()
+        val showInputOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
         if (emulationActivity?.isInPictureInPictureMode == true) {
             if (binding.drawerLayout.isOpen) {
                 binding.drawerLayout.close()
             }
-            if (EmulationMenuSettings.showOverlay) {
+            if (showInputOverlay) {
                 binding.surfaceInputOverlay.visibility = View.INVISIBLE
             }
         } else {
-            if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
+            if (showInputOverlay && emulationViewModel.emulationStarted.value) {
                 binding.surfaceInputOverlay.visibility = View.VISIBLE
             } else {
                 binding.surfaceInputOverlay.visibility = View.INVISIBLE
             }
             if (!isInFoldableLayout) {
                 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
-                    binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT
+                    binding.surfaceInputOverlay.layout = OverlayLayout.Portrait
                 } else {
-                    binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
+                    binding.surfaceInputOverlay.layout = OverlayLayout.Landscape
                 }
             }
         }
@@ -423,17 +421,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
     }
 
     private fun resetInputOverlay() {
-        preferences.edit()
-            .remove(Settings.PREF_CONTROL_SCALE)
-            .remove(Settings.PREF_CONTROL_OPACITY)
-            .apply()
+        IntSetting.OVERLAY_SCALE.reset()
+        IntSetting.OVERLAY_OPACITY.reset()
         binding.surfaceInputOverlay.post {
             binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
         }
     }
 
     private fun updateShowFpsOverlay() {
-        if (EmulationMenuSettings.showFps) {
+        if (BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()) {
             val SYSTEM_FPS = 0
             val FPS = 1
             val FRAMETIME = 2
@@ -496,7 +492,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                         binding.inGameMenu.layoutParams.height = it.bounds.bottom
 
                         isInFoldableLayout = true
-                        binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
+                        binding.surfaceInputOverlay.layout = OverlayLayout.Foldable
                     }
                 }
                 it.isSeparating
@@ -535,18 +531,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu)
 
         popup.menu.apply {
-            findItem(R.id.menu_toggle_fps).isChecked = EmulationMenuSettings.showFps
-            findItem(R.id.menu_rel_stick_center).isChecked = EmulationMenuSettings.joystickRelCenter
-            findItem(R.id.menu_dpad_slide).isChecked = EmulationMenuSettings.dpadSlide
-            findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
-            findItem(R.id.menu_haptics).isChecked = EmulationMenuSettings.hapticFeedback
+            findItem(R.id.menu_toggle_fps).isChecked =
+                BooleanSetting.SHOW_PERFORMANCE_OVERLAY.getBoolean()
+            findItem(R.id.menu_rel_stick_center).isChecked =
+                BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()
+            findItem(R.id.menu_dpad_slide).isChecked = BooleanSetting.DPAD_SLIDE.getBoolean()
+            findItem(R.id.menu_show_overlay).isChecked =
+                BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
+            findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean()
         }
 
         popup.setOnMenuItemClickListener {
             when (it.itemId) {
                 R.id.menu_toggle_fps -> {
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.showFps = it.isChecked
+                    BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(it.isChecked)
                     updateShowFpsOverlay()
                     true
                 }
@@ -564,11 +563,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 }
 
                 R.id.menu_toggle_controls -> {
-                    val preferences =
-                        PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-                    val optionsArray = BooleanArray(Settings.overlayPreferences.size)
-                    Settings.overlayPreferences.forEachIndexed { i, _ ->
-                        optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15)
+                    val overlayControlData = NativeConfig.getOverlayControlData()
+                    val optionsArray = BooleanArray(overlayControlData.size)
+                    overlayControlData.forEachIndexed { i, _ ->
+                        optionsArray[i] = overlayControlData.firstOrNull { data ->
+                            OverlayControl.entries[i].id == data.id
+                        }?.enabled == true
                     }
 
                     val dialog = MaterialAlertDialogBuilder(requireContext())
@@ -577,11 +577,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                             R.array.gamepadButtons,
                             optionsArray
                         ) { _, indexSelected, isChecked ->
-                            preferences.edit()
-                                .putBoolean("buttonToggle$indexSelected", isChecked)
-                                .apply()
+                            overlayControlData.firstOrNull { data ->
+                                OverlayControl.entries[indexSelected].id == data.id
+                            }?.enabled = isChecked
                         }
                         .setPositiveButton(android.R.string.ok) { _, _ ->
+                            NativeConfig.setOverlayControlData(overlayControlData)
+                            NativeConfig.saveGlobalConfig()
                             binding.surfaceInputOverlay.refreshControls()
                         }
                         .setNegativeButton(android.R.string.cancel, null)
@@ -592,12 +594,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                     dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
                         .setOnClickListener {
                             val isChecked = !optionsArray[0]
-                            Settings.overlayPreferences.forEachIndexed { i, _ ->
+                            overlayControlData.forEachIndexed { i, _ ->
                                 optionsArray[i] = isChecked
                                 dialog.listView.setItemChecked(i, isChecked)
-                                preferences.edit()
-                                    .putBoolean("buttonToggle$i", isChecked)
-                                    .apply()
+                                overlayControlData[i].enabled = isChecked
                             }
                         }
                     true
@@ -605,26 +605,26 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
                 R.id.menu_show_overlay -> {
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.showOverlay = it.isChecked
+                    BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(it.isChecked)
                     binding.surfaceInputOverlay.refreshControls()
                     true
                 }
 
                 R.id.menu_rel_stick_center -> {
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.joystickRelCenter = it.isChecked
+                    BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(it.isChecked)
                     true
                 }
 
                 R.id.menu_dpad_slide -> {
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.dpadSlide = it.isChecked
+                    BooleanSetting.DPAD_SLIDE.setBoolean(it.isChecked)
                     true
                 }
 
                 R.id.menu_haptics -> {
                     it.isChecked = !it.isChecked
-                    EmulationMenuSettings.hapticFeedback = it.isChecked
+                    BooleanSetting.HAPTIC_FEEDBACK.setBoolean(it.isChecked)
                     true
                 }
 
@@ -667,6 +667,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
             }
         }
+        NativeConfig.saveGlobalConfig()
     }
 
     @SuppressLint("SetTextI18n")
@@ -675,7 +676,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         adjustBinding.apply {
             inputScaleSlider.apply {
                 valueTo = 150F
-                value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
+                value = IntSetting.OVERLAY_SCALE.getInt().toFloat()
                 addOnChangeListener(
                     Slider.OnChangeListener { _, value, _ ->
                         inputScaleValue.text = "${value.toInt()}%"
@@ -685,7 +686,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
             }
             inputOpacitySlider.apply {
                 valueTo = 100F
-                value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
+                value = IntSetting.OVERLAY_OPACITY.getInt().toFloat()
                 addOnChangeListener(
                     Slider.OnChangeListener { _, value, _ ->
                         inputOpacityValue.text = "${value.toInt()}%"
@@ -709,16 +710,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
     }
 
     private fun setControlScale(scale: Int) {
-        preferences.edit()
-            .putInt(Settings.PREF_CONTROL_SCALE, scale)
-            .apply()
+        IntSetting.OVERLAY_SCALE.setInt(scale)
         binding.surfaceInputOverlay.refreshControls()
     }
 
     private fun setControlOpacity(opacity: Int) {
-        preferences.edit()
-            .putInt(Settings.PREF_CONTROL_OPACITY, opacity)
-            .apply()
+        IntSetting.OVERLAY_OPACITY.setInt(opacity)
         binding.surfaceInputOverlay.refreshControls()
     }
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index a13faf3c7..bb69b8bd5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -21,7 +21,6 @@ import android.view.View
 import android.view.View.OnTouchListener
 import android.view.WindowInsets
 import androidx.core.content.ContextCompat
-import androidx.preference.PreferenceManager
 import androidx.window.layout.WindowMetricsCalculator
 import kotlin.math.max
 import kotlin.math.min
@@ -29,9 +28,13 @@ import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
 import org.yuzu.yuzu_emu.NativeLibrary.StickType
 import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
+import org.yuzu.yuzu_emu.overlay.model.OverlayControl
+import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
+import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
+import org.yuzu.yuzu_emu.utils.NativeConfig
 
 /**
  * Draws the interactive input overlay on top of the
@@ -51,23 +54,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
 
     private lateinit var windowInsets: WindowInsets
 
-    var layout = LANDSCAPE
+    var layout = OverlayLayout.Landscape
 
     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
         super.onLayout(changed, left, top, right, bottom)
 
         windowInsets = rootWindowInsets
 
-        val overlayVersion = preferences.getInt(Settings.PREF_OVERLAY_VERSION, 0)
-        if (overlayVersion != OVERLAY_VERSION) {
-            resetAllLayouts()
+        val overlayControlData = NativeConfig.getOverlayControlData()
+        if (overlayControlData.isEmpty()) {
+            populateDefaultConfig()
         } else {
-            val layoutIndex = overlayLayouts.indexOf(layout)
-            val currentLayoutVersion =
-                preferences.getInt(Settings.overlayLayoutPrefs[layoutIndex], 0)
-            if (currentLayoutVersion != overlayLayoutVersions[layoutIndex]) {
-                resetCurrentLayout()
-            }
+            checkForNewControls(overlayControlData)
         }
 
         // Load the controls.
@@ -123,7 +121,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
         }
 
         for (dpad in overlayDpads) {
-            if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlide)) {
+            if (!dpad.updateStatus(event, BooleanSetting.DPAD_SLIDE.getBoolean())) {
                 continue
             }
             NativeLibrary.onGamePadButtonEvent(
@@ -174,7 +172,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
             invalidate()
         }
 
-        if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) {
+        if (!BooleanSetting.TOUCHSCREEN.getBoolean()) {
             return true
         }
 
@@ -211,7 +209,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
     }
 
     private fun playHaptics(event: MotionEvent) {
-        if (EmulationMenuSettings.hapticFeedback) {
+        if (BooleanSetting.HAPTIC_FEEDBACK.getBoolean()) {
             when (event.actionMasked) {
                 MotionEvent.ACTION_DOWN,
                 MotionEvent.ACTION_POINTER_DOWN ->
@@ -255,10 +253,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
                 MotionEvent.ACTION_POINTER_DOWN ->
                     // If no button is being moved now, remember the currently touched button to move.
                     if (buttonBeingConfigured == null &&
-                        button.bounds.contains(
-                                fingerPositionX,
-                                fingerPositionY
-                            )
+                        button.bounds.contains(fingerPositionX, fingerPositionY)
                     ) {
                         buttonBeingConfigured = button
                         buttonBeingConfigured!!.onConfigureTouch(event)
@@ -274,7 +269,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
                 MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
                     // Persist button position by saving new place.
                     saveControlPosition(
-                        buttonBeingConfigured!!.prefId,
+                        buttonBeingConfigured!!.overlayControlData.id,
                         buttonBeingConfigured!!.bounds.centerX(),
                         buttonBeingConfigured!!.bounds.centerY(),
                         layout
@@ -321,10 +316,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
             when (event.action) {
                 MotionEvent.ACTION_DOWN,
                 MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
-                    joystick.bounds.contains(
-                            fingerPositionX,
-                            fingerPositionY
-                        )
+                    joystick.bounds.contains(fingerPositionX, fingerPositionY)
                 ) {
                     joystickBeingConfigured = joystick
                     joystickBeingConfigured!!.onConfigureTouch(event)
@@ -351,231 +343,257 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
         return true
     }
 
-    private fun addOverlayControls(layout: String) {
+    private fun addOverlayControls(layout: OverlayLayout) {
         val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
-        if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.facebutton_a,
-                    R.drawable.facebutton_a_depressed,
-                    ButtonType.BUTTON_A,
-                    Settings.PREF_BUTTON_A,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_B, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.facebutton_b,
-                    R.drawable.facebutton_b_depressed,
-                    ButtonType.BUTTON_B,
-                    Settings.PREF_BUTTON_B,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_X, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.facebutton_x,
-                    R.drawable.facebutton_x_depressed,
-                    ButtonType.BUTTON_X,
-                    Settings.PREF_BUTTON_X,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_Y, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.facebutton_y,
-                    R.drawable.facebutton_y_depressed,
-                    ButtonType.BUTTON_Y,
-                    Settings.PREF_BUTTON_Y,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_L, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.l_shoulder,
-                    R.drawable.l_shoulder_depressed,
-                    ButtonType.TRIGGER_L,
-                    Settings.PREF_BUTTON_L,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_R, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.r_shoulder,
-                    R.drawable.r_shoulder_depressed,
-                    ButtonType.TRIGGER_R,
-                    Settings.PREF_BUTTON_R,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_ZL, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.zl_trigger,
-                    R.drawable.zl_trigger_depressed,
-                    ButtonType.TRIGGER_ZL,
-                    Settings.PREF_BUTTON_ZL,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_ZR, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.zr_trigger,
-                    R.drawable.zr_trigger_depressed,
-                    ButtonType.TRIGGER_ZR,
-                    Settings.PREF_BUTTON_ZR,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_PLUS, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.facebutton_plus,
-                    R.drawable.facebutton_plus_depressed,
-                    ButtonType.BUTTON_PLUS,
-                    Settings.PREF_BUTTON_PLUS,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_MINUS, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.facebutton_minus,
-                    R.drawable.facebutton_minus_depressed,
-                    ButtonType.BUTTON_MINUS,
-                    Settings.PREF_BUTTON_MINUS,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_DPAD, true)) {
-            overlayDpads.add(
-                initializeOverlayDpad(
-                    context,
-                    windowSize,
-                    R.drawable.dpad_standard,
-                    R.drawable.dpad_standard_cardinal_depressed,
-                    R.drawable.dpad_standard_diagonal_depressed,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_STICK_L, true)) {
-            overlayJoysticks.add(
-                initializeOverlayJoystick(
-                    context,
-                    windowSize,
-                    R.drawable.joystick_range,
-                    R.drawable.joystick,
-                    R.drawable.joystick_depressed,
-                    StickType.STICK_L,
-                    ButtonType.STICK_L,
-                    Settings.PREF_STICK_L,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_STICK_R, true)) {
-            overlayJoysticks.add(
-                initializeOverlayJoystick(
-                    context,
-                    windowSize,
-                    R.drawable.joystick_range,
-                    R.drawable.joystick,
-                    R.drawable.joystick_depressed,
-                    StickType.STICK_R,
-                    ButtonType.STICK_R,
-                    Settings.PREF_STICK_R,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_HOME, false)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.facebutton_home,
-                    R.drawable.facebutton_home_depressed,
-                    ButtonType.BUTTON_HOME,
-                    Settings.PREF_BUTTON_HOME,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_SCREENSHOT, false)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.facebutton_screenshot,
-                    R.drawable.facebutton_screenshot_depressed,
-                    ButtonType.BUTTON_CAPTURE,
-                    Settings.PREF_BUTTON_SCREENSHOT,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_L, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.button_l3,
-                    R.drawable.button_l3_depressed,
-                    ButtonType.STICK_L,
-                    Settings.PREF_BUTTON_STICK_L,
-                    layout
-                )
-            )
-        }
-        if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_R, true)) {
-            overlayButtons.add(
-                initializeOverlayButton(
-                    context,
-                    windowSize,
-                    R.drawable.button_r3,
-                    R.drawable.button_r3_depressed,
-                    ButtonType.STICK_R,
-                    Settings.PREF_BUTTON_STICK_R,
-                    layout
-                )
-            )
+        val overlayControlData = NativeConfig.getOverlayControlData()
+        for (data in overlayControlData) {
+            if (!data.enabled) {
+                continue
+            }
+
+            val position = data.positionFromLayout(layout)
+            when (data.id) {
+                OverlayControl.BUTTON_A.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.facebutton_a,
+                            R.drawable.facebutton_a_depressed,
+                            ButtonType.BUTTON_A,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_B.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.facebutton_b,
+                            R.drawable.facebutton_b_depressed,
+                            ButtonType.BUTTON_B,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_X.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.facebutton_x,
+                            R.drawable.facebutton_x_depressed,
+                            ButtonType.BUTTON_X,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_Y.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.facebutton_y,
+                            R.drawable.facebutton_y_depressed,
+                            ButtonType.BUTTON_Y,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_PLUS.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.facebutton_plus,
+                            R.drawable.facebutton_plus_depressed,
+                            ButtonType.BUTTON_PLUS,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_MINUS.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.facebutton_minus,
+                            R.drawable.facebutton_minus_depressed,
+                            ButtonType.BUTTON_MINUS,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_HOME.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.facebutton_home,
+                            R.drawable.facebutton_home_depressed,
+                            ButtonType.BUTTON_HOME,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_CAPTURE.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.facebutton_screenshot,
+                            R.drawable.facebutton_screenshot_depressed,
+                            ButtonType.BUTTON_CAPTURE,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_L.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.l_shoulder,
+                            R.drawable.l_shoulder_depressed,
+                            ButtonType.TRIGGER_L,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_R.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.r_shoulder,
+                            R.drawable.r_shoulder_depressed,
+                            ButtonType.TRIGGER_R,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_ZL.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.zl_trigger,
+                            R.drawable.zl_trigger_depressed,
+                            ButtonType.TRIGGER_ZL,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_ZR.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.zr_trigger,
+                            R.drawable.zr_trigger_depressed,
+                            ButtonType.TRIGGER_ZR,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_STICK_L.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.button_l3,
+                            R.drawable.button_l3_depressed,
+                            ButtonType.STICK_L,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.BUTTON_STICK_R.id -> {
+                    overlayButtons.add(
+                        initializeOverlayButton(
+                            context,
+                            windowSize,
+                            R.drawable.button_r3,
+                            R.drawable.button_r3_depressed,
+                            ButtonType.STICK_R,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.STICK_L.id -> {
+                    overlayJoysticks.add(
+                        initializeOverlayJoystick(
+                            context,
+                            windowSize,
+                            R.drawable.joystick_range,
+                            R.drawable.joystick,
+                            R.drawable.joystick_depressed,
+                            StickType.STICK_L,
+                            ButtonType.STICK_L,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.STICK_R.id -> {
+                    overlayJoysticks.add(
+                        initializeOverlayJoystick(
+                            context,
+                            windowSize,
+                            R.drawable.joystick_range,
+                            R.drawable.joystick,
+                            R.drawable.joystick_depressed,
+                            StickType.STICK_R,
+                            ButtonType.STICK_R,
+                            data,
+                            position
+                        )
+                    )
+                }
+
+                OverlayControl.COMBINED_DPAD.id -> {
+                    overlayDpads.add(
+                        initializeOverlayDpad(
+                            context,
+                            windowSize,
+                            R.drawable.dpad_standard,
+                            R.drawable.dpad_standard_cardinal_depressed,
+                            R.drawable.dpad_standard_diagonal_depressed,
+                            position
+                        )
+                    )
+                }
+            }
         }
     }
 
@@ -586,313 +604,87 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
         overlayJoysticks.clear()
 
         // Add all the enabled overlay items back to the HashSet.
-        if (EmulationMenuSettings.showOverlay) {
+        if (BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) {
             addOverlayControls(layout)
         }
         invalidate()
     }
 
-    private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) {
+    private fun saveControlPosition(id: String, x: Int, y: Int, layout: OverlayLayout) {
         val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight))
         val min = windowSize.first
         val max = windowSize.second
-        PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
-            .putFloat("$prefId-X$layout", (x - min.x).toFloat() / max.x)
-            .putFloat("$prefId-Y$layout", (y - min.y).toFloat() / max.y)
-            .apply()
+        val overlayControlData = NativeConfig.getOverlayControlData()
+        val data = overlayControlData.firstOrNull { it.id == id }
+        val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y)
+        when (layout) {
+            OverlayLayout.Landscape -> data?.landscapePosition = newPosition
+            OverlayLayout.Portrait -> data?.portraitPosition = newPosition
+            OverlayLayout.Foldable -> data?.foldablePosition = newPosition
+        }
+        NativeConfig.setOverlayControlData(overlayControlData)
     }
 
     fun setIsInEditMode(editMode: Boolean) {
         inEditMode = editMode
     }
 
-    private fun resetCurrentLayout() {
-        defaultOverlayByLayout(layout)
-        val layoutIndex = overlayLayouts.indexOf(layout)
-        preferences.edit()
-            .putInt(Settings.overlayLayoutPrefs[layoutIndex], overlayLayoutVersions[layoutIndex])
-            .apply()
+    /**
+     * Applies and saves all default values for the overlay
+     */
+    private fun populateDefaultConfig() {
+        val newConfig = OverlayControl.entries.map { it.toOverlayControlData() }
+        NativeConfig.setOverlayControlData(newConfig.toTypedArray())
+        NativeConfig.saveGlobalConfig()
     }
 
-    private fun resetAllLayouts() {
-        val editor = preferences.edit()
-        overlayLayouts.forEachIndexed { i, layout ->
-            defaultOverlayByLayout(layout)
-            editor.putInt(Settings.overlayLayoutPrefs[i], overlayLayoutVersions[i])
+    /**
+     * Checks if any new controls were added to OverlayControl that do not exist within deserialized
+     * config and adds / saves them if necessary
+     *
+     * @param overlayControlData Overlay control data from [NativeConfig.getOverlayControlData]
+     */
+    private fun checkForNewControls(overlayControlData: Array<OverlayControlData>) {
+        val missingControls = mutableListOf<OverlayControlData>()
+        OverlayControl.entries.forEach { defaultControl ->
+            val controlData = overlayControlData.firstOrNull { it.id == defaultControl.id }
+            if (controlData == null) {
+                missingControls.add(defaultControl.toOverlayControlData())
+            }
+        }
+
+        if (missingControls.isNotEmpty()) {
+            NativeConfig.setOverlayControlData(
+                arrayOf(*overlayControlData, *(missingControls.toTypedArray()))
+            )
+            NativeConfig.saveGlobalConfig()
         }
-        editor.putInt(Settings.PREF_OVERLAY_VERSION, OVERLAY_VERSION)
-        editor.apply()
     }
 
     fun resetLayoutVisibilityAndPlacement() {
-        defaultOverlayByLayout(layout)
-        val editor = preferences.edit()
-        Settings.overlayPreferences.forEachIndexed { _, pref ->
-            editor.remove(pref)
+        defaultOverlayPositionByLayout(layout)
+
+        val overlayControlData = NativeConfig.getOverlayControlData()
+        overlayControlData.forEach {
+            it.enabled = OverlayControl.from(it.id)?.defaultVisibility == false
         }
-        editor.apply()
+        NativeConfig.setOverlayControlData(overlayControlData)
+
         refreshControls()
     }
 
-    private val landscapeResources = arrayOf(
-        R.integer.SWITCH_BUTTON_A_X,
-        R.integer.SWITCH_BUTTON_A_Y,
-        R.integer.SWITCH_BUTTON_B_X,
-        R.integer.SWITCH_BUTTON_B_Y,
-        R.integer.SWITCH_BUTTON_X_X,
-        R.integer.SWITCH_BUTTON_X_Y,
-        R.integer.SWITCH_BUTTON_Y_X,
-        R.integer.SWITCH_BUTTON_Y_Y,
-        R.integer.SWITCH_TRIGGER_ZL_X,
-        R.integer.SWITCH_TRIGGER_ZL_Y,
-        R.integer.SWITCH_TRIGGER_ZR_X,
-        R.integer.SWITCH_TRIGGER_ZR_Y,
-        R.integer.SWITCH_BUTTON_DPAD_X,
-        R.integer.SWITCH_BUTTON_DPAD_Y,
-        R.integer.SWITCH_TRIGGER_L_X,
-        R.integer.SWITCH_TRIGGER_L_Y,
-        R.integer.SWITCH_TRIGGER_R_X,
-        R.integer.SWITCH_TRIGGER_R_Y,
-        R.integer.SWITCH_BUTTON_PLUS_X,
-        R.integer.SWITCH_BUTTON_PLUS_Y,
-        R.integer.SWITCH_BUTTON_MINUS_X,
-        R.integer.SWITCH_BUTTON_MINUS_Y,
-        R.integer.SWITCH_BUTTON_HOME_X,
-        R.integer.SWITCH_BUTTON_HOME_Y,
-        R.integer.SWITCH_BUTTON_CAPTURE_X,
-        R.integer.SWITCH_BUTTON_CAPTURE_Y,
-        R.integer.SWITCH_STICK_R_X,
-        R.integer.SWITCH_STICK_R_Y,
-        R.integer.SWITCH_STICK_L_X,
-        R.integer.SWITCH_STICK_L_Y,
-        R.integer.SWITCH_BUTTON_STICK_L_X,
-        R.integer.SWITCH_BUTTON_STICK_L_Y,
-        R.integer.SWITCH_BUTTON_STICK_R_X,
-        R.integer.SWITCH_BUTTON_STICK_R_Y
-    )
-
-    private val portraitResources = arrayOf(
-        R.integer.SWITCH_BUTTON_A_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_A_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_B_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_B_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_X_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_X_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_Y_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_Y_Y_PORTRAIT,
-        R.integer.SWITCH_TRIGGER_ZL_X_PORTRAIT,
-        R.integer.SWITCH_TRIGGER_ZL_Y_PORTRAIT,
-        R.integer.SWITCH_TRIGGER_ZR_X_PORTRAIT,
-        R.integer.SWITCH_TRIGGER_ZR_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_DPAD_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_DPAD_Y_PORTRAIT,
-        R.integer.SWITCH_TRIGGER_L_X_PORTRAIT,
-        R.integer.SWITCH_TRIGGER_L_Y_PORTRAIT,
-        R.integer.SWITCH_TRIGGER_R_X_PORTRAIT,
-        R.integer.SWITCH_TRIGGER_R_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_PLUS_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_PLUS_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_MINUS_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_MINUS_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_HOME_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_HOME_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_CAPTURE_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_CAPTURE_Y_PORTRAIT,
-        R.integer.SWITCH_STICK_R_X_PORTRAIT,
-        R.integer.SWITCH_STICK_R_Y_PORTRAIT,
-        R.integer.SWITCH_STICK_L_X_PORTRAIT,
-        R.integer.SWITCH_STICK_L_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_STICK_L_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_STICK_L_Y_PORTRAIT,
-        R.integer.SWITCH_BUTTON_STICK_R_X_PORTRAIT,
-        R.integer.SWITCH_BUTTON_STICK_R_Y_PORTRAIT
-    )
-
-    private val foldableResources = arrayOf(
-        R.integer.SWITCH_BUTTON_A_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_A_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_B_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_B_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_X_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_X_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_Y_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_Y_Y_FOLDABLE,
-        R.integer.SWITCH_TRIGGER_ZL_X_FOLDABLE,
-        R.integer.SWITCH_TRIGGER_ZL_Y_FOLDABLE,
-        R.integer.SWITCH_TRIGGER_ZR_X_FOLDABLE,
-        R.integer.SWITCH_TRIGGER_ZR_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_DPAD_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_DPAD_Y_FOLDABLE,
-        R.integer.SWITCH_TRIGGER_L_X_FOLDABLE,
-        R.integer.SWITCH_TRIGGER_L_Y_FOLDABLE,
-        R.integer.SWITCH_TRIGGER_R_X_FOLDABLE,
-        R.integer.SWITCH_TRIGGER_R_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_PLUS_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_PLUS_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_MINUS_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_MINUS_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_HOME_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_HOME_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_CAPTURE_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_CAPTURE_Y_FOLDABLE,
-        R.integer.SWITCH_STICK_R_X_FOLDABLE,
-        R.integer.SWITCH_STICK_R_Y_FOLDABLE,
-        R.integer.SWITCH_STICK_L_X_FOLDABLE,
-        R.integer.SWITCH_STICK_L_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_STICK_L_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_STICK_L_Y_FOLDABLE,
-        R.integer.SWITCH_BUTTON_STICK_R_X_FOLDABLE,
-        R.integer.SWITCH_BUTTON_STICK_R_Y_FOLDABLE
-    )
-
-    private fun getResourceValue(layout: String, position: Int): Float {
-        return when (layout) {
-            PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
-            FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
-            else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000
+    private fun defaultOverlayPositionByLayout(layout: OverlayLayout) {
+        val overlayControlData = NativeConfig.getOverlayControlData()
+        for (data in overlayControlData) {
+            val defaultControlData = OverlayControl.from(data.id) ?: continue
+            val position = defaultControlData.getDefaultPositionForLayout(layout)
+            when (layout) {
+                OverlayLayout.Landscape -> data.landscapePosition = position
+                OverlayLayout.Portrait -> data.portraitPosition = position
+                OverlayLayout.Foldable -> data.foldablePosition = position
+            }
         }
-    }
-
-    private fun defaultOverlayByLayout(layout: String) {
-        // Each value represents the position of the button in relation to the screen size without insets.
-        preferences.edit()
-            .putFloat(
-                "${Settings.PREF_BUTTON_A}-X$layout",
-                getResourceValue(layout, 0)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_A}-Y$layout",
-                getResourceValue(layout, 1)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_B}-X$layout",
-                getResourceValue(layout, 2)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_B}-Y$layout",
-                getResourceValue(layout, 3)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_X}-X$layout",
-                getResourceValue(layout, 4)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_X}-Y$layout",
-                getResourceValue(layout, 5)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_Y}-X$layout",
-                getResourceValue(layout, 6)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_Y}-Y$layout",
-                getResourceValue(layout, 7)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_ZL}-X$layout",
-                getResourceValue(layout, 8)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_ZL}-Y$layout",
-                getResourceValue(layout, 9)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_ZR}-X$layout",
-                getResourceValue(layout, 10)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_ZR}-Y$layout",
-                getResourceValue(layout, 11)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_DPAD}-X$layout",
-                getResourceValue(layout, 12)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_DPAD}-Y$layout",
-                getResourceValue(layout, 13)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_L}-X$layout",
-                getResourceValue(layout, 14)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_L}-Y$layout",
-                getResourceValue(layout, 15)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_R}-X$layout",
-                getResourceValue(layout, 16)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_R}-Y$layout",
-                getResourceValue(layout, 17)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_PLUS}-X$layout",
-                getResourceValue(layout, 18)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_PLUS}-Y$layout",
-                getResourceValue(layout, 19)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_MINUS}-X$layout",
-                getResourceValue(layout, 20)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_MINUS}-Y$layout",
-                getResourceValue(layout, 21)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_HOME}-X$layout",
-                getResourceValue(layout, 22)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_HOME}-Y$layout",
-                getResourceValue(layout, 23)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_SCREENSHOT}-X$layout",
-                getResourceValue(layout, 24)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_SCREENSHOT}-Y$layout",
-                getResourceValue(layout, 25)
-            )
-            .putFloat(
-                "${Settings.PREF_STICK_R}-X$layout",
-                getResourceValue(layout, 26)
-            )
-            .putFloat(
-                "${Settings.PREF_STICK_R}-Y$layout",
-                getResourceValue(layout, 27)
-            )
-            .putFloat(
-                "${Settings.PREF_STICK_L}-X$layout",
-                getResourceValue(layout, 28)
-            )
-            .putFloat(
-                "${Settings.PREF_STICK_L}-Y$layout",
-                getResourceValue(layout, 29)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_STICK_L}-X$layout",
-                getResourceValue(layout, 30)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_STICK_L}-Y$layout",
-                getResourceValue(layout, 31)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_STICK_R}-X$layout",
-                getResourceValue(layout, 32)
-            )
-            .putFloat(
-                "${Settings.PREF_BUTTON_STICK_R}-Y$layout",
-                getResourceValue(layout, 33)
-            )
-            .apply()
+        NativeConfig.setOverlayControlData(overlayControlData)
     }
 
     override fun isInEditMode(): Boolean {
@@ -913,18 +705,6 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
             FOLDABLE_OVERLAY_VERSION
         )
 
-        private val preferences: SharedPreferences =
-            PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
-        const val LANDSCAPE = "_Landscape"
-        const val PORTRAIT = "_Portrait"
-        const val FOLDABLE = "_Foldable"
-        val overlayLayouts = listOf(
-            LANDSCAPE,
-            PORTRAIT,
-            FOLDABLE
-        )
-
         /**
          * Resizes a [Bitmap] by a given scale factor
          *
@@ -1036,29 +816,19 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
          * In the input overlay configuration menu,
          * once a touch event begins and then ends (ie. Organizing the buttons to one's own liking for the overlay).
          * the X and Y coordinates of the button at the END of its touch event
-         * (when you remove your finger/stylus from the touchscreen) are then stored
-         * within a SharedPreferences instance so that those values can be retrieved here.
-         *
-         *
-         * This has a few benefits over the conventional way of storing the values
-         * (ie. within the yuzu ini file).
-         *
-         *  * No native calls
-         *  * Keeps Android-only values inside the Android environment
-         *
-         *
+         * (when you remove your finger/stylus from the touchscreen) are then stored in a native .
          *
          * Technically no modifications should need to be performed on the returned
          * InputOverlayDrawableButton. Simply add it to the HashSet of overlay items and wait
          * for Android to call the onDraw method.
          *
-         * @param context      The current [Context].
-         * @param windowSize   The size of the window to draw the overlay on.
-         * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State).
-         * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State).
-         * @param buttonId     Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
-         * @param prefId       Identifier for determining where a button appears on screen.
-         * @param layout       The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
+         * @param context            The current [Context].
+         * @param windowSize         The size of the window to draw the overlay on.
+         * @param defaultResId       The resource ID of the [Drawable] to get the [Bitmap] of (Default State).
+         * @param pressedResId       The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State).
+         * @param buttonId           Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
+         * @param overlayControlData Identifier for determining where a button appears on screen.
+         * @param position           The position on screen as represented by an x and y value between 0 and 1.
          * @return An [InputOverlayDrawableButton] with the correct drawing bounds set.
          */
         private fun initializeOverlayButton(
@@ -1067,33 +837,30 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
             defaultResId: Int,
             pressedResId: Int,
             buttonId: Int,
-            prefId: String,
-            layout: String
+            overlayControlData: OverlayControlData,
+            position: Pair<Double, Double>
         ): InputOverlayDrawableButton {
             // Resources handle for fetching the initial Drawable resource.
             val res = context.resources
 
-            // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
-            val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
             // Decide scale based on button preference ID and user preference
-            var scale: Float = when (prefId) {
-                Settings.PREF_BUTTON_HOME,
-                Settings.PREF_BUTTON_SCREENSHOT,
-                Settings.PREF_BUTTON_PLUS,
-                Settings.PREF_BUTTON_MINUS -> 0.07f
+            var scale: Float = when (overlayControlData.id) {
+                OverlayControl.BUTTON_HOME.id,
+                OverlayControl.BUTTON_CAPTURE.id,
+                OverlayControl.BUTTON_PLUS.id,
+                OverlayControl.BUTTON_MINUS.id -> 0.07f
 
-                Settings.PREF_BUTTON_L,
-                Settings.PREF_BUTTON_R,
-                Settings.PREF_BUTTON_ZL,
-                Settings.PREF_BUTTON_ZR -> 0.26f
+                OverlayControl.BUTTON_L.id,
+                OverlayControl.BUTTON_R.id,
+                OverlayControl.BUTTON_ZL.id,
+                OverlayControl.BUTTON_ZR.id -> 0.26f
 
-                Settings.PREF_BUTTON_STICK_L,
-                Settings.PREF_BUTTON_STICK_R -> 0.155f
+                OverlayControl.BUTTON_STICK_L.id,
+                OverlayControl.BUTTON_STICK_R.id -> 0.155f
 
                 else -> 0.11f
             }
-            scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat()
+            scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
             scale /= 100f
 
             // Initialize the InputOverlayDrawableButton.
@@ -1104,7 +871,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
                 defaultStateBitmap,
                 pressedStateBitmap,
                 buttonId,
-                prefId
+                overlayControlData
             )
 
             // Get the minimum and maximum coordinates of the screen where the button can be placed.
@@ -1113,12 +880,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
 
             // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
             // These were set in the input overlay configuration menu.
-            val xKey = "$prefId-X$layout"
-            val yKey = "$prefId-Y$layout"
-            val drawableXPercent = sPrefs.getFloat(xKey, 0f)
-            val drawableYPercent = sPrefs.getFloat(yKey, 0f)
-            val drawableX = (drawableXPercent * max.x + min.x).toInt()
-            val drawableY = (drawableYPercent * max.y + min.y).toInt()
+            val drawableX = (position.first * max.x + min.x).toInt()
+            val drawableY = (position.second * max.y + min.y).toInt()
             val width = overlayDrawable.width
             val height = overlayDrawable.height
 
@@ -1136,8 +899,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
                 drawableX - (width / 2),
                 drawableY - (height / 2)
             )
-            val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
-            overlayDrawable.setOpacity(savedOpacity * 255 / 100)
+            overlayDrawable.setOpacity(IntSetting.OVERLAY_OPACITY.getInt() * 255 / 100)
             return overlayDrawable
         }
 
@@ -1149,7 +911,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
          * @param defaultResId              The [Bitmap] resource ID of the default state.
          * @param pressedOneDirectionResId  The [Bitmap] resource ID of the pressed state in one direction.
          * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions.
-         * @param layout                    The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
+         * @param position                  The position on screen as represented by an x and y value between 0 and 1.
          * @return The initialized [InputOverlayDrawableDpad]
          */
         private fun initializeOverlayDpad(
@@ -1158,17 +920,14 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
             defaultResId: Int,
             pressedOneDirectionResId: Int,
             pressedTwoDirectionsResId: Int,
-            layout: String
+            position: Pair<Double, Double>
         ): InputOverlayDrawableDpad {
             // Resources handle for fetching the initial Drawable resource.
             val res = context.resources
 
-            // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableDpad.
-            val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
             // Decide scale based on button ID and user preference
             var scale = 0.25f
-            scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat()
+            scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
             scale /= 100f
 
             // Initialize the InputOverlayDrawableDpad.
@@ -1195,10 +954,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
 
             // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
             // These were set in the input overlay configuration menu.
-            val drawableXPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-X$layout", 0f)
-            val drawableYPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-Y$layout", 0f)
-            val drawableX = (drawableXPercent * max.x + min.x).toInt()
-            val drawableY = (drawableYPercent * max.y + min.y).toInt()
+            val drawableX = (position.first * max.x + min.x).toInt()
+            val drawableY = (position.second * max.y + min.y).toInt()
             val width = overlayDrawable.width
             val height = overlayDrawable.height
 
@@ -1213,8 +970,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
 
             // Need to set the image's position
             overlayDrawable.setPosition(drawableX - (width / 2), drawableY - (height / 2))
-            val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
-            overlayDrawable.setOpacity(savedOpacity * 255 / 100)
+            overlayDrawable.setOpacity(IntSetting.OVERLAY_OPACITY.getInt() * 255 / 100)
             return overlayDrawable
         }
 
@@ -1227,9 +983,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
          * @param defaultResInner Resource ID for the default inner image of the joystick (the one you actually move around).
          * @param pressedResInner Resource ID for the pressed inner image of the joystick.
          * @param joystick        Identifier for which joystick this is.
-         * @param button          Identifier for which joystick button this is.
-         * @param prefId          Identifier for determining where a button appears on screen.
-         * @param layout          The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
+         * @param buttonId          Identifier for which joystick button this is.
+         * @param overlayControlData Identifier for determining where a button appears on screen.
+         * @param position           The position on screen as represented by an x and y value between 0 and 1.
          * @return The initialized [InputOverlayDrawableJoystick].
          */
         private fun initializeOverlayJoystick(
@@ -1239,19 +995,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
             defaultResInner: Int,
             pressedResInner: Int,
             joystick: Int,
-            button: Int,
-            prefId: String,
-            layout: String
+            buttonId: Int,
+            overlayControlData: OverlayControlData,
+            position: Pair<Double, Double>
         ): InputOverlayDrawableJoystick {
             // Resources handle for fetching the initial Drawable resource.
             val res = context.resources
 
-            // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableJoystick.
-            val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
             // Decide scale based on user preference
             var scale = 0.3f
-            scale *= (sPrefs.getInt(Settings.PREF_CONTROL_SCALE, 50) + 50).toFloat()
+            scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat()
             scale /= 100f
 
             // Initialize the InputOverlayDrawableJoystick.
@@ -1265,10 +1018,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
 
             // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
             // These were set in the input overlay configuration menu.
-            val drawableXPercent = sPrefs.getFloat("$prefId-X$layout", 0f)
-            val drawableYPercent = sPrefs.getFloat("$prefId-Y$layout", 0f)
-            val drawableX = (drawableXPercent * max.x + min.x).toInt()
-            val drawableY = (drawableYPercent * max.y + min.y).toInt()
+            val drawableX = (position.first * max.x + min.x).toInt()
+            val drawableY = (position.second * max.y + min.y).toInt()
             val outerScale = 1.66f
 
             // Now set the bounds for the InputOverlayDrawableJoystick.
@@ -1292,14 +1043,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
                 outerRect,
                 innerRect,
                 joystick,
-                button,
-                prefId
+                buttonId,
+                overlayControlData.id
             )
 
             // Need to set the image's position
             overlayDrawable.setPosition(drawableX, drawableY)
-            val savedOpacity = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100)
-            overlayDrawable.setOpacity(savedOpacity * 255 / 100)
+            overlayDrawable.setOpacity(IntSetting.OVERLAY_OPACITY.getInt() * 255 / 100)
             return overlayDrawable
         }
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
index 2c28dda88..b14a4f96e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
@@ -10,6 +10,7 @@ import android.graphics.Rect
 import android.graphics.drawable.BitmapDrawable
 import android.view.MotionEvent
 import org.yuzu.yuzu_emu.NativeLibrary.ButtonState
+import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
 
 /**
  * Custom [BitmapDrawable] that is capable
@@ -25,7 +26,7 @@ class InputOverlayDrawableButton(
     defaultStateBitmap: Bitmap,
     pressedStateBitmap: Bitmap,
     val buttonId: Int,
-    val prefId: String
+    val overlayControlData: OverlayControlData
 ) {
     // The ID value what motion event is tracking
     var trackId: Int
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
index 518b1e783..113bf7c24 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
@@ -14,7 +14,7 @@ import kotlin.math.cos
 import kotlin.math.sin
 import kotlin.math.sqrt
 import org.yuzu.yuzu_emu.NativeLibrary
-import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
 
 /**
  * Custom [BitmapDrawable] that is capable
@@ -125,7 +125,7 @@ class InputOverlayDrawableJoystick(
             pressedState = true
             outerBitmap.alpha = 0
             boundsBoxBitmap.alpha = opacity
-            if (EmulationMenuSettings.joystickRelCenter) {
+            if (BooleanSetting.JOYSTICK_REL_CENTER.getBoolean()) {
                 virtBounds.offset(
                     xPosition - virtBounds.centerX(),
                     yPosition - virtBounds.centerY()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
new file mode 100644
index 000000000..a0eeadf4b
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt
@@ -0,0 +1,188 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.overlay.model
+
+import androidx.annotation.IntegerRes
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+
+enum class OverlayControl(
+    val id: String,
+    val defaultVisibility: Boolean,
+    @IntegerRes val defaultLandscapePositionResources: Pair<Int, Int>,
+    @IntegerRes val defaultPortraitPositionResources: Pair<Int, Int>,
+    @IntegerRes val defaultFoldablePositionResources: Pair<Int, Int>
+) {
+    BUTTON_A(
+        "button_a",
+        true,
+        Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y),
+        Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE)
+    ),
+    BUTTON_B(
+        "button_b",
+        true,
+        Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y),
+        Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE)
+    ),
+    BUTTON_X(
+        "button_x",
+        true,
+        Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y),
+        Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE)
+    ),
+    BUTTON_Y(
+        "button_y",
+        true,
+        Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y),
+        Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE)
+    ),
+    BUTTON_PLUS(
+        "button_plus",
+        true,
+        Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y),
+        Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE)
+    ),
+    BUTTON_MINUS(
+        "button_minus",
+        true,
+        Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y),
+        Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE)
+    ),
+    BUTTON_HOME(
+        "button_home",
+        false,
+        Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y),
+        Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE)
+    ),
+    BUTTON_CAPTURE(
+        "button_capture",
+        false,
+        Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y),
+        Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE)
+    ),
+    BUTTON_L(
+        "button_l",
+        true,
+        Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y),
+        Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE)
+    ),
+    BUTTON_R(
+        "button_r",
+        true,
+        Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y),
+        Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE)
+    ),
+    BUTTON_ZL(
+        "button_zl",
+        true,
+        Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y),
+        Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE)
+    ),
+    BUTTON_ZR(
+        "button_zr",
+        true,
+        Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y),
+        Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE)
+    ),
+    BUTTON_STICK_L(
+        "button_stick_l",
+        true,
+        Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y),
+        Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE)
+    ),
+    BUTTON_STICK_R(
+        "button_stick_r",
+        true,
+        Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y),
+        Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT),
+        Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE)
+    ),
+    STICK_L(
+        "stick_l",
+        true,
+        Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y),
+        Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT),
+        Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE)
+    ),
+    STICK_R(
+        "stick_r",
+        true,
+        Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y),
+        Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT),
+        Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE)
+    ),
+    COMBINED_DPAD(
+        "combined_dpad",
+        true,
+        Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y),
+        Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT),
+        Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE)
+    );
+
+    fun getDefaultPositionForLayout(layout: OverlayLayout): Pair<Double, Double> {
+        val rawResourcePair: Pair<Int, Int>
+        YuzuApplication.appContext.resources.apply {
+            rawResourcePair = when (layout) {
+                OverlayLayout.Landscape -> {
+                    Pair(
+                        getInteger(this@OverlayControl.defaultLandscapePositionResources.first),
+                        getInteger(this@OverlayControl.defaultLandscapePositionResources.second)
+                    )
+                }
+
+                OverlayLayout.Portrait -> {
+                    Pair(
+                        getInteger(this@OverlayControl.defaultPortraitPositionResources.first),
+                        getInteger(this@OverlayControl.defaultPortraitPositionResources.second)
+                    )
+                }
+
+                OverlayLayout.Foldable -> {
+                    Pair(
+                        getInteger(this@OverlayControl.defaultFoldablePositionResources.first),
+                        getInteger(this@OverlayControl.defaultFoldablePositionResources.second)
+                    )
+                }
+            }
+        }
+
+        return Pair(
+            rawResourcePair.first.toDouble() / 1000,
+            rawResourcePair.second.toDouble() / 1000
+        )
+    }
+
+    fun toOverlayControlData(): OverlayControlData =
+        OverlayControlData(
+            id,
+            defaultVisibility,
+            getDefaultPositionForLayout(OverlayLayout.Landscape),
+            getDefaultPositionForLayout(OverlayLayout.Portrait),
+            getDefaultPositionForLayout(OverlayLayout.Foldable)
+        )
+
+    companion object {
+        val map: HashMap<String, OverlayControl> by lazy {
+            val hashMap = hashMapOf<String, OverlayControl>()
+            entries.forEach { hashMap[it.id] = it }
+            hashMap
+        }
+
+        fun from(id: String): OverlayControl? = map[id]
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
new file mode 100644
index 000000000..26cfeb1db
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.overlay.model
+
+data class OverlayControlData(
+    val id: String,
+    var enabled: Boolean,
+    var landscapePosition: Pair<Double, Double>,
+    var portraitPosition: Pair<Double, Double>,
+    var foldablePosition: Pair<Double, Double>
+) {
+    fun positionFromLayout(layout: OverlayLayout): Pair<Double, Double> =
+        when (layout) {
+            OverlayLayout.Landscape -> landscapePosition
+            OverlayLayout.Portrait -> portraitPosition
+            OverlayLayout.Foldable -> foldablePosition
+        }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt
new file mode 100644
index 000000000..6bd74c82f
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlDefault.kt
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.overlay.model
+
+import androidx.annotation.IntegerRes
+
+data class OverlayControlDefault(
+    val buttonId: String,
+    @IntegerRes val landscapePositionResource: Pair<Int, Int>,
+    @IntegerRes val portraitPositionResource: Pair<Int, Int>,
+    @IntegerRes val foldablePositionResource: Pair<Int, Int>
+)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt
new file mode 100644
index 000000000..d728164e5
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayLayout.kt
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.overlay.model
+
+enum class OverlayLayout(val id: String) {
+    Landscape("Landscape"),
+    Portrait("Portrait"),
+    Foldable("Foldable")
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index d4a9da06f..de0794a17 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -10,6 +10,9 @@ import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
+import org.yuzu.yuzu_emu.overlay.model.OverlayControl
+import org.yuzu.yuzu_emu.overlay.model.OverlayLayout
 import org.yuzu.yuzu_emu.utils.PreferenceUtil.migratePreference
 
 object DirectoryInitialization {
@@ -64,8 +67,147 @@ object DirectoryInitialization {
             saveConfig = true
         }
 
+        val joystickRelCenter =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER)
+        if (joystickRelCenter != null) {
+            BooleanSetting.JOYSTICK_REL_CENTER.setBoolean(joystickRelCenter)
+            saveConfig = true
+        }
+
+        val dpadSlide =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE)
+        if (dpadSlide != null) {
+            BooleanSetting.DPAD_SLIDE.setBoolean(dpadSlide)
+            saveConfig = true
+        }
+
+        val hapticFeedback =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_HAPTICS)
+        if (hapticFeedback != null) {
+            BooleanSetting.HAPTIC_FEEDBACK.setBoolean(hapticFeedback)
+            saveConfig = true
+        }
+
+        val showPerformanceOverlay =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_FPS)
+        if (showPerformanceOverlay != null) {
+            BooleanSetting.SHOW_PERFORMANCE_OVERLAY.setBoolean(showPerformanceOverlay)
+            saveConfig = true
+        }
+
+        val showInputOverlay =
+            preferences.migratePreference<Boolean>(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY)
+        if (showInputOverlay != null) {
+            BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(showInputOverlay)
+            saveConfig = true
+        }
+
+        val overlayOpacity = preferences.migratePreference<Int>(Settings.PREF_CONTROL_OPACITY)
+        if (overlayOpacity != null) {
+            IntSetting.OVERLAY_OPACITY.setInt(overlayOpacity)
+            saveConfig = true
+        }
+
+        val overlayScale = preferences.migratePreference<Int>(Settings.PREF_CONTROL_SCALE)
+        if (overlayScale != null) {
+            IntSetting.OVERLAY_SCALE.setInt(overlayScale)
+            saveConfig = true
+        }
+
+        var setOverlayData = false
+        val overlayControlData = NativeConfig.getOverlayControlData()
+        if (overlayControlData.isEmpty()) {
+            val overlayControlDataMap =
+                NativeConfig.getOverlayControlData().associateBy { it.id }.toMutableMap()
+            for (button in Settings.overlayPreferences) {
+                val buttonId = convertButtonId(button)
+                var buttonEnabled = preferences.migratePreference<Boolean>(button)
+                if (buttonEnabled == null) {
+                    buttonEnabled = OverlayControl.map[buttonId]?.defaultVisibility == true
+                }
+
+                var landscapeXPosition = preferences.migratePreference<Float>(
+                    "$button-X${Settings.PREF_LANDSCAPE_SUFFIX}"
+                )?.toDouble()
+                var landscapeYPosition = preferences.migratePreference<Float>(
+                    "$button-Y${Settings.PREF_LANDSCAPE_SUFFIX}"
+                )?.toDouble()
+                if (landscapeXPosition == null || landscapeYPosition == null) {
+                    val landscapePosition = OverlayControl.map[buttonId]
+                        ?.getDefaultPositionForLayout(OverlayLayout.Landscape) ?: Pair(0.0, 0.0)
+                    landscapeXPosition = landscapePosition.first
+                    landscapeYPosition = landscapePosition.second
+                }
+
+                var portraitXPosition = preferences.migratePreference<Float>(
+                    "$button-X${Settings.PREF_PORTRAIT_SUFFIX}"
+                )?.toDouble()
+                var portraitYPosition = preferences.migratePreference<Float>(
+                    "$button-Y${Settings.PREF_PORTRAIT_SUFFIX}"
+                )?.toDouble()
+                if (portraitXPosition == null || portraitYPosition == null) {
+                    val portraitPosition = OverlayControl.map[buttonId]
+                        ?.getDefaultPositionForLayout(OverlayLayout.Portrait) ?: Pair(0.0, 0.0)
+                    portraitXPosition = portraitPosition.first
+                    portraitYPosition = portraitPosition.second
+                }
+
+                var foldableXPosition = preferences.migratePreference<Float>(
+                    "$button-X${Settings.PREF_FOLDABLE_SUFFIX}"
+                )?.toDouble()
+                var foldableYPosition = preferences.migratePreference<Float>(
+                    "$button-Y${Settings.PREF_FOLDABLE_SUFFIX}"
+                )?.toDouble()
+                if (foldableXPosition == null || foldableYPosition == null) {
+                    val foldablePosition = OverlayControl.map[buttonId]
+                        ?.getDefaultPositionForLayout(OverlayLayout.Foldable) ?: Pair(0.0, 0.0)
+                    foldableXPosition = foldablePosition.first
+                    foldableYPosition = foldablePosition.second
+                }
+
+                val controlData = OverlayControlData(
+                    buttonId,
+                    buttonEnabled,
+                    Pair(landscapeXPosition, landscapeYPosition),
+                    Pair(portraitXPosition, portraitYPosition),
+                    Pair(foldableXPosition, foldableYPosition)
+                )
+                overlayControlDataMap[buttonId] = controlData
+                setOverlayData = true
+            }
+
+            if (setOverlayData) {
+                NativeConfig.setOverlayControlData(
+                    overlayControlDataMap.map { it.value }.toTypedArray()
+                )
+                saveConfig = true
+            }
+        }
+
         if (saveConfig) {
             NativeConfig.saveGlobalConfig()
         }
     }
+
+    private fun convertButtonId(buttonId: String): String =
+        when (buttonId) {
+            Settings.PREF_BUTTON_A -> OverlayControl.BUTTON_A.id
+            Settings.PREF_BUTTON_B -> OverlayControl.BUTTON_B.id
+            Settings.PREF_BUTTON_X -> OverlayControl.BUTTON_X.id
+            Settings.PREF_BUTTON_Y -> OverlayControl.BUTTON_Y.id
+            Settings.PREF_BUTTON_L -> OverlayControl.BUTTON_L.id
+            Settings.PREF_BUTTON_R -> OverlayControl.BUTTON_R.id
+            Settings.PREF_BUTTON_ZL -> OverlayControl.BUTTON_ZL.id
+            Settings.PREF_BUTTON_ZR -> OverlayControl.BUTTON_ZR.id
+            Settings.PREF_BUTTON_PLUS -> OverlayControl.BUTTON_PLUS.id
+            Settings.PREF_BUTTON_MINUS -> OverlayControl.BUTTON_MINUS.id
+            Settings.PREF_BUTTON_DPAD -> OverlayControl.COMBINED_DPAD.id
+            Settings.PREF_STICK_L -> OverlayControl.STICK_L.id
+            Settings.PREF_STICK_R -> OverlayControl.STICK_R.id
+            Settings.PREF_BUTTON_HOME -> OverlayControl.BUTTON_HOME.id
+            Settings.PREF_BUTTON_SCREENSHOT -> OverlayControl.BUTTON_CAPTURE.id
+            Settings.PREF_BUTTON_STICK_L -> OverlayControl.BUTTON_STICK_L.id
+            Settings.PREF_BUTTON_STICK_R -> OverlayControl.BUTTON_STICK_R.id
+            else -> ""
+        }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt
deleted file mode 100644
index 7e8f058c1..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/EmulationMenuSettings.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.utils
-
-import androidx.preference.PreferenceManager
-import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-
-object EmulationMenuSettings {
-    private val preferences =
-        PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-
-    var joystickRelCenter: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, true)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER, value)
-                .apply()
-        }
-    var dpadSlide: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, true)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_DPAD_SLIDE, value)
-                .apply()
-        }
-    var hapticFeedback: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, false)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_HAPTICS, value)
-                .apply()
-        }
-
-    var showFps: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, false)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_FPS, value)
-                .apply()
-        }
-    var showOverlay: Boolean
-        get() = preferences.getBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, true)
-        set(value) {
-            preferences.edit()
-                .putBoolean(Settings.PREF_MENU_SETTINGS_SHOW_OVERLAY, value)
-                .apply()
-        }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
index 7512d5eed..a4c14b3a7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -4,6 +4,7 @@
 package org.yuzu.yuzu_emu.utils
 
 import org.yuzu.yuzu_emu.model.GameDir
+import org.yuzu.yuzu_emu.overlay.model.OverlayControlData
 
 object NativeConfig {
     /**
@@ -150,4 +151,21 @@ object NativeConfig {
      */
     @Synchronized
     external fun setDisabledAddons(programId: String, disabledAddons: Array<String>)
+
+    /**
+     * Gets an array of [OverlayControlData] from settings
+     *
+     * @return An array of [OverlayControlData]
+     */
+    @Synchronized
+    external fun getOverlayControlData(): Array<OverlayControlData>
+
+    /**
+     * Clears the AndroidSettings::values.overlay_control_data array and replaces its values
+     * with [overlayControlData]
+     *
+     * @param overlayControlData Replacement array of [OverlayControlData]
+     */
+    @Synchronized
+    external fun setOverlayControlData(overlayControlData: Array<OverlayControlData>)
 }
diff --git a/src/android/app/src/main/jni/android_common/android_common.cpp b/src/android/app/src/main/jni/android_common/android_common.cpp
index 52d8ecfeb..1e884ffdd 100644
--- a/src/android/app/src/main/jni/android_common/android_common.cpp
+++ b/src/android/app/src/main/jni/android_common/android_common.cpp
@@ -9,6 +9,7 @@
 #include <jni.h>
 
 #include "common/string_util.h"
+#include "jni/id_cache.h"
 
 std::string GetJString(JNIEnv* env, jstring jstr) {
     if (!jstr) {
@@ -33,3 +34,11 @@ jstring ToJString(JNIEnv* env, std::string_view str) {
 jstring ToJString(JNIEnv* env, std::u16string_view str) {
     return ToJString(env, Common::UTF16ToUTF8(str));
 }
+
+double GetJDouble(JNIEnv* env, jobject jdouble) {
+    return env->GetDoubleField(jdouble, IDCache::GetDoubleValueField());
+}
+
+jobject ToJDouble(JNIEnv* env, double value) {
+    return env->NewObject(IDCache::GetDoubleClass(), IDCache::GetDoubleConstructor(), value);
+}
diff --git a/src/android/app/src/main/jni/android_common/android_common.h b/src/android/app/src/main/jni/android_common/android_common.h
index ccb0c06f7..8eb803e1b 100644
--- a/src/android/app/src/main/jni/android_common/android_common.h
+++ b/src/android/app/src/main/jni/android_common/android_common.h
@@ -10,3 +10,6 @@
 std::string GetJString(JNIEnv* env, jstring jstr);
 jstring ToJString(JNIEnv* env, std::string_view str);
 jstring ToJString(JNIEnv* env, std::u16string_view str);
+
+double GetJDouble(JNIEnv* env, jobject jdouble);
+jobject ToJDouble(JNIEnv* env, double value);
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
index fb17ab6f6..c86aa1c39 100644
--- a/src/android/app/src/main/jni/android_config.cpp
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -35,6 +35,7 @@ void AndroidConfig::ReadAndroidValues() {
     if (global) {
         ReadAndroidUIValues();
         ReadUIValues();
+        ReadOverlayValues();
     }
     ReadDriverValues();
 }
@@ -81,10 +82,42 @@ void AndroidConfig::ReadDriverValues() {
     EndGroup();
 }
 
+void AndroidConfig::ReadOverlayValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
+
+    ReadCategory(Settings::Category::Overlay);
+
+    AndroidSettings::values.overlay_control_data.clear();
+    const int control_data_size = BeginArray("control_data");
+    for (int i = 0; i < control_data_size; ++i) {
+        SetArrayIndex(i);
+        AndroidSettings::OverlayControlData control_data;
+        control_data.id = ReadStringSetting(std::string("id"));
+        control_data.enabled = ReadBooleanSetting(std::string("enabled"));
+        control_data.landscape_position.first =
+            ReadDoubleSetting(std::string("landscape\\x_position"));
+        control_data.landscape_position.second =
+            ReadDoubleSetting(std::string("landscape\\y_position"));
+        control_data.portrait_position.first =
+            ReadDoubleSetting(std::string("portrait\\x_position"));
+        control_data.portrait_position.second =
+            ReadDoubleSetting(std::string("portrait\\y_position"));
+        control_data.foldable_position.first =
+            ReadDoubleSetting(std::string("foldable\\x_position"));
+        control_data.foldable_position.second =
+            ReadDoubleSetting(std::string("foldable\\y_position"));
+        AndroidSettings::values.overlay_control_data.push_back(control_data);
+    }
+    EndArray();
+
+    EndGroup();
+}
+
 void AndroidConfig::SaveAndroidValues() {
     if (global) {
         SaveAndroidUIValues();
         SaveUIValues();
+        SaveOverlayValues();
     }
     SaveDriverValues();
 
@@ -131,6 +164,35 @@ void AndroidConfig::SaveDriverValues() {
     EndGroup();
 }
 
+void AndroidConfig::SaveOverlayValues() {
+    BeginGroup(Settings::TranslateCategory(Settings::Category::Overlay));
+
+    WriteCategory(Settings::Category::Overlay);
+
+    BeginArray("control_data");
+    for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
+        SetArrayIndex(i);
+        const auto& control_data = AndroidSettings::values.overlay_control_data[i];
+        WriteStringSetting(std::string("id"), control_data.id);
+        WriteBooleanSetting(std::string("enabled"), control_data.enabled);
+        WriteDoubleSetting(std::string("landscape\\x_position"),
+                           control_data.landscape_position.first);
+        WriteDoubleSetting(std::string("landscape\\y_position"),
+                           control_data.landscape_position.second);
+        WriteDoubleSetting(std::string("portrait\\x_position"),
+                           control_data.portrait_position.first);
+        WriteDoubleSetting(std::string("portrait\\y_position"),
+                           control_data.portrait_position.second);
+        WriteDoubleSetting(std::string("foldable\\x_position"),
+                           control_data.foldable_position.first);
+        WriteDoubleSetting(std::string("foldable\\y_position"),
+                           control_data.foldable_position.second);
+    }
+    EndArray();
+
+    EndGroup();
+}
+
 std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) {
     auto& map = Settings::values.linkage.by_category;
     if (map.contains(category)) {
diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h
index 2c12874e1..d83852de9 100644
--- a/src/android/app/src/main/jni/android_config.h
+++ b/src/android/app/src/main/jni/android_config.h
@@ -18,6 +18,7 @@ protected:
     void ReadAndroidValues();
     void ReadAndroidUIValues();
     void ReadDriverValues();
+    void ReadOverlayValues();
     void ReadHidbusValues() override {}
     void ReadDebugControlValues() override {}
     void ReadPathValues() override;
@@ -30,6 +31,7 @@ protected:
     void SaveAndroidValues();
     void SaveAndroidUIValues();
     void SaveDriverValues();
+    void SaveOverlayValues();
     void SaveHidbusValues() override {}
     void SaveDebugControlValues() override {}
     void SavePathValues() override;
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index 1e4906b9a..559ae83eb 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -14,6 +14,14 @@ struct GameDir {
     bool deep_scan = false;
 };
 
+struct OverlayControlData {
+    std::string id;
+    bool enabled;
+    std::pair<double, double> landscape_position;
+    std::pair<double, double> portrait_position;
+    std::pair<double, double> foldable_position;
+};
+
 struct Values {
     Settings::Linkage linkage;
 
@@ -38,6 +46,23 @@ struct Values {
     Settings::Setting<s32> theme_mode{linkage, -1, "theme_mode", Settings::Category::Android};
     Settings::Setting<bool> black_backgrounds{linkage, false, "black_backgrounds",
                                               Settings::Category::Android};
+
+    // Input/performance overlay settings
+    std::vector<OverlayControlData> overlay_control_data;
+    Settings::Setting<s32> overlay_scale{linkage, 50, "control_scale", Settings::Category::Overlay};
+    Settings::Setting<s32> overlay_opacity{linkage, 100, "control_opacity",
+                                           Settings::Category::Overlay};
+
+    Settings::Setting<bool> joystick_rel_center{linkage, true, "joystick_rel_center",
+                                                Settings::Category::Overlay};
+    Settings::Setting<bool> dpad_slide{linkage, true, "dpad_slide", Settings::Category::Overlay};
+    Settings::Setting<bool> haptic_feedback{linkage, true, "haptic_feedback",
+                                            Settings::Category::Overlay};
+    Settings::Setting<bool> show_performance_overlay{linkage, true, "show_performance_overlay",
+                                                     Settings::Category::Overlay};
+    Settings::Setting<bool> show_input_overlay{linkage, true, "show_input_overlay",
+                                               Settings::Category::Overlay};
+    Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay};
 };
 
 extern Values values;
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index e7a86d3fd..c79ad7d76 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -35,6 +35,18 @@ static jmethodID s_pair_constructor;
 static jfieldID s_pair_first_field;
 static jfieldID s_pair_second_field;
 
+static jclass s_overlay_control_data_class;
+static jmethodID s_overlay_control_data_constructor;
+static jfieldID s_overlay_control_data_id_field;
+static jfieldID s_overlay_control_data_enabled_field;
+static jfieldID s_overlay_control_data_landscape_position_field;
+static jfieldID s_overlay_control_data_portrait_position_field;
+static jfieldID s_overlay_control_data_foldable_position_field;
+
+static jclass s_double_class;
+static jmethodID s_double_constructor;
+static jfieldID s_double_value_field;
+
 static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
 
 namespace IDCache {
@@ -146,6 +158,46 @@ jfieldID GetPairSecondField() {
     return s_pair_second_field;
 }
 
+jclass GetOverlayControlDataClass() {
+    return s_overlay_control_data_class;
+}
+
+jmethodID GetOverlayControlDataConstructor() {
+    return s_overlay_control_data_constructor;
+}
+
+jfieldID GetOverlayControlDataIdField() {
+    return s_overlay_control_data_id_field;
+}
+
+jfieldID GetOverlayControlDataEnabledField() {
+    return s_overlay_control_data_enabled_field;
+}
+
+jfieldID GetOverlayControlDataLandscapePositionField() {
+    return s_overlay_control_data_landscape_position_field;
+}
+
+jfieldID GetOverlayControlDataPortraitPositionField() {
+    return s_overlay_control_data_portrait_position_field;
+}
+
+jfieldID GetOverlayControlDataFoldablePositionField() {
+    return s_overlay_control_data_foldable_position_field;
+}
+
+jclass GetDoubleClass() {
+    return s_double_class;
+}
+
+jmethodID GetDoubleConstructor() {
+    return s_double_constructor;
+}
+
+jfieldID GetDoubleValueField() {
+    return s_double_value_field;
+}
+
 } // namespace IDCache
 
 #ifdef __cplusplus
@@ -207,6 +259,31 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
     s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;");
     env->DeleteLocalRef(pair_class);
 
+    const jclass overlay_control_data_class =
+        env->FindClass("org/yuzu/yuzu_emu/overlay/model/OverlayControlData");
+    s_overlay_control_data_class =
+        reinterpret_cast<jclass>(env->NewGlobalRef(overlay_control_data_class));
+    s_overlay_control_data_constructor =
+        env->GetMethodID(overlay_control_data_class, "<init>",
+                         "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V");
+    s_overlay_control_data_id_field =
+        env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;");
+    s_overlay_control_data_enabled_field =
+        env->GetFieldID(overlay_control_data_class, "enabled", "Z");
+    s_overlay_control_data_landscape_position_field =
+        env->GetFieldID(overlay_control_data_class, "landscapePosition", "Lkotlin/Pair;");
+    s_overlay_control_data_portrait_position_field =
+        env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;");
+    s_overlay_control_data_foldable_position_field =
+        env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;");
+    env->DeleteLocalRef(overlay_control_data_class);
+
+    const jclass double_class = env->FindClass("java/lang/Double");
+    s_double_class = reinterpret_cast<jclass>(env->NewGlobalRef(double_class));
+    s_double_constructor = env->GetMethodID(double_class, "<init>", "(D)V");
+    s_double_value_field = env->GetFieldID(double_class, "value", "D");
+    env->DeleteLocalRef(double_class);
+
     // Initialize Android Storage
     Common::FS::Android::RegisterCallbacks(env, s_native_library_class);
 
@@ -231,6 +308,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
     env->DeleteGlobalRef(s_game_class);
     env->DeleteGlobalRef(s_string_class);
     env->DeleteGlobalRef(s_pair_class);
+    env->DeleteGlobalRef(s_overlay_control_data_class);
+    env->DeleteGlobalRef(s_double_class);
 
     // UnInitialize applets
     SoftwareKeyboard::CleanupJNI(env);
diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h
index 24030be42..784d1412f 100644
--- a/src/android/app/src/main/jni/id_cache.h
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -35,4 +35,16 @@ jmethodID GetPairConstructor();
 jfieldID GetPairFirstField();
 jfieldID GetPairSecondField();
 
+jclass GetOverlayControlDataClass();
+jmethodID GetOverlayControlDataConstructor();
+jfieldID GetOverlayControlDataIdField();
+jfieldID GetOverlayControlDataEnabledField();
+jfieldID GetOverlayControlDataLandscapePositionField();
+jfieldID GetOverlayControlDataPortraitPositionField();
+jfieldID GetOverlayControlDataFoldablePositionField();
+
+jclass GetDoubleClass();
+jmethodID GetDoubleConstructor();
+jfieldID GetDoubleValueField();
+
 } // namespace IDCache
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 324d9e9cd..535902483 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -344,4 +344,74 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, j
     Settings::values.disabled_addons[program_id] = disabled_addons;
 }
 
+jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JNIEnv* env,
+                                                                              jobject obj) {
+    jobjectArray joverlayControlDataArray =
+        env->NewObjectArray(AndroidSettings::values.overlay_control_data.size(),
+                            IDCache::GetOverlayControlDataClass(), nullptr);
+    for (size_t i = 0; i < AndroidSettings::values.overlay_control_data.size(); ++i) {
+        const auto& control_data = AndroidSettings::values.overlay_control_data[i];
+        jobject jlandscapePosition =
+            env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
+                           ToJDouble(env, control_data.landscape_position.first),
+                           ToJDouble(env, control_data.landscape_position.second));
+        jobject jportraitPosition =
+            env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
+                           ToJDouble(env, control_data.portrait_position.first),
+                           ToJDouble(env, control_data.portrait_position.second));
+        jobject jfoldablePosition =
+            env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(),
+                           ToJDouble(env, control_data.foldable_position.first),
+                           ToJDouble(env, control_data.foldable_position.second));
+
+        jobject jcontrolData = env->NewObject(
+            IDCache::GetOverlayControlDataClass(), IDCache::GetOverlayControlDataConstructor(),
+            ToJString(env, control_data.id), control_data.enabled, jlandscapePosition,
+            jportraitPosition, jfoldablePosition);
+        env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData);
+    }
+    return joverlayControlDataArray;
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData(
+    JNIEnv* env, jobject obj, jobjectArray joverlayControlDataArray) {
+    AndroidSettings::values.overlay_control_data.clear();
+    int size = env->GetArrayLength(joverlayControlDataArray);
+
+    if (size == 0) {
+        return;
+    }
+
+    for (int i = 0; i < size; ++i) {
+        jobject joverlayControlData = env->GetObjectArrayElement(joverlayControlDataArray, i);
+        jstring jidString = static_cast<jstring>(
+            env->GetObjectField(joverlayControlData, IDCache::GetOverlayControlDataIdField()));
+        bool enabled = static_cast<bool>(env->GetBooleanField(
+            joverlayControlData, IDCache::GetOverlayControlDataEnabledField()));
+
+        jobject jlandscapePosition = env->GetObjectField(
+            joverlayControlData, IDCache::GetOverlayControlDataLandscapePositionField());
+        std::pair<double, double> landscape_position = std::make_pair(
+            GetJDouble(env, env->GetObjectField(jlandscapePosition, IDCache::GetPairFirstField())),
+            GetJDouble(env,
+                       env->GetObjectField(jlandscapePosition, IDCache::GetPairSecondField())));
+
+        jobject jportraitPosition = env->GetObjectField(
+            joverlayControlData, IDCache::GetOverlayControlDataPortraitPositionField());
+        std::pair<double, double> portrait_position = std::make_pair(
+            GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairFirstField())),
+            GetJDouble(env, env->GetObjectField(jportraitPosition, IDCache::GetPairSecondField())));
+
+        jobject jfoldablePosition = env->GetObjectField(
+            joverlayControlData, IDCache::GetOverlayControlDataFoldablePositionField());
+        std::pair<double, double> foldable_position = std::make_pair(
+            GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairFirstField())),
+            GetJDouble(env, env->GetObjectField(jfoldablePosition, IDCache::GetPairSecondField())));
+
+        AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{
+            GetJString(env, jidString), enabled, landscape_position, portrait_position,
+            foldable_position});
+    }
+}
+
 } // extern "C"
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index c882a8e62..45d57c3ea 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -212,19 +212,19 @@
         <item>B</item>
         <item>X</item>
         <item>Y</item>
+        <item>+</item>
+        <item>-</item>
+        <item>@string/gamepad_home</item>
+        <item>@string/gamepad_screenshot</item>
         <item>L</item>
         <item>R</item>
         <item>ZL</item>
         <item>ZR</item>
-        <item>+</item>
-        <item>-</item>
-        <item>@string/gamepad_d_pad</item>
         <item>@string/gamepad_left_stick</item>
         <item>@string/gamepad_right_stick</item>
         <item>L3</item>
         <item>R3</item>
-        <item>@string/gamepad_home</item>
-        <item>@string/gamepad_screenshot</item>
+        <item>@string/gamepad_d_pad</item>
     </string-array>
 
     <string-array name="themeEntries">
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml
index dc527965c..1c6f5db93 100644
--- a/src/android/app/src/main/res/values/integers.xml
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -3,111 +3,111 @@
     <integer name="grid_columns">1</integer>
 
     <!-- Default SWITCH landscape layout -->
-    <integer name="SWITCH_BUTTON_A_X">760</integer>
-    <integer name="SWITCH_BUTTON_A_Y">790</integer>
-    <integer name="SWITCH_BUTTON_B_X">710</integer>
-    <integer name="SWITCH_BUTTON_B_Y">900</integer>
-    <integer name="SWITCH_BUTTON_X_X">710</integer>
-    <integer name="SWITCH_BUTTON_X_Y">680</integer>
-    <integer name="SWITCH_BUTTON_Y_X">660</integer>
-    <integer name="SWITCH_BUTTON_Y_Y">790</integer>
-    <integer name="SWITCH_STICK_L_X">100</integer>
-    <integer name="SWITCH_STICK_L_Y">670</integer>
-    <integer name="SWITCH_STICK_R_X">900</integer>
-    <integer name="SWITCH_STICK_R_Y">670</integer>
-    <integer name="SWITCH_TRIGGER_L_X">70</integer>
-    <integer name="SWITCH_TRIGGER_L_Y">220</integer>
-    <integer name="SWITCH_TRIGGER_R_X">930</integer>
-    <integer name="SWITCH_TRIGGER_R_Y">220</integer>
-    <integer name="SWITCH_TRIGGER_ZL_X">70</integer>
-    <integer name="SWITCH_TRIGGER_ZL_Y">90</integer>
-    <integer name="SWITCH_TRIGGER_ZR_X">930</integer>
-    <integer name="SWITCH_TRIGGER_ZR_Y">90</integer>
-    <integer name="SWITCH_BUTTON_MINUS_X">460</integer>
-    <integer name="SWITCH_BUTTON_MINUS_Y">950</integer>
-    <integer name="SWITCH_BUTTON_PLUS_X">540</integer>
-    <integer name="SWITCH_BUTTON_PLUS_Y">950</integer>
-    <integer name="SWITCH_BUTTON_HOME_X">600</integer>
-    <integer name="SWITCH_BUTTON_HOME_Y">950</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_X">400</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer>
-    <integer name="SWITCH_BUTTON_DPAD_X">260</integer>
-    <integer name="SWITCH_BUTTON_DPAD_Y">790</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_X">870</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_Y">400</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_X">960</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_Y">430</integer>
+    <integer name="BUTTON_A_X">760</integer>
+    <integer name="BUTTON_A_Y">790</integer>
+    <integer name="BUTTON_B_X">710</integer>
+    <integer name="BUTTON_B_Y">900</integer>
+    <integer name="BUTTON_X_X">710</integer>
+    <integer name="BUTTON_X_Y">680</integer>
+    <integer name="BUTTON_Y_X">660</integer>
+    <integer name="BUTTON_Y_Y">790</integer>
+    <integer name="BUTTON_PLUS_X">540</integer>
+    <integer name="BUTTON_PLUS_Y">950</integer>
+    <integer name="BUTTON_MINUS_X">460</integer>
+    <integer name="BUTTON_MINUS_Y">950</integer>
+    <integer name="BUTTON_HOME_X">600</integer>
+    <integer name="BUTTON_HOME_Y">950</integer>
+    <integer name="BUTTON_CAPTURE_X">400</integer>
+    <integer name="BUTTON_CAPTURE_Y">950</integer>
+    <integer name="BUTTON_L_X">70</integer>
+    <integer name="BUTTON_L_Y">220</integer>
+    <integer name="BUTTON_R_X">930</integer>
+    <integer name="BUTTON_R_Y">220</integer>
+    <integer name="BUTTON_ZL_X">70</integer>
+    <integer name="BUTTON_ZL_Y">90</integer>
+    <integer name="BUTTON_ZR_X">930</integer>
+    <integer name="BUTTON_ZR_Y">90</integer>
+    <integer name="BUTTON_STICK_L_X">870</integer>
+    <integer name="BUTTON_STICK_L_Y">400</integer>
+    <integer name="BUTTON_STICK_R_X">960</integer>
+    <integer name="BUTTON_STICK_R_Y">430</integer>
+    <integer name="STICK_L_X">100</integer>
+    <integer name="STICK_L_Y">670</integer>
+    <integer name="STICK_R_X">900</integer>
+    <integer name="STICK_R_Y">670</integer>
+    <integer name="COMBINED_DPAD_X">260</integer>
+    <integer name="COMBINED_DPAD_Y">790</integer>
 
     <!-- Default SWITCH portrait layout -->
-    <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer>
-    <integer name="SWITCH_BUTTON_A_Y_PORTRAIT">840</integer>
-    <integer name="SWITCH_BUTTON_B_X_PORTRAIT">740</integer>
-    <integer name="SWITCH_BUTTON_B_Y_PORTRAIT">880</integer>
-    <integer name="SWITCH_BUTTON_X_X_PORTRAIT">740</integer>
-    <integer name="SWITCH_BUTTON_X_Y_PORTRAIT">800</integer>
-    <integer name="SWITCH_BUTTON_Y_X_PORTRAIT">640</integer>
-    <integer name="SWITCH_BUTTON_Y_Y_PORTRAIT">840</integer>
-    <integer name="SWITCH_STICK_L_X_PORTRAIT">180</integer>
-    <integer name="SWITCH_STICK_L_Y_PORTRAIT">660</integer>
-    <integer name="SWITCH_STICK_R_X_PORTRAIT">820</integer>
-    <integer name="SWITCH_STICK_R_Y_PORTRAIT">660</integer>
-    <integer name="SWITCH_TRIGGER_L_X_PORTRAIT">140</integer>
-    <integer name="SWITCH_TRIGGER_L_Y_PORTRAIT">260</integer>
-    <integer name="SWITCH_TRIGGER_R_X_PORTRAIT">860</integer>
-    <integer name="SWITCH_TRIGGER_R_Y_PORTRAIT">260</integer>
-    <integer name="SWITCH_TRIGGER_ZL_X_PORTRAIT">140</integer>
-    <integer name="SWITCH_TRIGGER_ZL_Y_PORTRAIT">200</integer>
-    <integer name="SWITCH_TRIGGER_ZR_X_PORTRAIT">860</integer>
-    <integer name="SWITCH_TRIGGER_ZR_Y_PORTRAIT">200</integer>
-    <integer name="SWITCH_BUTTON_MINUS_X_PORTRAIT">440</integer>
-    <integer name="SWITCH_BUTTON_MINUS_Y_PORTRAIT">950</integer>
-    <integer name="SWITCH_BUTTON_PLUS_X_PORTRAIT">560</integer>
-    <integer name="SWITCH_BUTTON_PLUS_Y_PORTRAIT">950</integer>
-    <integer name="SWITCH_BUTTON_HOME_X_PORTRAIT">680</integer>
-    <integer name="SWITCH_BUTTON_HOME_Y_PORTRAIT">950</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_X_PORTRAIT">320</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
-    <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer>
-    <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_X_PORTRAIT">730</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_Y_PORTRAIT">510</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_X_PORTRAIT">900</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_Y_PORTRAIT">540</integer>
+    <integer name="BUTTON_A_X_PORTRAIT">840</integer>
+    <integer name="BUTTON_A_Y_PORTRAIT">840</integer>
+    <integer name="BUTTON_B_X_PORTRAIT">740</integer>
+    <integer name="BUTTON_B_Y_PORTRAIT">880</integer>
+    <integer name="BUTTON_X_X_PORTRAIT">740</integer>
+    <integer name="BUTTON_X_Y_PORTRAIT">800</integer>
+    <integer name="BUTTON_Y_X_PORTRAIT">640</integer>
+    <integer name="BUTTON_Y_Y_PORTRAIT">840</integer>
+    <integer name="BUTTON_PLUS_Y_PORTRAIT">950</integer>
+    <integer name="BUTTON_MINUS_X_PORTRAIT">440</integer>
+    <integer name="BUTTON_MINUS_Y_PORTRAIT">950</integer>
+    <integer name="BUTTON_HOME_X_PORTRAIT">680</integer>
+    <integer name="BUTTON_HOME_Y_PORTRAIT">950</integer>
+    <integer name="BUTTON_CAPTURE_X_PORTRAIT">320</integer>
+    <integer name="BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
+    <integer name="BUTTON_L_X_PORTRAIT">140</integer>
+    <integer name="BUTTON_L_Y_PORTRAIT">260</integer>
+    <integer name="BUTTON_R_X_PORTRAIT">860</integer>
+    <integer name="BUTTON_R_Y_PORTRAIT">260</integer>
+    <integer name="BUTTON_ZL_X_PORTRAIT">140</integer>
+    <integer name="BUTTON_ZL_Y_PORTRAIT">200</integer>
+    <integer name="BUTTON_ZR_X_PORTRAIT">860</integer>
+    <integer name="BUTTON_ZR_Y_PORTRAIT">200</integer>
+    <integer name="BUTTON_PLUS_X_PORTRAIT">560</integer>
+    <integer name="BUTTON_STICK_L_X_PORTRAIT">730</integer>
+    <integer name="BUTTON_STICK_L_Y_PORTRAIT">510</integer>
+    <integer name="BUTTON_STICK_R_X_PORTRAIT">900</integer>
+    <integer name="BUTTON_STICK_R_Y_PORTRAIT">540</integer>
+    <integer name="STICK_L_X_PORTRAIT">180</integer>
+    <integer name="STICK_L_Y_PORTRAIT">660</integer>
+    <integer name="STICK_R_X_PORTRAIT">820</integer>
+    <integer name="STICK_R_Y_PORTRAIT">660</integer>
+    <integer name="COMBINED_DPAD_X_PORTRAIT">240</integer>
+    <integer name="COMBINED_DPAD_Y_PORTRAIT">840</integer>
 
     <!-- Default SWITCH foldable layout -->
-    <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer>
-    <integer name="SWITCH_BUTTON_A_Y_FOLDABLE">390</integer>
-    <integer name="SWITCH_BUTTON_B_X_FOLDABLE">740</integer>
-    <integer name="SWITCH_BUTTON_B_Y_FOLDABLE">430</integer>
-    <integer name="SWITCH_BUTTON_X_X_FOLDABLE">740</integer>
-    <integer name="SWITCH_BUTTON_X_Y_FOLDABLE">350</integer>
-    <integer name="SWITCH_BUTTON_Y_X_FOLDABLE">640</integer>
-    <integer name="SWITCH_BUTTON_Y_Y_FOLDABLE">390</integer>
-    <integer name="SWITCH_STICK_L_X_FOLDABLE">180</integer>
-    <integer name="SWITCH_STICK_L_Y_FOLDABLE">250</integer>
-    <integer name="SWITCH_STICK_R_X_FOLDABLE">820</integer>
-    <integer name="SWITCH_STICK_R_Y_FOLDABLE">250</integer>
-    <integer name="SWITCH_TRIGGER_L_X_FOLDABLE">140</integer>
-    <integer name="SWITCH_TRIGGER_L_Y_FOLDABLE">130</integer>
-    <integer name="SWITCH_TRIGGER_R_X_FOLDABLE">860</integer>
-    <integer name="SWITCH_TRIGGER_R_Y_FOLDABLE">130</integer>
-    <integer name="SWITCH_TRIGGER_ZL_X_FOLDABLE">140</integer>
-    <integer name="SWITCH_TRIGGER_ZL_Y_FOLDABLE">70</integer>
-    <integer name="SWITCH_TRIGGER_ZR_X_FOLDABLE">860</integer>
-    <integer name="SWITCH_TRIGGER_ZR_Y_FOLDABLE">70</integer>
-    <integer name="SWITCH_BUTTON_MINUS_X_FOLDABLE">440</integer>
-    <integer name="SWITCH_BUTTON_MINUS_Y_FOLDABLE">470</integer>
-    <integer name="SWITCH_BUTTON_PLUS_X_FOLDABLE">560</integer>
-    <integer name="SWITCH_BUTTON_PLUS_Y_FOLDABLE">470</integer>
-    <integer name="SWITCH_BUTTON_HOME_X_FOLDABLE">680</integer>
-    <integer name="SWITCH_BUTTON_HOME_Y_FOLDABLE">470</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_X_FOLDABLE">320</integer>
-    <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
-    <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer>
-    <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_X_FOLDABLE">550</integer>
-    <integer name="SWITCH_BUTTON_STICK_L_Y_FOLDABLE">210</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_X_FOLDABLE">550</integer>
-    <integer name="SWITCH_BUTTON_STICK_R_Y_FOLDABLE">280</integer>
+    <integer name="BUTTON_A_X_FOLDABLE">840</integer>
+    <integer name="BUTTON_A_Y_FOLDABLE">390</integer>
+    <integer name="BUTTON_B_X_FOLDABLE">740</integer>
+    <integer name="BUTTON_B_Y_FOLDABLE">430</integer>
+    <integer name="BUTTON_X_X_FOLDABLE">740</integer>
+    <integer name="BUTTON_X_Y_FOLDABLE">350</integer>
+    <integer name="BUTTON_Y_X_FOLDABLE">640</integer>
+    <integer name="BUTTON_Y_Y_FOLDABLE">390</integer>
+    <integer name="BUTTON_PLUS_X_FOLDABLE">560</integer>
+    <integer name="BUTTON_PLUS_Y_FOLDABLE">470</integer>
+    <integer name="BUTTON_MINUS_X_FOLDABLE">440</integer>
+    <integer name="BUTTON_MINUS_Y_FOLDABLE">470</integer>
+    <integer name="BUTTON_HOME_X_FOLDABLE">680</integer>
+    <integer name="BUTTON_HOME_Y_FOLDABLE">470</integer>
+    <integer name="BUTTON_CAPTURE_X_FOLDABLE">320</integer>
+    <integer name="BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
+    <integer name="BUTTON_L_X_FOLDABLE">140</integer>
+    <integer name="BUTTON_L_Y_FOLDABLE">130</integer>
+    <integer name="BUTTON_R_X_FOLDABLE">860</integer>
+    <integer name="BUTTON_R_Y_FOLDABLE">130</integer>
+    <integer name="BUTTON_ZL_X_FOLDABLE">140</integer>
+    <integer name="BUTTON_ZL_Y_FOLDABLE">70</integer>
+    <integer name="BUTTON_ZR_X_FOLDABLE">860</integer>
+    <integer name="BUTTON_ZR_Y_FOLDABLE">70</integer>
+    <integer name="BUTTON_STICK_L_X_FOLDABLE">550</integer>
+    <integer name="BUTTON_STICK_L_Y_FOLDABLE">210</integer>
+    <integer name="BUTTON_STICK_R_X_FOLDABLE">550</integer>
+    <integer name="BUTTON_STICK_R_Y_FOLDABLE">280</integer>
+    <integer name="STICK_L_X_FOLDABLE">180</integer>
+    <integer name="STICK_L_Y_FOLDABLE">250</integer>
+    <integer name="STICK_R_X_FOLDABLE">820</integer>
+    <integer name="STICK_R_Y_FOLDABLE">250</integer>
+    <integer name="COMBINED_DPAD_X_FOLDABLE">240</integer>
+    <integer name="COMBINED_DPAD_Y_FOLDABLE">390</integer>
 
 </resources>
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index ea52bbfa6..07709d4e5 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -199,6 +199,8 @@ const char* TranslateCategory(Category category) {
     case Category::CpuDebug:
     case Category::CpuUnsafe:
         return "Cpu";
+    case Category::Overlay:
+        return "Overlay";
     case Category::Renderer:
     case Category::RendererAdvanced:
     case Category::RendererDebug:
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index c82e17495..1a290ad77 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -18,6 +18,7 @@ enum class Category : u32 {
     Cpu,
     CpuDebug,
     CpuUnsafe,
+    Overlay,
     Renderer,
     RendererAdvanced,
     RendererDebug,

From 24a78dba319fc1183a47ec94e6a61add5225eb75 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Fri, 29 Dec 2023 01:08:12 -0500
Subject: [PATCH 5/7] frontend_common: config: Do not count "." as a special
 character

It would break reading floating point settings
---
 src/frontend_common/config.h | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/frontend_common/config.h b/src/frontend_common/config.h
index e73cf6929..0c4d505b8 100644
--- a/src/frontend_common/config.h
+++ b/src/frontend_common/config.h
@@ -213,9 +213,8 @@ private:
                               const std::optional<bool>& use_global);
     void WriteString(const std::string& key, const std::string& value);
 
-    inline static std::array<char, 19> special_characters = {'!', '#', '$',  '%',  '^', '&', '*',
-                                                             '|', ';', '\'', '\"', ',', '<', '.',
-                                                             '>', '?', '`',  '~',  '='};
+    inline static std::array<char, 18> special_characters = {
+        '!', '#', '$', '%', '^', '&', '*', '|', ';', '\'', '\"', ',', '<', '>', '?', '`', '~', '='};
 
     struct ConfigArray {
         std::string name;

From 278cafb76c016427c9e524a38cd0b42caf25d72a Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Fri, 29 Dec 2023 16:38:15 -0500
Subject: [PATCH 6/7] android: Don't apply material you below android 12

Since you can manually change the config file now, users can force the material you theme under Android 12 and that will result in crashing on startup. This swaps to the default theme if that happens.
---
 .../src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
index 792f6a253..6f7f40e43 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt
@@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu.utils
 
 import android.content.res.Configuration
 import android.graphics.Color
+import android.os.Build
 import androidx.annotation.ColorInt
 import androidx.appcompat.app.AppCompatActivity
 import androidx.appcompat.app.AppCompatDelegate
@@ -23,7 +24,13 @@ object ThemeHelper {
         setThemeMode(activity)
         when (Theme.from(IntSetting.THEME.getInt())) {
             Theme.Default -> activity.setTheme(R.style.Theme_Yuzu_Main)
-            Theme.MaterialYou -> activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
+            Theme.MaterialYou -> {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                    activity.setTheme(R.style.Theme_Yuzu_Main_MaterialYou)
+                } else {
+                    activity.setTheme(R.style.Theme_Yuzu_Main)
+                }
+            }
         }
 
         // Using a specific night mode check because this could apply incorrectly when using the

From aa4d15594f7bfd1c1aeb5d9ba40a59ff689e25e7 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Fri, 29 Dec 2023 16:55:00 -0500
Subject: [PATCH 7/7] android: Expose touchscreen toggle

---
 .../java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt  | 7 +++++++
 src/android/app/src/main/res/menu/menu_overlay_options.xml | 5 +++++
 src/android/app/src/main/res/values/strings.xml            | 1 +
 3 files changed, 13 insertions(+)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 6e5dd1dba..510b2b5eb 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -539,6 +539,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
             findItem(R.id.menu_show_overlay).isChecked =
                 BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()
             findItem(R.id.menu_haptics).isChecked = BooleanSetting.HAPTIC_FEEDBACK.getBoolean()
+            findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.getBoolean()
         }
 
         popup.setOnMenuItemClickListener {
@@ -628,6 +629,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                     true
                 }
 
+                R.id.menu_touchscreen -> {
+                    it.isChecked = !it.isChecked
+                    BooleanSetting.TOUCHSCREEN.setBoolean(it.isChecked)
+                    true
+                }
+
                 R.id.menu_reset_overlay -> {
                     binding.drawerLayout.close()
                     resetInputOverlay()
diff --git a/src/android/app/src/main/res/menu/menu_overlay_options.xml b/src/android/app/src/main/res/menu/menu_overlay_options.xml
index 4885b4f6f..363781652 100644
--- a/src/android/app/src/main/res/menu/menu_overlay_options.xml
+++ b/src/android/app/src/main/res/menu/menu_overlay_options.xml
@@ -38,6 +38,11 @@
         android:title="@string/emulation_haptics"
         android:checkable="true" />
 
+    <item
+        android:id="@+id/menu_touchscreen"
+        android:title="@string/touchscreen"
+        android:checkable="true" />
+
     <item
         android:id="@+id/menu_reset_overlay"
         android:title="@string/emulation_touch_overlay_reset" />
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 4d5c268fe..1bedcb1ef 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -366,6 +366,7 @@
     <string name="emulation_pause">Pause emulation</string>
     <string name="emulation_unpause">Unpause emulation</string>
     <string name="emulation_input_overlay">Overlay options</string>
+    <string name="touchscreen">Touchscreen</string>
 
     <string name="load_settings">Loading settings…</string>