diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index f763c657e3f9fb824f65a9a8ac2494e9b5011fc2..53aafa08c2395445e54786191a3f5d5fcdc71347 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/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 9b08f008d17fbc24dfc2fef011557a783a6d7139..93c8ce92252a857294e143f4634716f8c0e46fd3 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 16f06cd0af76d268f6dc9d095eaff8dba6f33171..86bd3367264bfed98d5fb0eb281d128a180bbe3f 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,14 @@ 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"),
+    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 df760440f25f1772f904c131dced3f860f02b310..16fb87614fcffe0709303485ddb2f90ef010ebee 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,11 @@ 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"),
+    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 9551fc05e49d0794f6ef32840bec43e64bd6c6ed..43caac989a01f318aaaf3d7c3b29b8692180d3d5 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,23 +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"
-
-    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,
@@ -83,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/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index db1a1076cd104e808ac3e7cb35dffb4979cb114e..2ad2f496647b2a2309572ff4140cdf9c13f1557b 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 val key: String = BooleanSetting.BLACK_BACKGROUNDS.key
+                override val isRuntimeModifiable: Boolean =
+                    BooleanSetting.BLACK_BACKGROUNDS.isRuntimeModifiable
+
                 override fun getValueAsString(needsGlobal: Boolean): String =
-                    getBoolean().toString()
+                    BooleanSetting.BLACK_BACKGROUNDS.getValueAsString()
 
-                override val defaultValue: Boolean = false
+                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/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index d7b38f62d01bd5a911ae9095b3903d6d2b899c29..510b2b5eb5b534d9f14dc332e71aadff7f209ff3 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,22 @@ 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()
+            findItem(R.id.menu_touchscreen).isChecked = BooleanSetting.TOUCHSCREEN.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 +564,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 +578,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 +595,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 +606,32 @@ 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
+                }
+
+                R.id.menu_touchscreen -> {
+                    it.isChecked = !it.isChecked
+                    BooleanSetting.TOUCHSCREEN.setBoolean(it.isChecked)
                     true
                 }
 
@@ -667,6 +674,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
             }
         }
+        NativeConfig.saveGlobalConfig()
     }
 
     @SuppressLint("SetTextI18n")
@@ -675,7 +683,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 +693,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 +717,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 a13faf3c78dc7bd04d90e4e3de71459aa4e77034..bb69b8bd54951db420e2b5234831ef0c4ec2cad5 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 2c28dda8865d992572e47ca826e9ebda5b751e54..b14a4f96e49573ffd3395dca77a2618b8b07ce98 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 518b1e783743798361b804e62d79d6ad5f709f2b..113bf7c2480e6ace2cc09242c563435099ab70b5 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 0000000000000000000000000000000000000000..a0eeadf4bcaa12a9009539610597548de6ed4797
--- /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 0000000000000000000000000000000000000000..26cfeb1db52246f49b8c3d8ee0644fde80b67302
--- /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 0000000000000000000000000000000000000000..6bd74c82fe3c4e95d15cebd7cf638c066fdbef6b
--- /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 0000000000000000000000000000000000000000..d728164e5916dff80e1012aff3a0851a85ea0aeb
--- /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 0197fd712ff4d613f0bf8a4197f18e8067b1eeb7..de0794a17fc73a4dc841849b5fc3cacee0385643 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,17 @@
 
 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.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 {
     private var userPath: String? = null
@@ -17,6 +25,7 @@ object DirectoryInitialization {
             initializeInternalStorage()
             NativeLibrary.initializeSystem(false)
             NativeConfig.initializeGlobalConfig()
+            migrateSettings()
             areDirectoriesReady = true
         }
     }
@@ -35,4 +44,170 @@ 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
+        }
+
+        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 7e8f058c14d9381c12ce0efee660629ae8df5f48..0000000000000000000000000000000000000000
--- 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 7512d5eed9f3f840ef345071350c017ce7c00c53..a4c14b3a7725d72030547b12cb387609145c1f1b 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/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 0000000000000000000000000000000000000000..a233ba25c720ee5f7a80ee0ef7b4578c0364341a
--- /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 f312e24cf1d4bebaeabf861fca733c2ca1c5121c..6f7f40e436e31c5f47a3506dc0ce3bb25595ceb4 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,38 +5,38 @@ 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
 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 -> {
+                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
         // 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 +60,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 +94,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_common/android_common.cpp b/src/android/app/src/main/jni/android_common/android_common.cpp
index 52d8ecfeb0257ec650adb6ad9fbbe073e952527b..1e884ffdd2ed603d6b32de9af56a1c1530486643 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 ccb0c06f74048fe1d2b94fbb2ba5fc32e22c37b2..8eb803e1b08c49333cec490d007266c2954eb382 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 9c3a5a9b298a8b164ed2ed10ecdf5f74a410bd36..c86aa1c3965a4abbd727e486f12a9c00c72b55a9 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();
 
@@ -114,8 +147,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();
 
@@ -130,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 2c12874e15444b048335b777cab020ebed229775..d83852de9eb434f1fb316d35a692bb9707b34bb7 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 3733f5a3c53df047e71d418f3c82f8fcc2964903..559ae83eb739b5179efb78f976444356f380498c 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;
 
@@ -33,6 +41,28 @@ 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};
+
+    // 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 e7a86d3fd0f926b6decaff925d33637d40a6a7cc..c79ad7d76b96dc36ae430e76d8194719c3603fe6 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 24030be42185c105c4dfea1a4628062eb445f3f9..784d1412f659a379c7599b847e5215c32f5a4ee8 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 324d9e9cdc72961e2b31a3dd7ed5ab3574752535..5359024833de8c13077367e415fd5d7712bf1e76 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/menu/menu_overlay_options.xml b/src/android/app/src/main/res/menu/menu_overlay_options.xml
index 4885b4f6fb6922cda798279a920e87eb31cf55f2..36378165288921e7068d9a041b15cc8774a63888 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/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index c882a8e62922eedc64bfb885566ccd8f53a8550d..45d57c3ea684ec441d07e5ce329dadd80e57a993 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 dc527965c9eed7f2e121215c7abf8080aa4b2368..1c6f5db93a66f35dd4ad921ba54233fd790b4a25 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/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 4d5c268fe842e1dd60f53ae8a92a4e09b1e47584..1bedcb1ef72e696736f9367909d4c5d9159a73e1 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>
 
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 51e559321582216bc44f1bdc2d3ab9d77c05a589..b77906ed6d546fa5844041dec693409d39169f1f 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 {
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index ea52bbfa677b616fc9282e69c04d1779f84f7e17..07709d4e5c193fda96640e238e254fe300a22c0e 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 c82e174959729436f310ef75a3faad1c717f0238..1a290ad7708419fe7fdb1656987c2b4a881c2b0f 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,
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp
index d9f99148bce8f28ea2a45a3a405d08d6e9a4776c..51576b4eeb1305d7d2e6d6212245d309faedb5f2 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);
+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);
+}
 
-    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()));
-        }
+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);
+}
 
-    if (default_value.has_value() && use_global.has_value()) {
+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 b3812af17f8e078332cc52d30daf93aa8bf1ebc1..0c4d505b8919b3fc4ab51c498126a9c44bc59248 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,9 +208,13 @@ protected:
     const bool global;
 
 private:
-    inline static std::array<char, 19> special_characters = {'!', '#', '$',  '%',  '^', '&', '*',
-                                                             '|', ';', '\'', '\"', ',', '<', '.',
-                                                             '>', '?', '`',  '~',  '='};
+    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, 18> special_characters = {
+        '!', '#', '$', '%', '^', '&', '*', '|', ';', '\'', '\"', ',', '<', '>', '?', '`', '~', '='};
 
     struct ConfigArray {
         std::string name;
diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp
index a71000b72f673b2bfb923fb53198c7915be64734..6aca71d7c45db9352a28493152d499f7714748a1 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 39fd8050c6f9fd1c32a181c4ee27856bef3d8135..e81bf5d45f4309e64c7559ea60e3fbdc21431175 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) {