diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 9c32e044c0..5a7cf4ed73 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -219,10 +219,6 @@ object NativeLibrary {
 
     external fun reloadSettings()
 
-    external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String?
-
-    external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?)
-
     external fun initGameIni(gameID: String?)
 
     /**
@@ -413,14 +409,17 @@ object NativeLibrary {
                     details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
                 )
             }
+
             CoreError.ErrorSavestate -> {
                 title = emulationActivity.getString(R.string.save_load_error)
                 message = details
             }
+
             CoreError.ErrorUnknown -> {
                 title = emulationActivity.getString(R.string.fatal_error)
                 message = emulationActivity.getString(R.string.fatal_error_message)
             }
+
             else -> {
                 return true
             }
@@ -454,6 +453,7 @@ object NativeLibrary {
                 captionId = R.string.loader_error_video_core
                 descriptionId = R.string.loader_error_video_core_description
             }
+
             else -> {
                 captionId = R.string.loader_error_encrypted
                 descriptionId = R.string.loader_error_encrypted_roms_description
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 04ab6a220a..9561748cbb 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -46,7 +46,7 @@ class YuzuApplication : Application() {
         super.onCreate()
         application = this
         documentsTree = DocumentsTree()
-        DirectoryInitialization.start(applicationContext)
+        DirectoryInitialization.start()
         GpuDriverHelper.initializeDriverParameters(applicationContext)
         NativeLibrary.logDeviceInfo()
 
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 7461fb093f..dbd602a1d0 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
@@ -28,7 +28,6 @@ import android.view.Surface
 import android.view.View
 import android.view.inputmethod.InputMethodManager
 import android.widget.Toast
-import androidx.activity.viewModels
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
@@ -42,7 +41,6 @@ import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
 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.model.SettingsViewModel
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
 import org.yuzu.yuzu_emu.utils.ForegroundService
@@ -72,8 +70,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
     private val actionMute = "ACTION_EMULATOR_MUTE"
     private val actionUnmute = "ACTION_EMULATOR_UNMUTE"
 
-    private val settingsViewModel: SettingsViewModel by viewModels()
-
     override fun onDestroy() {
         stopForegroundService(this)
         super.onDestroy()
@@ -82,8 +78,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
     override fun onCreate(savedInstanceState: Bundle?) {
         ThemeHelper.setTheme(this)
 
-        settingsViewModel.settings.loadSettings()
-
         super.onCreate(savedInstanceState)
 
         binding = ActivityEmulationBinding.inflate(layoutInflater)
@@ -91,9 +85,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
 
         val navHostFragment =
             supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
-        val navController = navHostFragment.navController
-        navController
-            .setGraph(R.navigation.emulation_navigation, intent.extras)
+        navHostFragment.navController.setGraph(R.navigation.emulation_navigation, intent.extras)
 
         isActivityRecreated = savedInstanceState != null
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
index a6e9833ee7..aeda8d2220 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt
@@ -4,5 +4,7 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 interface AbstractBooleanSetting : AbstractSetting {
-    var boolean: Boolean
+    val boolean: Boolean
+
+    fun setBoolean(value: Boolean)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt
similarity index 59%
rename from src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt
rename to src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt
index bd9233d625..606519ad84 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt
@@ -3,8 +3,8 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
-import androidx.lifecycle.ViewModel
+interface AbstractByteSetting : AbstractSetting {
+    val byte: Byte
 
-class SettingsViewModel : ViewModel() {
-    val settings = Settings()
+    fun setByte(value: Byte)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
index 6fe4bc2635..974925eeda 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt
@@ -4,5 +4,7 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 interface AbstractFloatSetting : AbstractSetting {
-    var float: Float
+    val float: Float
+
+    fun setFloat(value: Float)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
index 892b7dcfe1..89b285b108 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt
@@ -4,5 +4,7 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 interface AbstractIntSetting : AbstractSetting {
-    var int: Int
+    val int: Int
+
+    fun setInt(value: Int)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
new file mode 100644
index 0000000000..4873942db7
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+interface AbstractLongSetting : AbstractSetting {
+    val long: Long
+
+    fun setLong(value: Long)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
index 2585802092..8b6d29fe5b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt
@@ -3,10 +3,22 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 interface AbstractSetting {
-    val key: String?
-    val section: String?
-    val isRuntimeEditable: Boolean
-    val valueAsString: String
+    val key: String
+    val category: Settings.Category
     val defaultValue: Any
+    val androidDefault: Any?
+        get() = null
+    val valueAsString: String
+        get() = ""
+
+    val isRuntimeModifiable: Boolean
+        get() = NativeConfig.getIsRuntimeModifiable(key)
+
+    val pairedSettingKey: String
+        get() = NativeConfig.getPairedSettingKey(key)
+
+    fun reset()
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
new file mode 100644
index 0000000000..91407ccbb4
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+interface AbstractShortSetting : AbstractSetting {
+    val short: Short
+
+    fun setShort(value: Short)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
index 0d02c59973..c8935cc48c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt
@@ -4,5 +4,7 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 interface AbstractStringSetting : AbstractSetting {
-    var string: String
+    val string: String
+
+    fun setString(value: String)
 }
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 d41933766d..e0c0538c78 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
@@ -3,41 +3,37 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 enum class BooleanSetting(
     override val key: String,
-    override val section: String,
-    override val defaultValue: Boolean
+    override val category: Settings.Category,
+    override val androidDefault: Boolean? = null
 ) : AbstractBooleanSetting {
-    CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
-    FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
-    FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
-    PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
-    USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
+    CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
+    FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
+    FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
+    RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core),
+    USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false),
+    RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer),
+    RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer),
+    RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer),
+    RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false),
+    RENDERER_DEBUG("debug", Settings.Category.Renderer),
+    PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android),
+    USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System);
 
-    override var boolean: Boolean = defaultValue
+    override val boolean: Boolean
+        get() = NativeConfig.getBoolean(key, false)
+
+    override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value)
+
+    override val defaultValue: Boolean by lazy {
+        androidDefault ?: NativeConfig.getBoolean(key, true)
+    }
 
     override val valueAsString: String
-        get() = boolean.toString()
+        get() = if (boolean) "1" else "0"
 
-    override val isRuntimeEditable: Boolean
-        get() {
-            for (setting in NOT_RUNTIME_EDITABLE) {
-                if (setting == this) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    companion object {
-        private val NOT_RUNTIME_EDITABLE = listOf(
-            PICTURE_IN_PICTURE,
-            USE_CUSTOM_RTC
-        )
-
-        fun from(key: String): BooleanSetting? =
-            BooleanSetting.values().firstOrNull { it.key == key }
-
-        fun clear() = BooleanSetting.values().forEach { it.boolean = it.defaultValue }
-    }
+    override fun reset() = NativeConfig.setBoolean(key, defaultValue)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
new file mode 100644
index 0000000000..6ec0a765ef
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class ByteSetting(
+    override val key: String,
+    override val category: Settings.Category
+) : AbstractByteSetting {
+    AUDIO_VOLUME("volume", Settings.Category.Audio);
+
+    override val byte: Byte
+        get() = NativeConfig.getByte(key, false)
+
+    override fun setByte(value: Byte) = NativeConfig.setByte(key, value)
+
+    override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) }
+
+    override val valueAsString: String
+        get() = byte.toString()
+
+    override fun reset() = NativeConfig.setByte(key, defaultValue)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
index e5545a916a..0181d06f21 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt
@@ -3,34 +3,24 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 enum class FloatSetting(
     override val key: String,
-    override val section: String,
-    override val defaultValue: Float
+    override val category: Settings.Category
 ) : AbstractFloatSetting {
     // No float settings currently exist
-    EMPTY_SETTING("", "", 0f);
+    EMPTY_SETTING("", Settings.Category.UiGeneral);
 
-    override var float: Float = defaultValue
+    override val float: Float
+        get() = NativeConfig.getFloat(key, false)
+
+    override fun setFloat(value: Float) = NativeConfig.setFloat(key, value)
+
+    override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) }
 
     override val valueAsString: String
         get() = float.toString()
 
-    override val isRuntimeEditable: Boolean
-        get() {
-            for (setting in NOT_RUNTIME_EDITABLE) {
-                if (setting == this) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    companion object {
-        private val NOT_RUNTIME_EDITABLE = emptyList<FloatSetting>()
-
-        fun from(key: String): FloatSetting? = FloatSetting.values().firstOrNull { it.key == key }
-
-        fun clear() = FloatSetting.values().forEach { it.float = it.defaultValue }
-    }
+    override fun reset() = NativeConfig.setFloat(key, defaultValue)
 }
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 4427a7d9dc..1513621243 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
@@ -3,139 +3,37 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 enum class IntSetting(
     override val key: String,
-    override val section: String,
-    override val defaultValue: Int
+    override val category: Settings.Category,
+    override val androidDefault: Int? = null
 ) : AbstractIntSetting {
-    RENDERER_USE_SPEED_LIMIT(
-        "use_speed_limit",
-        Settings.SECTION_RENDERER,
-        1
-    ),
-    USE_DOCKED_MODE(
-        "use_docked_mode",
-        Settings.SECTION_SYSTEM,
-        0
-    ),
-    RENDERER_USE_DISK_SHADER_CACHE(
-        "use_disk_shader_cache",
-        Settings.SECTION_RENDERER,
-        1
-    ),
-    RENDERER_FORCE_MAX_CLOCK(
-        "force_max_clock",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_ASYNCHRONOUS_SHADERS(
-        "use_asynchronous_shaders",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_REACTIVE_FLUSHING(
-        "use_reactive_flushing",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_DEBUG(
-        "debug",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_SPEED_LIMIT(
-        "speed_limit",
-        Settings.SECTION_RENDERER,
-        100
-    ),
-    CPU_ACCURACY(
-        "cpu_accuracy",
-        Settings.SECTION_CPU,
-        0
-    ),
-    REGION_INDEX(
-        "region_index",
-        Settings.SECTION_SYSTEM,
-        -1
-    ),
-    LANGUAGE_INDEX(
-        "language_index",
-        Settings.SECTION_SYSTEM,
-        1
-    ),
-    RENDERER_BACKEND(
-        "backend",
-        Settings.SECTION_RENDERER,
-        1
-    ),
-    RENDERER_ACCURACY(
-        "gpu_accuracy",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_RESOLUTION(
-        "resolution_setup",
-        Settings.SECTION_RENDERER,
-        2
-    ),
-    RENDERER_VSYNC(
-        "use_vsync",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_SCALING_FILTER(
-        "scaling_filter",
-        Settings.SECTION_RENDERER,
-        1
-    ),
-    RENDERER_ANTI_ALIASING(
-        "anti_aliasing",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    RENDERER_SCREEN_LAYOUT(
-        "screen_layout",
-        Settings.SECTION_RENDERER,
-        Settings.LayoutOption_MobileLandscape
-    ),
-    RENDERER_ASPECT_RATIO(
-        "aspect_ratio",
-        Settings.SECTION_RENDERER,
-        0
-    ),
-    AUDIO_VOLUME(
-        "volume",
-        Settings.SECTION_AUDIO,
-        100
-    );
+    CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu),
+    REGION_INDEX("region_index", Settings.Category.System),
+    LANGUAGE_INDEX("language_index", Settings.Category.System),
+    RENDERER_BACKEND("backend", Settings.Category.Renderer),
+    RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0),
+    RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer),
+    RENDERER_VSYNC("use_vsync", Settings.Category.Renderer),
+    RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer),
+    RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer),
+    RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android),
+    RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer),
+    AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio);
 
-    override var int: Int = defaultValue
+    override val int: Int
+        get() = NativeConfig.getInt(key, false)
+
+    override fun setInt(value: Int) = NativeConfig.setInt(key, value)
+
+    override val defaultValue: Int by lazy {
+        androidDefault ?: NativeConfig.getInt(key, true)
+    }
 
     override val valueAsString: String
         get() = int.toString()
 
-    override val isRuntimeEditable: Boolean
-        get() {
-            for (setting in NOT_RUNTIME_EDITABLE) {
-                if (setting == this) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    companion object {
-        private val NOT_RUNTIME_EDITABLE = listOf(
-            RENDERER_USE_DISK_SHADER_CACHE,
-            RENDERER_ASYNCHRONOUS_SHADERS,
-            RENDERER_DEBUG,
-            RENDERER_BACKEND,
-            RENDERER_RESOLUTION,
-            RENDERER_VSYNC
-        )
-
-        fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }
-
-        fun clear() = IntSetting.values().forEach { it.int = it.defaultValue }
-    }
+    override fun reset() = NativeConfig.setInt(key, defaultValue)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
new file mode 100644
index 0000000000..c526fc4cfa
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class LongSetting(
+    override val key: String,
+    override val category: Settings.Category
+) : AbstractLongSetting {
+    CUSTOM_RTC("custom_rtc", Settings.Category.System);
+
+    override val long: Long
+        get() = NativeConfig.getLong(key, false)
+
+    override fun setLong(value: Long) = NativeConfig.setLong(key, value)
+
+    override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) }
+
+    override val valueAsString: String
+        get() = long.toString()
+
+    override fun reset() = NativeConfig.setLong(key, defaultValue)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
deleted file mode 100644
index 474f598a91..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.model
-
-/**
- * A semantically-related group of Settings objects. These Settings are
- * internally stored as a HashMap.
- */
-class SettingSection(val name: String) {
-    val settings = HashMap<String, AbstractSetting>()
-
-    /**
-     * Convenience method; inserts a value directly into the backing HashMap.
-     *
-     * @param setting The Setting to be inserted.
-     */
-    fun putSetting(setting: AbstractSetting) {
-        settings[setting.key!!] = setting
-    }
-
-    /**
-     * Convenience method; gets a value directly from the backing HashMap.
-     *
-     * @param key Used to retrieve the Setting.
-     * @return A Setting object (you should probably cast this before using)
-     */
-    fun getSetting(key: String): AbstractSetting? {
-        return settings[key]
-    }
-
-    fun mergeSection(settingSection: SettingSection) {
-        for (setting in settingSection.settings.values) {
-            putSetting(setting)
-        }
-    }
-}
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 a6251bafdd..0702236e81 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
@@ -4,195 +4,151 @@
 package org.yuzu.yuzu_emu.features.settings.model
 
 import android.text.TextUtils
-import java.util.*
+import android.widget.Toast
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 
-class Settings {
-    private var gameId: String? = null
+object Settings {
+    private val context get() = YuzuApplication.appContext
 
-    var isLoaded = false
-
-    /**
-     * A HashMap<String></String>, SettingSection> that constructs a new SettingSection instead of returning null
-     * when getting a key not already in the map
-     */
-    class SettingsSectionMap : HashMap<String, SettingSection?>() {
-        override operator fun get(key: String): SettingSection? {
-            if (!super.containsKey(key)) {
-                val section = SettingSection(key)
-                super.put(key, section)
-                return section
-            }
-            return super.get(key)
-        }
-    }
-
-    var sections: HashMap<String, SettingSection?> = SettingsSectionMap()
-
-    fun getSection(sectionName: String): SettingSection? {
-        return sections[sectionName]
-    }
-
-    val isEmpty: Boolean
-        get() = sections.isEmpty()
-
-    fun loadSettings(view: SettingsActivityView? = null) {
-        sections = SettingsSectionMap()
-        loadYuzuSettings(view)
-        if (!TextUtils.isEmpty(gameId)) {
-            loadCustomGameSettings(gameId!!, view)
-        }
-        isLoaded = true
-    }
-
-    private fun loadYuzuSettings(view: SettingsActivityView?) {
-        for ((fileName) in configFileSectionsMap) {
-            sections.putAll(SettingsFile.readFile(fileName, view))
-        }
-    }
-
-    private fun loadCustomGameSettings(gameId: String, view: SettingsActivityView?) {
-        // Custom game settings
-        mergeSections(SettingsFile.readCustomGameSettings(gameId, view))
-    }
-
-    private fun mergeSections(updatedSections: HashMap<String, SettingSection?>) {
-        for ((key, updatedSection) in updatedSections) {
-            if (sections.containsKey(key)) {
-                val originalSection = sections[key]
-                originalSection!!.mergeSection(updatedSection!!)
-            } else {
-                sections[key] = updatedSection
-            }
-        }
-    }
-
-    fun loadSettings(gameId: String, view: SettingsActivityView) {
-        this.gameId = gameId
-        loadSettings(view)
-    }
-
-    fun saveSettings(view: SettingsActivityView) {
+    fun saveSettings(gameId: String = "") {
         if (TextUtils.isEmpty(gameId)) {
-            view.showToastMessage(
-                YuzuApplication.appContext.getString(R.string.ini_saved),
-                false
-            )
-
-            for ((fileName, sectionNames) in configFileSectionsMap) {
-                val iniSections = TreeMap<String, SettingSection>()
-                for (section in sectionNames) {
-                    iniSections[section] = sections[section]!!
-                }
-
-                SettingsFile.saveFile(fileName, iniSections, view)
-            }
+            Toast.makeText(
+                context,
+                context.getString(R.string.ini_saved),
+                Toast.LENGTH_SHORT
+            ).show()
+            SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG)
         } else {
-            // Custom game settings
-            view.showToastMessage(
-                YuzuApplication.appContext.getString(R.string.gameid_saved, gameId),
-                false
-            )
-
-            SettingsFile.saveCustomGameSettings(gameId, sections)
+            // TODO: Save custom game settings
+            Toast.makeText(
+                context,
+                context.getString(R.string.gameid_saved, gameId),
+                Toast.LENGTH_SHORT
+            ).show()
         }
     }
 
-    companion object {
-        const val SECTION_GENERAL = "General"
-        const val SECTION_SYSTEM = "System"
-        const val SECTION_RENDERER = "Renderer"
-        const val SECTION_AUDIO = "Audio"
-        const val SECTION_CPU = "Cpu"
-        const val SECTION_THEME = "Theme"
-        const val SECTION_DEBUG = "Debug"
-
-        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
-        )
-
-        const val PREF_CONTROL_SCALE = "controlScale"
-        const val PREF_CONTROL_OPACITY = "controlOpacity"
-        const val PREF_TOUCH_ENABLED = "isTouchEnabled"
-        const val PREF_BUTTON_A = "buttonToggle0"
-        const val PREF_BUTTON_B = "buttonToggle1"
-        const val PREF_BUTTON_X = "buttonToggle2"
-        const val PREF_BUTTON_Y = "buttonToggle3"
-        const val PREF_BUTTON_L = "buttonToggle4"
-        const val PREF_BUTTON_R = "buttonToggle5"
-        const val PREF_BUTTON_ZL = "buttonToggle6"
-        const val PREF_BUTTON_ZR = "buttonToggle7"
-        const val PREF_BUTTON_PLUS = "buttonToggle8"
-        const val PREF_BUTTON_MINUS = "buttonToggle9"
-        const val PREF_BUTTON_DPAD = "buttonToggle10"
-        const val PREF_STICK_L = "buttonToggle11"
-        const val PREF_STICK_R = "buttonToggle12"
-        const val PREF_BUTTON_STICK_L = "buttonToggle13"
-        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"
-
-        private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
-
-        val overlayPreferences = listOf(
-            PREF_OVERLAY_VERSION,
-            PREF_CONTROL_SCALE,
-            PREF_CONTROL_OPACITY,
-            PREF_TOUCH_ENABLED,
-            PREF_BUTTON_A,
-            PREF_BUTTON_B,
-            PREF_BUTTON_X,
-            PREF_BUTTON_Y,
-            PREF_BUTTON_L,
-            PREF_BUTTON_R,
-            PREF_BUTTON_ZL,
-            PREF_BUTTON_ZR,
-            PREF_BUTTON_PLUS,
-            PREF_BUTTON_MINUS,
-            PREF_BUTTON_DPAD,
-            PREF_STICK_L,
-            PREF_STICK_R,
-            PREF_BUTTON_HOME,
-            PREF_BUTTON_SCREENSHOT,
-            PREF_BUTTON_STICK_L,
-            PREF_BUTTON_STICK_R
-        )
-
-        const val LayoutOption_Unspecified = 0
-        const val LayoutOption_MobilePortrait = 4
-        const val LayoutOption_MobileLandscape = 5
-
-        init {
-            configFileSectionsMap[SettingsFile.FILE_NAME_CONFIG] =
-                listOf(
-                    SECTION_GENERAL,
-                    SECTION_SYSTEM,
-                    SECTION_RENDERER,
-                    SECTION_AUDIO,
-                    SECTION_CPU
-                )
-        }
+    enum class Category {
+        Android,
+        Audio,
+        Core,
+        Cpu,
+        CpuDebug,
+        CpuUnsafe,
+        Renderer,
+        RendererAdvanced,
+        RendererDebug,
+        System,
+        SystemAudio,
+        DataStorage,
+        Debugging,
+        DebuggingGraphics,
+        Miscellaneous,
+        Network,
+        WebService,
+        AddOns,
+        Controls,
+        Ui,
+        UiGeneral,
+        UiLayout,
+        UiGameList,
+        Screenshots,
+        Shortcuts,
+        Multiplayer,
+        Services,
+        Paths,
+        MaxEnum
     }
+
+    val settingsList = listOf<AbstractSetting>(
+        *BooleanSetting.values(),
+        *ByteSetting.values(),
+        *ShortSetting.values(),
+        *IntSetting.values(),
+        *FloatSetting.values(),
+        *LongSetting.values(),
+        *StringSetting.values()
+    )
+
+    const val SECTION_GENERAL = "General"
+    const val SECTION_SYSTEM = "System"
+    const val SECTION_RENDERER = "Renderer"
+    const val SECTION_AUDIO = "Audio"
+    const val SECTION_CPU = "Cpu"
+    const val SECTION_THEME = "Theme"
+    const val SECTION_DEBUG = "Debug"
+
+    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
+    )
+
+    const val PREF_CONTROL_SCALE = "controlScale"
+    const val PREF_CONTROL_OPACITY = "controlOpacity"
+    const val PREF_TOUCH_ENABLED = "isTouchEnabled"
+    const val PREF_BUTTON_A = "buttonToggle0"
+    const val PREF_BUTTON_B = "buttonToggle1"
+    const val PREF_BUTTON_X = "buttonToggle2"
+    const val PREF_BUTTON_Y = "buttonToggle3"
+    const val PREF_BUTTON_L = "buttonToggle4"
+    const val PREF_BUTTON_R = "buttonToggle5"
+    const val PREF_BUTTON_ZL = "buttonToggle6"
+    const val PREF_BUTTON_ZR = "buttonToggle7"
+    const val PREF_BUTTON_PLUS = "buttonToggle8"
+    const val PREF_BUTTON_MINUS = "buttonToggle9"
+    const val PREF_BUTTON_DPAD = "buttonToggle10"
+    const val PREF_STICK_L = "buttonToggle11"
+    const val PREF_STICK_R = "buttonToggle12"
+    const val PREF_BUTTON_STICK_L = "buttonToggle13"
+    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,
+        PREF_BUTTON_Y,
+        PREF_BUTTON_L,
+        PREF_BUTTON_R,
+        PREF_BUTTON_ZL,
+        PREF_BUTTON_ZR,
+        PREF_BUTTON_PLUS,
+        PREF_BUTTON_MINUS,
+        PREF_BUTTON_DPAD,
+        PREF_STICK_L,
+        PREF_STICK_R,
+        PREF_BUTTON_HOME,
+        PREF_BUTTON_SCREENSHOT,
+        PREF_BUTTON_STICK_L,
+        PREF_BUTTON_STICK_R
+    )
+
+    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/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
new file mode 100644
index 0000000000..c9a0c664cb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.features.settings.model
+
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+enum class ShortSetting(
+    override val key: String,
+    override val category: Settings.Category
+) : AbstractShortSetting {
+    RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core);
+
+    override val short: Short
+        get() = NativeConfig.getShort(key, false)
+
+    override fun setShort(value: Short) = NativeConfig.setShort(key, value)
+
+    override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) }
+
+    override val valueAsString: String
+        get() = short.toString()
+
+    override fun reset() = NativeConfig.setShort(key, defaultValue)
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
index 6621289fd5..9bb3e66d4f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt
@@ -3,36 +3,24 @@
 
 package org.yuzu.yuzu_emu.features.settings.model
 
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
 enum class StringSetting(
     override val key: String,
-    override val section: String,
-    override val defaultValue: String
+    override val category: Settings.Category
 ) : AbstractStringSetting {
-    AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
-    CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
+    // No string settings currently exist
+    EMPTY_SETTING("", Settings.Category.UiGeneral);
 
-    override var string: String = defaultValue
+    override val string: String
+        get() = NativeConfig.getString(key, false)
+
+    override fun setString(value: String) = NativeConfig.setString(key, value)
+
+    override val defaultValue: String by lazy { NativeConfig.getString(key, true) }
 
     override val valueAsString: String
         get() = string
 
-    override val isRuntimeEditable: Boolean
-        get() {
-            for (setting in NOT_RUNTIME_EDITABLE) {
-                if (setting == this) {
-                    return false
-                }
-            }
-            return true
-        }
-
-    companion object {
-        private val NOT_RUNTIME_EDITABLE = listOf(
-            CUSTOM_RTC
-        )
-
-        fun from(key: String): StringSetting? = StringSetting.values().firstOrNull { it.key == key }
-
-        fun clear() = StringSetting.values().forEach { it.string = it.defaultValue }
-    }
+    override fun reset() = NativeConfig.setString(key, defaultValue)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
index bc0bf77889..8bc1641978 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt
@@ -3,29 +3,16 @@
 
 package org.yuzu.yuzu_emu.features.settings.model.view
 
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
 
 class DateTimeSetting(
-    setting: AbstractSetting?,
+    private val longSetting: AbstractLongSetting,
     titleId: Int,
-    descriptionId: Int,
-    val key: String? = null,
-    private val defaultValue: String? = null
-) : SettingsItem(setting, titleId, descriptionId) {
+    descriptionId: Int
+) : SettingsItem(longSetting, titleId, descriptionId) {
     override val type = TYPE_DATETIME_SETTING
 
-    val value: String
-        get() = if (setting != null) {
-            val setting = setting as AbstractStringSetting
-            setting.string
-        } else {
-            defaultValue!!
-        }
-
-    fun setSelectedValue(datetime: String): AbstractStringSetting {
-        val stringSetting = setting as AbstractStringSetting
-        stringSetting.string = datetime
-        return stringSetting
-    }
+    var value: Long
+        get() = longSetting.long
+        set(value) = (setting as AbstractLongSetting).setLong(value)
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
index a670013111..d31ce1c312 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt
@@ -5,6 +5,6 @@ package org.yuzu.yuzu_emu.features.settings.model.view
 
 class HeaderSetting(
     titleId: Int
-) : SettingsItem(null, titleId, 0) {
+) : SettingsItem(emptySetting, titleId, 0) {
     override val type = TYPE_HEADER
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
index caaab50d81..522cc49df1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/RunnableSetting.kt
@@ -8,6 +8,6 @@ class RunnableSetting(
     descriptionId: Int,
     val isRuntimeRunnable: Boolean,
     val runnable: () -> Unit
-) : SettingsItem(null, titleId, descriptionId) {
+) : SettingsItem(emptySetting, titleId, descriptionId) {
     override val type = TYPE_RUNNABLE
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
index 07520849ec..b3b3fc2099 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
@@ -4,7 +4,15 @@
 package org.yuzu.yuzu_emu.features.settings.model.view
 
 import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
+import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
+import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.LongSetting
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 
 /**
  * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -14,7 +22,7 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
  * file.)
  */
 abstract class SettingsItem(
-    var setting: AbstractSetting?,
+    val setting: AbstractSetting,
     val nameId: Int,
     val descriptionId: Int
 ) {
@@ -23,7 +31,7 @@ abstract class SettingsItem(
     val isEditable: Boolean
         get() {
             if (!NativeLibrary.isRunning()) return true
-            return setting?.isRuntimeEditable ?: false
+            return setting.isRuntimeModifiable
         }
 
     companion object {
@@ -35,5 +43,240 @@ abstract class SettingsItem(
         const val TYPE_STRING_SINGLE_CHOICE = 5
         const val TYPE_DATETIME_SETTING = 6
         const val TYPE_RUNNABLE = 7
+
+        const val FASTMEM_COMBINED = "fastmem_combined"
+
+        val emptySetting = object : AbstractSetting {
+            override val key: String = ""
+            override val category: Settings.Category = Settings.Category.Ui
+            override val defaultValue: Any = false
+            override fun reset() {}
+        }
+
+        // Extension for putting SettingsItems into a hashmap without repeating yourself
+        fun HashMap<String, SettingsItem>.put(item: SettingsItem) {
+            put(item.setting.key, item)
+        }
+
+        // List of all general
+        val settingsItems = HashMap<String, SettingsItem>().apply {
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_USE_SPEED_LIMIT,
+                    R.string.frame_limit_enable,
+                    R.string.frame_limit_enable_description
+                )
+            )
+            put(
+                SliderSetting(
+                    ShortSetting.RENDERER_SPEED_LIMIT,
+                    R.string.frame_limit_slider,
+                    R.string.frame_limit_slider_description,
+                    1,
+                    200,
+                    "%"
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.CPU_ACCURACY,
+                    R.string.cpu_accuracy,
+                    0,
+                    R.array.cpuAccuracyNames,
+                    R.array.cpuAccuracyValues
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.PICTURE_IN_PICTURE,
+                    R.string.picture_in_picture,
+                    R.string.picture_in_picture_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.USE_DOCKED_MODE,
+                    R.string.use_docked_mode,
+                    R.string.use_docked_mode_description
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.REGION_INDEX,
+                    R.string.emulated_region,
+                    0,
+                    R.array.regionNames,
+                    R.array.regionValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.LANGUAGE_INDEX,
+                    R.string.emulated_language,
+                    0,
+                    R.array.languageNames,
+                    R.array.languageValues
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.USE_CUSTOM_RTC,
+                    R.string.use_custom_rtc,
+                    R.string.use_custom_rtc_description
+                )
+            )
+            put(DateTimeSetting(LongSetting.CUSTOM_RTC, R.string.set_custom_rtc, 0))
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_ACCURACY,
+                    R.string.renderer_accuracy,
+                    0,
+                    R.array.rendererAccuracyNames,
+                    R.array.rendererAccuracyValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_RESOLUTION,
+                    R.string.renderer_resolution,
+                    0,
+                    R.array.rendererResolutionNames,
+                    R.array.rendererResolutionValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_VSYNC,
+                    R.string.renderer_vsync,
+                    0,
+                    R.array.rendererVSyncNames,
+                    R.array.rendererVSyncValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_SCALING_FILTER,
+                    R.string.renderer_scaling_filter,
+                    0,
+                    R.array.rendererScalingFilterNames,
+                    R.array.rendererScalingFilterValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_ANTI_ALIASING,
+                    R.string.renderer_anti_aliasing,
+                    0,
+                    R.array.rendererAntiAliasingNames,
+                    R.array.rendererAntiAliasingValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_SCREEN_LAYOUT,
+                    R.string.renderer_screen_layout,
+                    0,
+                    R.array.rendererScreenLayoutNames,
+                    R.array.rendererScreenLayoutValues
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_ASPECT_RATIO,
+                    R.string.renderer_aspect_ratio,
+                    0,
+                    R.array.rendererAspectRatioNames,
+                    R.array.rendererAspectRatioValues
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE,
+                    R.string.use_disk_shader_cache,
+                    R.string.use_disk_shader_cache_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK,
+                    R.string.renderer_force_max_clock,
+                    R.string.renderer_force_max_clock_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
+                    R.string.renderer_asynchronous_shaders,
+                    R.string.renderer_asynchronous_shaders_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_REACTIVE_FLUSHING,
+                    R.string.renderer_reactive_flushing,
+                    R.string.renderer_reactive_flushing_description
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.AUDIO_OUTPUT_ENGINE,
+                    R.string.audio_output_engine,
+                    0,
+                    R.array.outputEngineEntries,
+                    R.array.outputEngineValues
+                )
+            )
+            put(
+                SliderSetting(
+                    ByteSetting.AUDIO_VOLUME,
+                    R.string.audio_volume,
+                    R.string.audio_volume_description,
+                    0,
+                    100,
+                    "%"
+                )
+            )
+            put(
+                SingleChoiceSetting(
+                    IntSetting.RENDERER_BACKEND,
+                    R.string.renderer_api,
+                    0,
+                    R.array.rendererApiNames,
+                    R.array.rendererApiValues
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.RENDERER_DEBUG,
+                    R.string.renderer_debug,
+                    R.string.renderer_debug_description
+                )
+            )
+            put(
+                SwitchSetting(
+                    BooleanSetting.CPU_DEBUG_MODE,
+                    R.string.cpu_debug_mode,
+                    R.string.cpu_debug_mode_description
+                )
+            )
+
+            val fastmem = object : AbstractBooleanSetting {
+                override val boolean: Boolean
+                    get() =
+                        BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
+
+                override fun setBoolean(value: Boolean) {
+                    BooleanSetting.FASTMEM.setBoolean(value)
+                    BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
+                }
+
+                override val key: String = FASTMEM_COMBINED
+                override val category = Settings.Category.Cpu
+                override val isRuntimeModifiable: Boolean = false
+                override val defaultValue: Boolean = true
+                override fun reset() = setBoolean(defaultValue)
+            }
+            put(SwitchSetting(fastmem, R.string.fastmem, 0))
+        }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
index 7306ec458e..705527a733 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt
@@ -4,36 +4,27 @@
 package org.yuzu.yuzu_emu.features.settings.model.view
 
 import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
+import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 
 class SingleChoiceSetting(
-    setting: AbstractIntSetting?,
+    setting: AbstractSetting,
     titleId: Int,
     descriptionId: Int,
     val choicesId: Int,
-    val valuesId: Int,
-    val key: String? = null,
-    val defaultValue: Int? = null
+    val valuesId: Int
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_SINGLE_CHOICE
 
-    val selectedValue: Int
-        get() = if (setting != null) {
-            val setting = setting as AbstractIntSetting
-            setting.int
-        } else {
-            defaultValue!!
+    var selectedValue: Int
+        get() {
+            return when (setting) {
+                is AbstractIntSetting -> setting.int
+                else -> -1
+            }
+        }
+        set(value) {
+            when (setting) {
+                is AbstractIntSetting -> setting.setInt(value)
+            }
         }
-
-    /**
-     * Write a value to the backing int. If that int was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param selection New value of the int.
-     * @return the existing setting with the new value applied.
-     */
-    fun setSelectedValue(selection: Int): AbstractIntSetting {
-        val intSetting = setting as AbstractIntSetting
-        intSetting.int = selection
-        return intSetting
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
index 92d0167ae0..c3b5df02c9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt
@@ -3,60 +3,39 @@
 
 package org.yuzu.yuzu_emu.features.settings.model.view
 
-import kotlin.math.roundToInt
+import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.utils.Log
+import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting
+import kotlin.math.roundToInt
 
 class SliderSetting(
-    setting: AbstractSetting?,
+    setting: AbstractSetting,
     titleId: Int,
     descriptionId: Int,
     val min: Int,
     val max: Int,
-    val units: String,
-    val key: String? = null,
-    val defaultValue: Int? = null
+    val units: String
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_SLIDER
 
-    val selectedValue: Int
+    var selectedValue: Int
         get() {
-            val setting = setting ?: return defaultValue!!
             return when (setting) {
+                is AbstractByteSetting -> setting.byte.toInt()
+                is AbstractShortSetting -> setting.short.toInt()
                 is AbstractIntSetting -> setting.int
                 is AbstractFloatSetting -> setting.float.roundToInt()
-                else -> {
-                    Log.error("[SliderSetting] Error casting setting type.")
-                    -1
-                }
+                else -> -1
+            }
+        }
+        set(value) {
+            when (setting) {
+                is AbstractByteSetting -> setting.setByte(value.toByte())
+                is AbstractShortSetting -> setting.setShort(value.toShort())
+                is AbstractIntSetting -> setting.setInt(value)
+                is AbstractFloatSetting -> setting.setFloat(value.toFloat())
             }
         }
-
-    /**
-     * Write a value to the backing int. If that int was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param selection New value of the int.
-     * @return the existing setting with the new value applied.
-     */
-    fun setSelectedValue(selection: Int): AbstractIntSetting {
-        val intSetting = setting as AbstractIntSetting
-        intSetting.int = selection
-        return intSetting
-    }
-
-    /**
-     * Write a value to the backing float. If that float was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param selection New value of the float.
-     * @return the existing setting with the new value applied.
-     */
-    fun setSelectedValue(selection: Float): AbstractFloatSetting {
-        val floatSetting = setting as AbstractFloatSetting
-        floatSetting.float = selection
-        return floatSetting
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
index 3b6731dcd5..871dab4f3b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt
@@ -3,57 +3,31 @@
 
 package org.yuzu.yuzu_emu.features.settings.model.view
 
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
 
 class StringSingleChoiceSetting(
-    setting: AbstractSetting?,
+    private val stringSetting: AbstractStringSetting,
     titleId: Int,
     descriptionId: Int,
     val choices: Array<String>,
-    val values: Array<String>?,
-    val key: String? = null,
-    private val defaultValue: String? = null
-) : SettingsItem(setting, titleId, descriptionId) {
+    val values: Array<String>
+) : SettingsItem(stringSetting, titleId, descriptionId) {
     override val type = TYPE_STRING_SINGLE_CHOICE
 
-    fun getValueAt(index: Int): String? {
-        if (values == null) return null
-        return if (index >= 0 && index < values.size) {
-            values[index]
-        } else {
-            ""
-        }
-    }
+    fun getValueAt(index: Int): String =
+        if (index >= 0 && index < values.size) values[index] else ""
+
+    var selectedValue: String
+        get() = stringSetting.string
+        set(value) = stringSetting.setString(value)
 
-    val selectedValue: String
-        get() = if (setting != null) {
-            val setting = setting as AbstractStringSetting
-            setting.string
-        } else {
-            defaultValue!!
-        }
     val selectValueIndex: Int
         get() {
-            val selectedValue = selectedValue
-            for (i in values!!.indices) {
+            for (i in values.indices) {
                 if (values[i] == selectedValue) {
                     return i
                 }
             }
             return -1
         }
-
-    /**
-     * Write a value to the backing int. If that int was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param selection New value of the int.
-     * @return the existing setting with the new value applied.
-     */
-    fun setSelectedValue(selection: String): AbstractStringSetting {
-        val stringSetting = setting as AbstractStringSetting
-        stringSetting.string = selection
-        return stringSetting
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index 8a9d13a92e..91c273964a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -7,6 +7,6 @@ class SubmenuSetting(
     titleId: Int,
     descriptionId: Int,
     val menuKey: String
-) : SettingsItem(null, titleId, descriptionId) {
+) : SettingsItem(emptySetting, titleId, descriptionId) {
     override val type = TYPE_SUBMENU
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
index 90b198718a..416967e645 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt
@@ -10,53 +10,22 @@ import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 class SwitchSetting(
     setting: AbstractSetting,
     titleId: Int,
-    descriptionId: Int,
-    val key: String? = null,
-    val defaultValue: Any? = null
+    descriptionId: Int
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_SWITCH
 
-    val isChecked: Boolean
+    var checked: Boolean
         get() {
-            if (setting == null) {
-                return defaultValue as Boolean
+            return when (setting) {
+                is AbstractIntSetting -> setting.int == 1
+                is AbstractBooleanSetting -> setting.boolean
+                else -> false
             }
-
-            // Try integer setting
-            try {
-                val setting = setting as AbstractIntSetting
-                return setting.int == 1
-            } catch (_: ClassCastException) {
-            }
-
-            // Try boolean setting
-            try {
-                val setting = setting as AbstractBooleanSetting
-                return setting.boolean
-            } catch (_: ClassCastException) {
-            }
-            return defaultValue as Boolean
         }
-
-    /**
-     * Write a value to the backing boolean. If that boolean was previously null,
-     * initializes a new one and returns it, so it can be added to the Hashmap.
-     *
-     * @param checked Pretty self explanatory.
-     * @return the existing setting with the new value applied.
-     */
-    fun setChecked(checked: Boolean): AbstractSetting {
-        // Try integer setting
-        try {
-            val setting = setting as AbstractIntSetting
-            setting.int = if (checked) 1 else 0
-            return setting
-        } catch (_: ClassCastException) {
+        set(value) {
+            when (setting) {
+                is AbstractIntSetting -> setting.setInt(if (value) 1 else 0)
+                is AbstractBooleanSetting -> setting.setBoolean(value)
+            }
         }
-
-        // Try boolean setting
-        val setting = setting as AbstractBooleanSetting
-        setting.boolean = checked
-        return setting
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index e6fffc8328..908c01265c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -3,10 +3,7 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
-import android.content.Context
-import android.content.Intent
 import android.os.Bundle
-import android.view.Menu
 import android.view.View
 import android.view.ViewGroup.MarginLayoutParams
 import android.widget.Toast
@@ -16,28 +13,24 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.updatePadding
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.navArgs
 import com.google.android.material.color.MaterialColors
 import java.io.IOException
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
-import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
-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.model.SettingsViewModel
-import org.yuzu.yuzu_emu.features.settings.model.StringSetting
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
+import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
+import org.yuzu.yuzu_emu.model.SettingsViewModel
 import org.yuzu.yuzu_emu.utils.*
 
-class SettingsActivity : AppCompatActivity(), SettingsActivityView {
-    private val presenter = SettingsActivityPresenter(this)
-
+class SettingsActivity : AppCompatActivity() {
     private lateinit var binding: ActivitySettingsBinding
 
-    private val settingsViewModel: SettingsViewModel by viewModels()
+    private val args by navArgs<SettingsActivityArgs>()
 
-    override val settings: Settings get() = settingsViewModel.settings
+    private val settingsViewModel: SettingsViewModel by viewModels()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         ThemeHelper.setTheme(this)
@@ -47,16 +40,17 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         binding = ActivitySettingsBinding.inflate(layoutInflater)
         setContentView(binding.root)
 
+        settingsViewModel.game = args.game
+
+        val navHostFragment =
+            supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
+        navHostFragment.navController.setGraph(R.navigation.settings_navigation, intent.extras)
+
         WindowCompat.setDecorFitsSystemWindows(window, false)
 
-        val launcher = intent
-        val gameID = launcher.getStringExtra(ARG_GAME_ID)
-        val menuTag = launcher.getStringExtra(ARG_MENU_TAG)
-        presenter.onCreate(savedInstanceState, menuTag!!, gameID!!)
-
-        // Show "Back" button in the action bar for navigation
-        setSupportActionBar(binding.toolbarSettings)
-        supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+        if (savedInstanceState != null) {
+            settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
+        }
 
         if (InsetsHelper.getSystemGestureType(applicationContext) !=
             InsetsHelper.GESTURE_NAVIGATION
@@ -72,6 +66,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
             )
         }
 
+        settingsViewModel.shouldRecreate.observe(this) {
+            if (it) {
+                settingsViewModel.setShouldRecreate(false)
+                recreate()
+            }
+        }
+        settingsViewModel.shouldNavigateBack.observe(this) {
+            if (it) {
+                settingsViewModel.setShouldNavigateBack(false)
+                navigateBack()
+            }
+        }
+        settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
+            if (it) {
+                settingsViewModel.setShouldShowResetSettingsDialog(false)
+                ResetSettingsDialogFragment().show(
+                    supportFragmentManager,
+                    ResetSettingsDialogFragment.TAG
+                )
+            }
+        }
+
         onBackPressedDispatcher.addCallback(
             this,
             object : OnBackPressedCallback(true) {
@@ -82,34 +98,28 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         setInsets()
     }
 
-    override fun onSupportNavigateUp(): Boolean {
-        navigateBack()
-        return true
-    }
-
-    private fun navigateBack() {
-        if (supportFragmentManager.backStackEntryCount > 0) {
-            supportFragmentManager.popBackStack()
+    fun navigateBack() {
+        val navHostFragment =
+            supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
+        if (navHostFragment.childFragmentManager.backStackEntryCount > 0) {
+            navHostFragment.navController.popBackStack()
         } else {
             finish()
         }
     }
 
-    override fun onCreateOptionsMenu(menu: Menu): Boolean {
-        val inflater = menuInflater
-        inflater.inflate(R.menu.menu_settings, menu)
-        return true
-    }
-
     override fun onSaveInstanceState(outState: Bundle) {
         // Critical: If super method is not called, rotations will be busted.
         super.onSaveInstanceState(outState)
-        presenter.saveState(outState)
+        outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
     }
 
     override fun onStart() {
         super.onStart()
-        presenter.onStart()
+        // TODO: Load custom settings contextually
+        if (!DirectoryInitialization.areDirectoriesReady) {
+            DirectoryInitialization.start()
+        }
     }
 
     /**
@@ -119,131 +129,51 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
      */
     override fun onStop() {
         super.onStop()
-        presenter.onStop(isFinishing)
-    }
-
-    override fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String) {
-        if (!addToStack && settingsFragment != null) {
-            return
+        if (isFinishing && settingsViewModel.shouldSave) {
+            Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
+            Settings.saveSettings()
         }
-
-        val transaction = supportFragmentManager.beginTransaction()
-        if (addToStack) {
-            if (areSystemAnimationsEnabled()) {
-                transaction.setCustomAnimations(
-                    R.anim.anim_settings_fragment_in,
-                    R.anim.anim_settings_fragment_out,
-                    0,
-                    R.anim.anim_pop_settings_fragment_out
-                )
-            }
-            transaction.addToBackStack(null)
-        }
-        transaction.replace(
-            R.id.frame_content,
-            SettingsFragment.newInstance(menuTag, gameId),
-            FRAGMENT_TAG
-        )
-        transaction.commit()
     }
 
-    private fun areSystemAnimationsEnabled(): Boolean {
-        val duration = android.provider.Settings.Global.getFloat(
-            contentResolver,
-            android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
-            1f
-        )
-        val transition = android.provider.Settings.Global.getFloat(
-            contentResolver,
-            android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
-            1f
-        )
-        return duration != 0f && transition != 0f
-    }
-
-    override fun onSettingsFileLoaded() {
-        val fragment: SettingsFragmentView? = settingsFragment
-        fragment?.loadSettingsList()
-    }
-
-    override fun onSettingsFileNotFound() {
-        val fragment: SettingsFragmentView? = settingsFragment
-        fragment?.loadSettingsList()
-    }
-
-    override fun showToastMessage(message: String, is_long: Boolean) {
-        Toast.makeText(
-            this,
-            message,
-            if (is_long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
-        ).show()
-    }
-
-    override fun onSettingChanged() {
-        presenter.onSettingChanged()
+    override fun onDestroy() {
+        settingsViewModel.clear()
+        super.onDestroy()
     }
 
     fun onSettingsReset() {
         // Prevents saving to a non-existent settings file
-        presenter.onSettingsReset()
-
-        // Reset the static memory representation of each setting
-        BooleanSetting.clear()
-        FloatSetting.clear()
-        IntSetting.clear()
-        StringSetting.clear()
+        settingsViewModel.shouldSave = false
 
         // Delete settings file because the user may have changed values that do not exist in the UI
         val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
         if (!settingsFile.delete()) {
             throw IOException("Failed to delete $settingsFile")
         }
+        Settings.settingsList.forEach { it.reset() }
 
-        showToastMessage(getString(R.string.settings_reset), true)
+        Toast.makeText(
+            applicationContext,
+            getString(R.string.settings_reset),
+            Toast.LENGTH_LONG
+        ).show()
         finish()
     }
 
-    fun setToolbarTitle(title: String) {
-        binding.toolbarSettingsLayout.title = title
-    }
-
-    private val settingsFragment: SettingsFragment?
-        get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
-
     private fun setInsets() {
         ViewCompat.setOnApplyWindowInsetsListener(
-            binding.frameContent
+            binding.navigationBarShade
         ) { view: View, windowInsets: WindowInsetsCompat ->
             val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
-            val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
-            view.updatePadding(
-                left = barInsets.left + cutoutInsets.left,
-                right = barInsets.right + cutoutInsets.right
-            )
 
-            val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
-            mlpAppBar.leftMargin = barInsets.left + cutoutInsets.left
-            mlpAppBar.rightMargin = barInsets.right + cutoutInsets.right
-            binding.appbarSettings.layoutParams = mlpAppBar
-
-            val mlpShade = binding.navigationBarShade.layoutParams as MarginLayoutParams
+            val mlpShade = view.layoutParams as MarginLayoutParams
             mlpShade.height = barInsets.bottom
-            binding.navigationBarShade.layoutParams = mlpShade
+            view.layoutParams = mlpShade
 
             windowInsets
         }
     }
 
     companion object {
-        private const val ARG_MENU_TAG = "menu_tag"
-        private const val ARG_GAME_ID = "game_id"
-        private const val FRAGMENT_TAG = "settings"
-
-        fun launch(context: Context, menuTag: String?, gameId: String?) {
-            val settings = Intent(context, SettingsActivity::class.java)
-            settings.putExtra(ARG_MENU_TAG, menuTag)
-            settings.putExtra(ARG_GAME_ID, gameId)
-            context.startActivity(settings)
-        }
+        private const val KEY_SHOULD_SAVE = "should_save"
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
deleted file mode 100644
index 93e677b21b..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
+++ /dev/null
@@ -1,90 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import android.content.Context
-import android.os.Bundle
-import android.text.TextUtils
-import java.io.File
-import org.yuzu.yuzu_emu.NativeLibrary
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.utils.DirectoryInitialization
-import org.yuzu.yuzu_emu.utils.Log
-
-class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
-    val settings: Settings get() = activityView.settings
-
-    private var shouldSave = false
-    private lateinit var menuTag: String
-    private lateinit var gameId: String
-
-    fun onCreate(savedInstanceState: Bundle?, menuTag: String, gameId: String) {
-        this.menuTag = menuTag
-        this.gameId = gameId
-        if (savedInstanceState != null) {
-            shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
-        }
-    }
-
-    fun onStart() {
-        prepareDirectoriesIfNeeded()
-    }
-
-    private fun loadSettingsUI() {
-        if (!settings.isLoaded) {
-            if (!TextUtils.isEmpty(gameId)) {
-                settings.loadSettings(gameId, activityView)
-            } else {
-                settings.loadSettings(activityView)
-            }
-        }
-        activityView.showSettingsFragment(menuTag, false, gameId)
-        activityView.onSettingsFileLoaded()
-    }
-
-    private fun prepareDirectoriesIfNeeded() {
-        val configFile =
-            File(
-                "${DirectoryInitialization.userDirectory}/config/" +
-                    "${SettingsFile.FILE_NAME_CONFIG}.ini"
-            )
-        if (!configFile.exists()) {
-            Log.error(
-                "${DirectoryInitialization.userDirectory}/config/" +
-                    "${SettingsFile.FILE_NAME_CONFIG}.ini"
-            )
-            Log.error("yuzu config file could not be found!")
-        }
-
-        if (!DirectoryInitialization.areDirectoriesReady) {
-            DirectoryInitialization.start(activityView as Context)
-        }
-        loadSettingsUI()
-    }
-
-    fun onStop(finishing: Boolean) {
-        if (finishing && shouldSave) {
-            Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
-            settings.saveSettings(activityView)
-        }
-        NativeLibrary.reloadSettings()
-    }
-
-    fun onSettingChanged() {
-        shouldSave = true
-    }
-
-    fun onSettingsReset() {
-        shouldSave = false
-    }
-
-    fun saveState(outState: Bundle) {
-        outState.putBoolean(KEY_SHOULD_SAVE, shouldSave)
-    }
-
-    companion object {
-        private const val KEY_SHOULD_SAVE = "should_save"
-    }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
deleted file mode 100644
index c186fc388c..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityView.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-
-/**
- * Abstraction for the Activity that manages SettingsFragments.
- */
-interface SettingsActivityView {
-    /**
-     * Show a new SettingsFragment.
-     *
-     * @param menuTag    Identifier for the settings group that should be displayed.
-     * @param addToStack Whether or not this fragment should replace a previous one.
-     */
-    fun showSettingsFragment(menuTag: String, addToStack: Boolean, gameId: String)
-
-    /**
-     * Called by a contained Fragment to get access to the Setting HashMap
-     * loaded from disk, so that each Fragment doesn't need to perform its own
-     * read operation.
-     *
-     * @return A HashMap of Settings.
-     */
-    val settings: Settings
-
-    /**
-     * Called when a load operation completes.
-     */
-    fun onSettingsFileLoaded()
-
-    /**
-     * Called when a load operation fails.
-     */
-    fun onSettingsFileNotFound()
-
-    /**
-     * Display a popup text message on screen.
-     *
-     * @param message The contents of the onscreen message.
-     * @param is_long Whether this should be a long Toast or short one.
-     */
-    fun showToastMessage(message: String, is_long: Boolean)
-
-    /**
-     * End the activity.
-     */
-    fun finish()
-
-    /**
-     * Called by a containing Fragment to tell the Activity that a setting was changed;
-     * unless this has been called, the Activity will not save to disk.
-     */
-    fun onSettingChanged()
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
index 9711e2c51e..a7a029fc12 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt
@@ -4,51 +4,54 @@
 package org.yuzu.yuzu_emu.features.settings.ui
 
 import android.content.Context
-import android.content.DialogInterface
 import android.icu.util.Calendar
 import android.icu.util.TimeZone
 import android.text.format.DateFormat
 import android.view.LayoutInflater
 import android.view.ViewGroup
-import android.widget.TextView
-import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
-import androidx.recyclerview.widget.RecyclerView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.findNavController
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
 import com.google.android.material.datepicker.MaterialDatePicker
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.slider.Slider
 import com.google.android.material.timepicker.MaterialTimePicker
 import com.google.android.material.timepicker.TimeFormat
+import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.SettingsNavigationDirections
 import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
 import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
-import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
-import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.*
 import org.yuzu.yuzu_emu.features.settings.ui.viewholder.*
+import org.yuzu.yuzu_emu.fragments.SettingsDialogFragment
+import org.yuzu.yuzu_emu.model.SettingsViewModel
 
 class SettingsAdapter(
-    private val fragmentView: SettingsFragmentView,
+    private val fragment: Fragment,
     private val context: Context
-) : RecyclerView.Adapter<SettingViewHolder?>(), DialogInterface.OnClickListener {
-    private var settings: ArrayList<SettingsItem>? = null
-    private var clickedItem: SettingsItem? = null
-    private var clickedPosition: Int
-    private var dialog: AlertDialog? = null
-    private var sliderProgress = 0
-    private var textSliderValue: TextView? = null
-
-    private var defaultCancelListener =
-        DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
+) : ListAdapter<SettingsItem, SettingViewHolder>(
+    AsyncDifferConfig.Builder(DiffCallback()).build()
+) {
+    private val settingsViewModel: SettingsViewModel
+        get() = ViewModelProvider(fragment.requireActivity())[SettingsViewModel::class.java]
 
     init {
-        clickedPosition = -1
+        fragment.viewLifecycleOwner.lifecycleScope.launch {
+            fragment.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                settingsViewModel.adapterItemChanged.collect {
+                    if (it != -1) {
+                        notifyItemChanged(it)
+                        settingsViewModel.setAdapterItemChanged(-1)
+                    }
+                }
+            }
+        }
     }
 
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingViewHolder {
@@ -90,67 +93,41 @@ class SettingsAdapter(
     }
 
     override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
-        holder.bind(getItem(position))
+        holder.bind(currentList[position])
     }
 
-    private fun getItem(position: Int): SettingsItem {
-        return settings!![position]
-    }
-
-    override fun getItemCount(): Int {
-        return if (settings != null) {
-            settings!!.size
-        } else {
-            0
-        }
-    }
+    override fun getItemCount(): Int = currentList.size
 
     override fun getItemViewType(position: Int): Int {
-        return getItem(position).type
+        return currentList[position].type
     }
 
-    fun setSettingsList(settings: ArrayList<SettingsItem>?) {
-        this.settings = settings
-        notifyDataSetChanged()
-    }
-
-    fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
-        val setting = item.setChecked(checked)
-        fragmentView.putSetting(setting)
-        fragmentView.onSettingChanged()
-    }
-
-    private fun onSingleChoiceClick(item: SingleChoiceSetting) {
-        clickedItem = item
-        val value = getSelectionForSingleChoiceValue(item)
-        dialog = MaterialAlertDialogBuilder(context)
-            .setTitle(item.nameId)
-            .setSingleChoiceItems(item.choicesId, value, this)
-            .show()
+    fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
+        item.checked = checked
+        settingsViewModel.setShouldReloadSettingsList(true)
+        settingsViewModel.shouldSave = true
     }
 
     fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
-        clickedPosition = position
-        onSingleChoiceClick(item)
-    }
-
-    private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
-        clickedItem = item
-        dialog = MaterialAlertDialogBuilder(context)
-            .setTitle(item.nameId)
-            .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
-            .show()
+        SettingsDialogFragment.newInstance(
+            settingsViewModel,
+            item,
+            SettingsItem.TYPE_SINGLE_CHOICE,
+            position
+        ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
     }
 
     fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
-        clickedPosition = position
-        onStringSingleChoiceClick(item)
+        SettingsDialogFragment.newInstance(
+            settingsViewModel,
+            item,
+            SettingsItem.TYPE_STRING_SINGLE_CHOICE,
+            position
+        ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
     }
 
     fun onDateTimeClick(item: DateTimeSetting, position: Int) {
-        clickedItem = item
-        clickedPosition = position
-        val storedTime = java.lang.Long.decode(item.value) * 1000
+        val storedTime = item.value * 1000
 
         // Helper to extract hour and minute from epoch time
         val calendar: Calendar = Calendar.getInstance()
@@ -158,7 +135,7 @@ class SettingsAdapter(
         calendar.timeZone = TimeZone.getTimeZone("UTC")
 
         var timeFormat: Int = TimeFormat.CLOCK_12H
-        if (DateFormat.is24HourFormat(fragmentView.activityView as AppCompatActivity)) {
+        if (DateFormat.is24HourFormat(context)) {
             timeFormat = TimeFormat.CLOCK_24H
         }
 
@@ -175,7 +152,7 @@ class SettingsAdapter(
 
         datePicker.addOnPositiveButtonClickListener {
             timePicker.show(
-                (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
+                fragment.childFragmentManager,
                 "TimePicker"
             )
         }
@@ -183,160 +160,50 @@ class SettingsAdapter(
             var epochTime: Long = datePicker.selection!! / 1000
             epochTime += timePicker.hour.toLong() * 60 * 60
             epochTime += timePicker.minute.toLong() * 60
-            val rtcString = epochTime.toString()
-            if (item.value != rtcString) {
-                fragmentView.onSettingChanged()
+            if (item.value != epochTime) {
+                settingsViewModel.shouldSave = true
+                notifyItemChanged(position)
+                item.value = epochTime
             }
-            notifyItemChanged(clickedPosition)
-            val setting = item.setSelectedValue(rtcString)
-            fragmentView.putSetting(setting)
-            clickedItem = null
         }
         datePicker.show(
-            (fragmentView.activityView as AppCompatActivity).supportFragmentManager,
+            fragment.childFragmentManager,
             "DatePicker"
         )
     }
 
     fun onSliderClick(item: SliderSetting, position: Int) {
-        clickedItem = item
-        clickedPosition = position
-        sliderProgress = item.selectedValue
-
-        val inflater = LayoutInflater.from(context)
-        val sliderBinding = DialogSliderBinding.inflate(inflater)
-
-        textSliderValue = sliderBinding.textValue
-        textSliderValue!!.text = String.format(
-            context.getString(R.string.value_with_units),
-            sliderProgress.toString(),
-            item.units
-        )
-
-        sliderBinding.slider.apply {
-            valueFrom = item.min.toFloat()
-            valueTo = item.max.toFloat()
-            value = sliderProgress.toFloat()
-            addOnChangeListener { _: Slider, value: Float, _: Boolean ->
-                sliderProgress = value.toInt()
-                textSliderValue!!.text = String.format(
-                    context.getString(R.string.value_with_units),
-                    sliderProgress.toString(),
-                    item.units
-                )
-            }
-        }
-
-        dialog = MaterialAlertDialogBuilder(context)
-            .setTitle(item.nameId)
-            .setView(sliderBinding.root)
-            .setPositiveButton(android.R.string.ok, this)
-            .setNegativeButton(android.R.string.cancel, defaultCancelListener)
-            .show()
+        SettingsDialogFragment.newInstance(
+            settingsViewModel,
+            item,
+            SettingsItem.TYPE_SLIDER,
+            position
+        ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
     }
 
     fun onSubmenuClick(item: SubmenuSetting) {
-        fragmentView.loadSubMenu(item.menuKey)
+        val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null)
+        fragment.view?.findNavController()?.navigate(action)
     }
 
-    override fun onClick(dialog: DialogInterface, which: Int) {
-        when (clickedItem) {
-            is SingleChoiceSetting -> {
-                val scSetting = clickedItem as SingleChoiceSetting
-                val value = getValueForSingleChoiceSelection(scSetting, which)
-                if (scSetting.selectedValue != value) {
-                    fragmentView.onSettingChanged()
-                }
-
-                // Get the backing Setting, which may be null (if for example it was missing from the file)
-                val setting = scSetting.setSelectedValue(value)
-                fragmentView.putSetting(setting)
-                closeDialog()
-            }
-
-            is StringSingleChoiceSetting -> {
-                val scSetting = clickedItem as StringSingleChoiceSetting
-                val value = scSetting.getValueAt(which)
-                if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
-                val setting = scSetting.setSelectedValue(value!!)
-                fragmentView.putSetting(setting)
-                closeDialog()
-            }
-
-            is SliderSetting -> {
-                val sliderSetting = clickedItem as SliderSetting
-                if (sliderSetting.selectedValue != sliderProgress) {
-                    fragmentView.onSettingChanged()
-                }
-                if (sliderSetting.setting is FloatSetting) {
-                    val value = sliderProgress.toFloat()
-                    val setting = sliderSetting.setSelectedValue(value)
-                    fragmentView.putSetting(setting)
-                } else {
-                    val setting = sliderSetting.setSelectedValue(sliderProgress)
-                    fragmentView.putSetting(setting)
-                }
-                closeDialog()
-            }
-        }
-        clickedItem = null
-        sliderProgress = -1
-    }
-
-    fun onLongClick(setting: AbstractSetting, position: Int): Boolean {
-        MaterialAlertDialogBuilder(context)
-            .setMessage(R.string.reset_setting_confirmation)
-            .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int ->
-                when (setting) {
-                    is AbstractBooleanSetting -> setting.boolean = setting.defaultValue as Boolean
-                    is AbstractFloatSetting -> setting.float = setting.defaultValue as Float
-                    is AbstractIntSetting -> setting.int = setting.defaultValue as Int
-                    is AbstractStringSetting -> setting.string = setting.defaultValue as String
-                }
-                notifyItemChanged(position)
-                fragmentView.onSettingChanged()
-            }
-            .setNegativeButton(android.R.string.cancel, null)
-            .show()
+    fun onLongClick(item: SettingsItem, position: Int): Boolean {
+        SettingsDialogFragment.newInstance(
+            settingsViewModel,
+            item,
+            SettingsDialogFragment.TYPE_RESET_SETTING,
+            position
+        ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
 
         return true
     }
 
-    fun closeDialog() {
-        if (dialog != null) {
-            if (clickedPosition != -1) {
-                notifyItemChanged(clickedPosition)
-                clickedPosition = -1
-            }
-            dialog!!.dismiss()
-            dialog = null
+    private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
+        override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
+            return oldItem.setting.key == newItem.setting.key
         }
-    }
 
-    private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
-        val valuesId = item.valuesId
-        return if (valuesId > 0) {
-            val valuesArray = context.resources.getIntArray(valuesId)
-            valuesArray[which]
-        } else {
-            which
+        override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
+            return oldItem.setting.key == newItem.setting.key
         }
     }
-
-    private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
-        val value = item.selectedValue
-        val valuesId = item.valuesId
-        if (valuesId > 0) {
-            val valuesArray = context.resources.getIntArray(valuesId)
-            for (index in valuesArray.indices) {
-                val current = valuesArray[index]
-                if (current == value) {
-                    return index
-                }
-            }
-        } else {
-            return value
-        }
-        return -1
-    }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index 70a74c4dda..bc319714c7 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -3,40 +3,43 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
-import android.content.Context
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.view.ViewGroup.MarginLayoutParams
 import androidx.core.view.ViewCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.navigation.findNavController
+import androidx.navigation.fragment.navArgs
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.divider.MaterialDividerItemDecoration
+import com.google.android.material.transition.MaterialSharedAxis
+import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
+import org.yuzu.yuzu_emu.model.SettingsViewModel
 
-class SettingsFragment : Fragment(), SettingsFragmentView {
-    override var activityView: SettingsActivityView? = null
-
-    private val fragmentPresenter = SettingsFragmentPresenter(this)
+class SettingsFragment : Fragment() {
+    private lateinit var presenter: SettingsFragmentPresenter
     private var settingsAdapter: SettingsAdapter? = null
 
     private var _binding: FragmentSettingsBinding? = null
     private val binding get() = _binding!!
 
-    override fun onAttach(context: Context) {
-        super.onAttach(context)
-        activityView = requireActivity() as SettingsActivityView
-    }
+    private val args by navArgs<SettingsFragmentArgs>()
+
+    private val settingsViewModel: SettingsViewModel by activityViewModels()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        val menuTag = requireArguments().getString(ARGUMENT_MENU_TAG)
-        val gameId = requireArguments().getString(ARGUMENT_GAME_ID)
-        fragmentPresenter.onCreate(menuTag!!, gameId!!)
+        enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+        returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+        exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
     }
 
     override fun onCreateView(
@@ -49,7 +52,14 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        settingsAdapter = SettingsAdapter(this, requireActivity())
+        settingsAdapter = SettingsAdapter(this, requireContext())
+        presenter = SettingsFragmentPresenter(
+            settingsViewModel,
+            settingsAdapter!!,
+            args.menuTag,
+            args.game?.gameId ?: ""
+        )
+
         val dividerDecoration = MaterialDividerItemDecoration(
             requireContext(),
             LinearLayoutManager.VERTICAL
@@ -57,71 +67,86 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
         dividerDecoration.isLastItemDecorated = false
         binding.listSettings.apply {
             adapter = settingsAdapter
-            layoutManager = LinearLayoutManager(activity)
+            layoutManager = LinearLayoutManager(requireContext())
             addItemDecoration(dividerDecoration)
         }
-        fragmentPresenter.onViewCreated()
+
+        binding.toolbarSettings.setNavigationOnClickListener {
+            settingsViewModel.setShouldNavigateBack(true)
+        }
+
+        settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
+            if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
+        }
+
+        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
+            if (it) {
+                settingsViewModel.setShouldReloadSettingsList(false)
+                presenter.loadSettingsList()
+            }
+        }
+
+        settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
+            if (it) {
+                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+                exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+            } else {
+                reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+                exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+            }
+        }
+
+        if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
+            binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
+            binding.toolbarSettings.setOnMenuItemClickListener {
+                when (it.itemId) {
+                    R.id.action_search -> {
+                        reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+                        exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+                        view.findNavController()
+                            .navigate(R.id.action_settingsFragment_to_settingsSearchFragment)
+                        true
+                    }
+
+                    else -> false
+                }
+            }
+        }
+
+        presenter.onViewCreated()
 
         setInsets()
     }
 
-    override fun onDetach() {
-        super.onDetach()
-        activityView = null
-        if (settingsAdapter != null) {
-            settingsAdapter!!.closeDialog()
-        }
-    }
-
-    override fun showSettingsList(settingsList: ArrayList<SettingsItem>) {
-        settingsAdapter!!.setSettingsList(settingsList)
-    }
-
-    override fun loadSettingsList() {
-        fragmentPresenter.loadSettingsList()
-    }
-
-    override fun loadSubMenu(menuKey: String) {
-        activityView!!.showSettingsFragment(
-            menuKey,
-            true,
-            requireArguments().getString(ARGUMENT_GAME_ID)!!
-        )
-    }
-
-    override fun showToastMessage(message: String?, is_long: Boolean) {
-        activityView!!.showToastMessage(message!!, is_long)
-    }
-
-    override fun putSetting(setting: AbstractSetting) {
-        fragmentPresenter.putSetting(setting)
-    }
-
-    override fun onSettingChanged() {
-        activityView!!.onSettingChanged()
+    override fun onResume() {
+        super.onResume()
+        settingsViewModel.setIsUsingSearch(false)
     }
 
     private fun setInsets() {
         ViewCompat.setOnApplyWindowInsetsListener(
-            binding.listSettings
-        ) { view: View, windowInsets: WindowInsetsCompat ->
-            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
-            view.updatePadding(bottom = insets.bottom)
+            binding.root
+        ) { _: View, windowInsets: WindowInsetsCompat ->
+            val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+            val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+
+            val leftInsets = barInsets.left + cutoutInsets.left
+            val rightInsets = barInsets.right + cutoutInsets.right
+
+            val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
+            val mlpSettingsList = binding.listSettings.layoutParams as MarginLayoutParams
+            mlpSettingsList.leftMargin = sideMargin + leftInsets
+            mlpSettingsList.rightMargin = sideMargin + rightInsets
+            binding.listSettings.layoutParams = mlpSettingsList
+            binding.listSettings.updatePadding(
+                bottom = barInsets.bottom
+            )
+
+            val mlpAppBar = binding.appbarSettings.layoutParams as MarginLayoutParams
+            mlpAppBar.leftMargin = leftInsets
+            mlpAppBar.rightMargin = rightInsets
+            binding.appbarSettings.layoutParams = mlpAppBar
             windowInsets
         }
     }
-
-    companion object {
-        private const val ARGUMENT_MENU_TAG = "menu_tag"
-        private const val ARGUMENT_GAME_ID = "game_id"
-
-        fun newInstance(menuTag: String?, gameId: String?): Fragment {
-            val fragment = SettingsFragment()
-            val arguments = Bundle()
-            arguments.putString(ARGUMENT_MENU_TAG, menuTag)
-            arguments.putString(ARGUMENT_GAME_ID, gameId)
-            fragment.arguments = arguments
-            return fragment
-        }
-    }
 }
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 59c1d9d545..22a529b1ba 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,63 +3,66 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
+import android.content.Context
 import android.content.SharedPreferences
 import android.os.Build
 import android.text.TextUtils
+import android.widget.Toast
 import androidx.preference.PreferenceManager
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
 import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
+import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.LongSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.StringSetting
+import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
 import org.yuzu.yuzu_emu.features.settings.model.view.*
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
-import org.yuzu.yuzu_emu.utils.ThemeHelper
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
 
-class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
-    private var menuTag: String? = null
-    private lateinit var gameId: String
-    private var settingsList: ArrayList<SettingsItem>? = null
+class SettingsFragmentPresenter(
+    private val settingsViewModel: SettingsViewModel,
+    private val adapter: SettingsAdapter,
+    private var menuTag: String,
+    private var gameId: String
+) {
+    private var settingsList = ArrayList<SettingsItem>()
 
-    private val settingsActivity get() = fragmentView.activityView as SettingsActivity
-    private val settings get() = fragmentView.activityView!!.settings
+    private val preferences: SharedPreferences
+        get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
 
-    private lateinit var preferences: SharedPreferences
+    private val context: Context get() = YuzuApplication.appContext
 
-    fun onCreate(menuTag: String, gameId: String) {
-        this.gameId = gameId
-        this.menuTag = menuTag
+    // Extension for populating settings list based on paired settings
+    fun ArrayList<SettingsItem>.add(key: String) {
+        val item = SettingsItem.settingsItems[key]!!
+        val pairedSettingKey = item.setting.pairedSettingKey
+        if (pairedSettingKey.isNotEmpty()) {
+            val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
+            if (!pairedSettingValue) return
+        }
+        add(item)
     }
 
     fun onViewCreated() {
-        preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
         loadSettingsList()
     }
 
-    fun putSetting(setting: AbstractSetting) {
-        if (setting.section == null || setting.key == null) {
-            return
-        }
-
-        val section = settings.getSection(setting.section!!)!!
-        if (section.getSetting(setting.key!!) == null) {
-            section.putSetting(setting)
-        }
-    }
-
     fun loadSettingsList() {
         if (!TextUtils.isEmpty(gameId)) {
-            settingsActivity.setToolbarTitle("Game Settings: $gameId")
+            settingsViewModel.setToolbarTitle(
+                context.getString(
+                    R.string.advanced_settings_game,
+                    gameId
+                )
+            )
         }
+
         val sl = ArrayList<SettingsItem>()
-        if (menuTag == null) {
-            return
-        }
         when (menuTag) {
             SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
             Settings.SECTION_GENERAL -> addGeneralSettings(sl)
@@ -69,335 +72,104 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             Settings.SECTION_THEME -> addThemeSettings(sl)
             Settings.SECTION_DEBUG -> addDebugSettings(sl)
             else -> {
-                fragmentView.showToastMessage("Unimplemented menu", false)
+                val context = YuzuApplication.appContext
+                Toast.makeText(
+                    context,
+                    context.getString(R.string.unimplemented_menu),
+                    Toast.LENGTH_SHORT
+                ).show()
                 return
             }
         }
         settingsList = sl
-        fragmentView.showSettingsList(settingsList!!)
+        adapter.submitList(settingsList)
     }
 
     private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.advanced_settings))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
         sl.apply {
+            add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
+            add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
+            add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
+            add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
+            add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
             add(
-                SubmenuSetting(
-                    R.string.preferences_general,
-                    0,
-                    Settings.SECTION_GENERAL
-                )
-            )
-            add(
-                SubmenuSetting(
-                    R.string.preferences_system,
-                    0,
-                    Settings.SECTION_SYSTEM
-                )
-            )
-            add(
-                SubmenuSetting(
-                    R.string.preferences_graphics,
-                    0,
-                    Settings.SECTION_RENDERER
-                )
-            )
-            add(
-                SubmenuSetting(
-                    R.string.preferences_audio,
-                    0,
-                    Settings.SECTION_AUDIO
-                )
-            )
-            add(
-                SubmenuSetting(
-                    R.string.preferences_debug,
-                    0,
-                    Settings.SECTION_DEBUG
-                )
-            )
-            add(
-                RunnableSetting(
-                    R.string.reset_to_default,
-                    0,
-                    false
-                ) {
-                    ResetSettingsDialogFragment().show(
-                        settingsActivity.supportFragmentManager,
-                        ResetSettingsDialogFragment.TAG
-                    )
+                RunnableSetting(R.string.reset_to_default, 0, false) {
+                    settingsViewModel.setShouldShowResetSettingsDialog(true)
                 }
             )
         }
     }
 
     private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_general))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
         sl.apply {
-            add(
-                SwitchSetting(
-                    IntSetting.RENDERER_USE_SPEED_LIMIT,
-                    R.string.frame_limit_enable,
-                    R.string.frame_limit_enable_description,
-                    IntSetting.RENDERER_USE_SPEED_LIMIT.key,
-                    IntSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
-                )
-            )
-            add(
-                SliderSetting(
-                    IntSetting.RENDERER_SPEED_LIMIT,
-                    R.string.frame_limit_slider,
-                    R.string.frame_limit_slider_description,
-                    1,
-                    200,
-                    "%",
-                    IntSetting.RENDERER_SPEED_LIMIT.key,
-                    IntSetting.RENDERER_SPEED_LIMIT.defaultValue
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.CPU_ACCURACY,
-                    R.string.cpu_accuracy,
-                    0,
-                    R.array.cpuAccuracyNames,
-                    R.array.cpuAccuracyValues,
-                    IntSetting.CPU_ACCURACY.key,
-                    IntSetting.CPU_ACCURACY.defaultValue
-                )
-            )
-            add(
-                SwitchSetting(
-                    BooleanSetting.PICTURE_IN_PICTURE,
-                    R.string.picture_in_picture,
-                    R.string.picture_in_picture_description,
-                    BooleanSetting.PICTURE_IN_PICTURE.key,
-                    BooleanSetting.PICTURE_IN_PICTURE.defaultValue
-                )
-            )
+            add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
+            add(ShortSetting.RENDERER_SPEED_LIMIT.key)
+            add(IntSetting.CPU_ACCURACY.key)
+            add(BooleanSetting.PICTURE_IN_PICTURE.key)
         }
     }
 
     private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_system))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
         sl.apply {
-            add(
-                SwitchSetting(
-                    IntSetting.USE_DOCKED_MODE,
-                    R.string.use_docked_mode,
-                    R.string.use_docked_mode_description,
-                    IntSetting.USE_DOCKED_MODE.key,
-                    IntSetting.USE_DOCKED_MODE.defaultValue
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.REGION_INDEX,
-                    R.string.emulated_region,
-                    0,
-                    R.array.regionNames,
-                    R.array.regionValues,
-                    IntSetting.REGION_INDEX.key,
-                    IntSetting.REGION_INDEX.defaultValue
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.LANGUAGE_INDEX,
-                    R.string.emulated_language,
-                    0,
-                    R.array.languageNames,
-                    R.array.languageValues,
-                    IntSetting.LANGUAGE_INDEX.key,
-                    IntSetting.LANGUAGE_INDEX.defaultValue
-                )
-            )
-            add(
-                SwitchSetting(
-                    BooleanSetting.USE_CUSTOM_RTC,
-                    R.string.use_custom_rtc,
-                    R.string.use_custom_rtc_description,
-                    BooleanSetting.USE_CUSTOM_RTC.key,
-                    BooleanSetting.USE_CUSTOM_RTC.defaultValue
-                )
-            )
-            add(
-                DateTimeSetting(
-                    StringSetting.CUSTOM_RTC,
-                    R.string.set_custom_rtc,
-                    0,
-                    StringSetting.CUSTOM_RTC.key,
-                    StringSetting.CUSTOM_RTC.defaultValue
-                )
-            )
+            add(BooleanSetting.USE_DOCKED_MODE.key)
+            add(IntSetting.REGION_INDEX.key)
+            add(IntSetting.LANGUAGE_INDEX.key)
+            add(BooleanSetting.USE_CUSTOM_RTC.key)
+            add(LongSetting.CUSTOM_RTC.key)
         }
     }
 
     private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
         sl.apply {
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_ACCURACY,
-                    R.string.renderer_accuracy,
-                    0,
-                    R.array.rendererAccuracyNames,
-                    R.array.rendererAccuracyValues,
-                    IntSetting.RENDERER_ACCURACY.key,
-                    IntSetting.RENDERER_ACCURACY.defaultValue
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_RESOLUTION,
-                    R.string.renderer_resolution,
-                    0,
-                    R.array.rendererResolutionNames,
-                    R.array.rendererResolutionValues,
-                    IntSetting.RENDERER_RESOLUTION.key,
-                    IntSetting.RENDERER_RESOLUTION.defaultValue
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_VSYNC,
-                    R.string.renderer_vsync,
-                    0,
-                    R.array.rendererVSyncNames,
-                    R.array.rendererVSyncValues,
-                    IntSetting.RENDERER_VSYNC.key,
-                    IntSetting.RENDERER_VSYNC.defaultValue
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_SCALING_FILTER,
-                    R.string.renderer_scaling_filter,
-                    0,
-                    R.array.rendererScalingFilterNames,
-                    R.array.rendererScalingFilterValues,
-                    IntSetting.RENDERER_SCALING_FILTER.key,
-                    IntSetting.RENDERER_SCALING_FILTER.defaultValue
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_ANTI_ALIASING,
-                    R.string.renderer_anti_aliasing,
-                    0,
-                    R.array.rendererAntiAliasingNames,
-                    R.array.rendererAntiAliasingValues,
-                    IntSetting.RENDERER_ANTI_ALIASING.key,
-                    IntSetting.RENDERER_ANTI_ALIASING.defaultValue
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_SCREEN_LAYOUT,
-                    R.string.renderer_screen_layout,
-                    0,
-                    R.array.rendererScreenLayoutNames,
-                    R.array.rendererScreenLayoutValues,
-                    IntSetting.RENDERER_SCREEN_LAYOUT.key,
-                    IntSetting.RENDERER_SCREEN_LAYOUT.defaultValue
-                )
-            )
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_ASPECT_RATIO,
-                    R.string.renderer_aspect_ratio,
-                    0,
-                    R.array.rendererAspectRatioNames,
-                    R.array.rendererAspectRatioValues,
-                    IntSetting.RENDERER_ASPECT_RATIO.key,
-                    IntSetting.RENDERER_ASPECT_RATIO.defaultValue
-                )
-            )
-            add(
-                SwitchSetting(
-                    IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
-                    R.string.use_disk_shader_cache,
-                    R.string.use_disk_shader_cache_description,
-                    IntSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
-                    IntSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
-                )
-            )
-            add(
-                SwitchSetting(
-                    IntSetting.RENDERER_FORCE_MAX_CLOCK,
-                    R.string.renderer_force_max_clock,
-                    R.string.renderer_force_max_clock_description,
-                    IntSetting.RENDERER_FORCE_MAX_CLOCK.key,
-                    IntSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
-                )
-            )
-            add(
-                SwitchSetting(
-                    IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
-                    R.string.renderer_asynchronous_shaders,
-                    R.string.renderer_asynchronous_shaders_description,
-                    IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
-                    IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
-                )
-            )
-            add(
-                SwitchSetting(
-                    IntSetting.RENDERER_REACTIVE_FLUSHING,
-                    R.string.renderer_reactive_flushing,
-                    R.string.renderer_reactive_flushing_description,
-                    IntSetting.RENDERER_REACTIVE_FLUSHING.key,
-                    IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
-                )
-            )
+            add(IntSetting.RENDERER_ACCURACY.key)
+            add(IntSetting.RENDERER_RESOLUTION.key)
+            add(IntSetting.RENDERER_VSYNC.key)
+            add(IntSetting.RENDERER_SCALING_FILTER.key)
+            add(IntSetting.RENDERER_ANTI_ALIASING.key)
+            add(IntSetting.RENDERER_SCREEN_LAYOUT.key)
+            add(IntSetting.RENDERER_ASPECT_RATIO.key)
+            add(BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key)
+            add(BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key)
+            add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
+            add(BooleanSetting.RENDERER_REACTIVE_FLUSHING.key)
         }
     }
 
     private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
         sl.apply {
-            add(
-                StringSingleChoiceSetting(
-                    StringSetting.AUDIO_OUTPUT_ENGINE,
-                    R.string.audio_output_engine,
-                    0,
-                    settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
-                    settingsActivity.resources.getStringArray(R.array.outputEngineValues),
-                    StringSetting.AUDIO_OUTPUT_ENGINE.key,
-                    StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
-                )
-            )
-            add(
-                SliderSetting(
-                    IntSetting.AUDIO_VOLUME,
-                    R.string.audio_volume,
-                    R.string.audio_volume_description,
-                    0,
-                    100,
-                    "%",
-                    IntSetting.AUDIO_VOLUME.key,
-                    IntSetting.AUDIO_VOLUME.defaultValue
-                )
-            )
+            add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
+            add(ByteSetting.AUDIO_VOLUME.key)
         }
     }
 
     private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_theme))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
         sl.apply {
             val theme: AbstractIntSetting = object : AbstractIntSetting {
-                override var int: Int
+                override val int: Int
                     get() = preferences.getInt(Settings.PREF_THEME, 0)
-                    set(value) {
-                        preferences.edit()
-                            .putInt(Settings.PREF_THEME, value)
-                            .apply()
-                        settingsActivity.recreate()
-                    }
-                override val key: String? = null
-                override val section: String? = null
-                override val isRuntimeEditable: Boolean = false
-                override val valueAsString: String
-                    get() = preferences.getInt(Settings.PREF_THEME, 0).toString()
-                override val defaultValue: Any = 0
+
+                override fun setInt(value: Int) {
+                    preferences.edit()
+                        .putInt(Settings.PREF_THEME, value)
+                        .apply()
+                    settingsViewModel.setShouldRecreate(true)
+                }
+
+                override val key: String = Settings.PREF_THEME
+                override val category = Settings.Category.UiGeneral
+                override val isRuntimeModifiable: Boolean = false
+                override val defaultValue: Int = 0
+                override fun reset() {
+                    preferences.edit()
+                        .putInt(Settings.PREF_THEME, defaultValue)
+                        .apply()
+                }
             }
 
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -423,20 +195,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             }
 
             val themeMode: AbstractIntSetting = object : AbstractIntSetting {
-                override var int: Int
+                override val int: Int
                     get() = preferences.getInt(Settings.PREF_THEME_MODE, -1)
-                    set(value) {
-                        preferences.edit()
-                            .putInt(Settings.PREF_THEME_MODE, value)
-                            .apply()
-                        ThemeHelper.setThemeMode(settingsActivity)
-                    }
-                override val key: String? = null
-                override val section: String? = null
-                override val isRuntimeEditable: Boolean = false
-                override val valueAsString: String
-                    get() = preferences.getInt(Settings.PREF_THEME_MODE, -1).toString()
-                override val defaultValue: Any = -1
+
+                override fun setInt(value: Int) {
+                    preferences.edit()
+                        .putInt(Settings.PREF_THEME_MODE, value)
+                        .apply()
+                    settingsViewModel.setShouldRecreate(true)
+                }
+
+                override val key: String = Settings.PREF_THEME_MODE
+                override val category = Settings.Category.UiGeneral
+                override val isRuntimeModifiable: Boolean = false
+                override val defaultValue: Int = -1
+                override fun reset() {
+                    preferences.edit()
+                        .putInt(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
+                        .apply()
+                    settingsViewModel.setShouldRecreate(true)
+                }
             }
 
             add(
@@ -450,21 +228,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
 
             val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting {
-                override var boolean: Boolean
+                override val boolean: Boolean
                     get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
-                    set(value) {
-                        preferences.edit()
-                            .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
-                            .apply()
-                        settingsActivity.recreate()
-                    }
-                override val key: String? = null
-                override val section: String? = null
-                override val isRuntimeEditable: Boolean = false
-                override val valueAsString: String
-                    get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
-                        .toString()
-                override val defaultValue: Any = false
+
+                override fun setBoolean(value: Boolean) {
+                    preferences.edit()
+                        .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, value)
+                        .apply()
+                    settingsViewModel.setShouldRecreate(true)
+                }
+
+                override val key: String = Settings.PREF_BLACK_BACKGROUNDS
+                override val category = Settings.Category.UiGeneral
+                override val isRuntimeModifiable: Boolean = false
+                override val defaultValue: Boolean = false
+                override fun reset() {
+                    preferences.edit()
+                        .putBoolean(Settings.PREF_BLACK_BACKGROUNDS, defaultValue)
+                        .apply()
+                    settingsViewModel.setShouldRecreate(true)
+                }
             }
 
             add(
@@ -478,62 +261,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     }
 
     private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
-        settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
+        settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
         sl.apply {
             add(HeaderSetting(R.string.gpu))
-            add(
-                SingleChoiceSetting(
-                    IntSetting.RENDERER_BACKEND,
-                    R.string.renderer_api,
-                    0,
-                    R.array.rendererApiNames,
-                    R.array.rendererApiValues,
-                    IntSetting.RENDERER_BACKEND.key,
-                    IntSetting.RENDERER_BACKEND.defaultValue
-                )
-            )
-            add(
-                SwitchSetting(
-                    IntSetting.RENDERER_DEBUG,
-                    R.string.renderer_debug,
-                    R.string.renderer_debug_description,
-                    IntSetting.RENDERER_DEBUG.key,
-                    IntSetting.RENDERER_DEBUG.defaultValue
-                )
-            )
+            add(IntSetting.RENDERER_BACKEND.key)
+            add(BooleanSetting.RENDERER_DEBUG.key)
 
             add(HeaderSetting(R.string.cpu))
-            add(
-                SwitchSetting(
-                    BooleanSetting.CPU_DEBUG_MODE,
-                    R.string.cpu_debug_mode,
-                    R.string.cpu_debug_mode_description,
-                    BooleanSetting.CPU_DEBUG_MODE.key,
-                    BooleanSetting.CPU_DEBUG_MODE.defaultValue
-                )
-            )
-
-            val fastmem = object : AbstractBooleanSetting {
-                override var boolean: Boolean
-                    get() =
-                        BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
-                    set(value) {
-                        BooleanSetting.FASTMEM.boolean = value
-                        BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
-                    }
-                override val key: String? = null
-                override val section: String = Settings.SECTION_CPU
-                override val isRuntimeEditable: Boolean = false
-                override val valueAsString: String = ""
-                override val defaultValue: Any = true
-            }
-            add(
-                SwitchSetting(
-                    fastmem,
-                    R.string.fastmem,
-                    0
-                )
-            )
+            add(BooleanSetting.CPU_DEBUG_MODE.key)
+            add(SettingsItem.FASTMEM_COMBINED)
         }
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
deleted file mode 100644
index 1ebe35eaad..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentView.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.features.settings.ui
-
-import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
-
-/**
- * Abstraction for a screen showing a list of settings. Instances of
- * this type of view will each display a layer of the setting hierarchy.
- */
-interface SettingsFragmentView {
-    /**
-     * Pass an ArrayList to the View so that it can be displayed on screen.
-     *
-     * @param settingsList The result of converting the HashMap to an ArrayList
-     */
-    fun showSettingsList(settingsList: ArrayList<SettingsItem>)
-
-    /**
-     * Instructs the Fragment to load the settings screen.
-     */
-    fun loadSettingsList()
-
-    /**
-     * @return The Fragment's containing activity.
-     */
-    val activityView: SettingsActivityView?
-
-    /**
-     * Tell the Fragment to tell the containing Activity to show a new
-     * Fragment containing a submenu of settings.
-     *
-     * @param menuKey Identifier for the settings group that should be shown.
-     */
-    fun loadSubMenu(menuKey: String)
-
-    /**
-     * Tell the Fragment to tell the containing activity to display a toast message.
-     *
-     * @param message Text to be shown in the Toast
-     * @param is_long Whether this should be a long Toast or short one.
-     */
-    fun showToastMessage(message: String?, is_long: Boolean)
-
-    /**
-     * Have the fragment add a setting to the HashMap.
-     *
-     * @param setting The (possibly previously missing) new setting.
-     */
-    fun putSetting(setting: AbstractSetting)
-
-    /**
-     * Have the fragment tell the containing Activity that a setting was modified.
-     */
-    fun onSettingChanged()
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
index 79572fc06b..525f013f8b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt
@@ -29,7 +29,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
         }
 
         binding.textSettingValue.visibility = View.VISIBLE
-        val epochTime = setting.value.toLong()
+        val epochTime = setting.value
         val instant = Instant.ofEpochMilli(epochTime * 1000)
         val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC"))
         val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
@@ -46,7 +46,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+            return adapter.onLongClick(setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index b42d955aa7..80d1b22c1a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
@@ -35,7 +35,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
                 }
             }
         } else if (item is StringSingleChoiceSetting) {
-            for (i in item.values!!.indices) {
+            for (i in item.values.indices) {
                 if (item.values[i] == item.selectedValue) {
                     binding.textSettingValue.text = item.choices[i]
                     break
@@ -66,7 +66,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+            return adapter.onLongClick(setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
index a23b5d1099..b83c901006 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
@@ -41,7 +41,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+            return adapter.onLongClick(setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index ef34bf5f44..57fdeaa208 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -25,10 +25,12 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
             binding.textSettingDescription.text = ""
             binding.textSettingDescription.visibility = View.GONE
         }
+
+        binding.switchWidget.setOnCheckedChangeListener(null)
+        binding.switchWidget.isChecked = setting.checked
         binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
-            adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
+            adapter.onBooleanClick(item, binding.switchWidget.isChecked)
         }
-        binding.switchWidget.isChecked = setting.isChecked
 
         setStyle(setting.isEditable, binding)
     }
@@ -41,7 +43,7 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
 
     override fun onLongClick(clicked: View): Boolean {
         if (setting.isEditable) {
-            return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
+            return adapter.onLongClick(setting, bindingAdapterPosition)
         }
         return false
     }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
index 70a52df5d4..2b04d666a9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
@@ -3,18 +3,15 @@
 
 package org.yuzu.yuzu_emu.features.settings.utils
 
+import android.widget.Toast
 import java.io.*
-import java.util.*
 import org.ini4j.Wini
-import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.*
-import org.yuzu.yuzu_emu.features.settings.model.Settings.SettingsSectionMap
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
-import org.yuzu.yuzu_emu.utils.BiMap
 import org.yuzu.yuzu_emu.utils.DirectoryInitialization
 import org.yuzu.yuzu_emu.utils.Log
+import org.yuzu.yuzu_emu.utils.NativeConfig
 
 /**
  * Contains static methods for interacting with .ini files in which settings are stored.
@@ -22,243 +19,41 @@ import org.yuzu.yuzu_emu.utils.Log
 object SettingsFile {
     const val FILE_NAME_CONFIG = "config"
 
-    private var sectionsMap = BiMap<String?, String?>()
-
-    /**
-     * Reads a given .ini file from disk and returns it as a HashMap of Settings, themselves
-     * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
-     * failed.
-     *
-     * @param ini          The ini file to load the settings from
-     * @param isCustomGame
-     * @param view         The current view.
-     * @return An Observable that emits a HashMap of the file's contents, then completes.
-     */
-    private fun readFile(
-        ini: File?,
-        isCustomGame: Boolean,
-        view: SettingsActivityView? = null
-    ): HashMap<String, SettingSection?> {
-        val sections: HashMap<String, SettingSection?> = SettingsSectionMap()
-        var reader: BufferedReader? = null
-        try {
-            reader = BufferedReader(FileReader(ini))
-            var current: SettingSection? = null
-            var line: String?
-            while (reader.readLine().also { line = it } != null) {
-                if (line!!.startsWith("[") && line!!.endsWith("]")) {
-                    current = sectionFromLine(line!!, isCustomGame)
-                    sections[current.name] = current
-                } else if (current != null) {
-                    val setting = settingFromLine(line!!)
-                    if (setting != null) {
-                        current.putSetting(setting)
-                    }
-                }
-            }
-        } catch (e: FileNotFoundException) {
-            Log.error("[SettingsFile] File not found: " + e.message)
-            view?.onSettingsFileNotFound()
-        } catch (e: IOException) {
-            Log.error("[SettingsFile] Error reading from: " + e.message)
-            view?.onSettingsFileNotFound()
-        } finally {
-            if (reader != null) {
-                try {
-                    reader.close()
-                } catch (e: IOException) {
-                    Log.error("[SettingsFile] Error closing: " + e.message)
-                }
-            }
-        }
-        return sections
-    }
-
-    fun readFile(fileName: String, view: SettingsActivityView?): HashMap<String, SettingSection?> {
-        return readFile(getSettingsFile(fileName), false, view)
-    }
-
-    fun readFile(fileName: String): HashMap<String, SettingSection?> =
-        readFile(getSettingsFile(fileName), false)
-
-    /**
-     * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
-     * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
-     * failed.
-     *
-     * @param gameId the id of the game to load it's settings.
-     * @param view   The current view.
-     */
-    fun readCustomGameSettings(
-        gameId: String,
-        view: SettingsActivityView?
-    ): HashMap<String, SettingSection?> {
-        return readFile(getCustomGameSettingsFile(gameId), true, view)
-    }
-
     /**
      * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
      * telling why it failed.
      *
      * @param fileName The target filename without a path or extension.
-     * @param sections The HashMap containing the Settings we want to serialize.
-     * @param view     The current view.
      */
-    fun saveFile(
-        fileName: String,
-        sections: TreeMap<String, SettingSection>,
-        view: SettingsActivityView
-    ) {
+    fun saveFile(fileName: String) {
         val ini = getSettingsFile(fileName)
         try {
-            val writer = Wini(ini)
-            val keySet: Set<String> = sections.keys
-            for (key in keySet) {
-                val section = sections[key]
-                writeSection(writer, section!!)
+            val wini = Wini(ini)
+            for (specificCategory in Settings.Category.values()) {
+                val categoryHeader = NativeConfig.getConfigHeader(specificCategory.ordinal)
+                for (setting in Settings.settingsList) {
+                    if (setting.key!!.isEmpty()) continue
+
+                    val settingCategoryHeader =
+                        NativeConfig.getConfigHeader(setting.category.ordinal)
+                    val iniSetting: String? = wini.get(categoryHeader, setting.key)
+                    if (iniSetting != null || settingCategoryHeader == categoryHeader) {
+                        wini.put(settingCategoryHeader, setting.key, setting.valueAsString)
+                    }
+                }
             }
-            writer.store()
+            wini.store()
         } catch (e: IOException) {
             Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.message)
-            view.showToastMessage(
-                YuzuApplication.appContext
-                    .getString(R.string.error_saving, fileName, e.message),
-                false
-            )
+            val context = YuzuApplication.appContext
+            Toast.makeText(
+                context,
+                context.getString(R.string.error_saving, fileName, e.message),
+                Toast.LENGTH_SHORT
+            ).show()
         }
     }
 
-    fun saveCustomGameSettings(gameId: String?, sections: HashMap<String, SettingSection?>) {
-        val sortedSections: Set<String> = TreeSet(sections.keys)
-        for (sectionKey in sortedSections) {
-            val section = sections[sectionKey]
-            val settings = section!!.settings
-            val sortedKeySet: Set<String> = TreeSet(settings.keys)
-            for (settingKey in sortedKeySet) {
-                val setting = settings[settingKey]
-                NativeLibrary.setUserSetting(
-                    gameId,
-                    mapSectionNameFromIni(
-                        section.name
-                    ),
-                    setting!!.key,
-                    setting.valueAsString
-                )
-            }
-        }
-    }
-
-    private fun mapSectionNameFromIni(generalSectionName: String): String? {
-        return if (sectionsMap.getForward(generalSectionName) != null) {
-            sectionsMap.getForward(generalSectionName)
-        } else {
-            generalSectionName
-        }
-    }
-
-    private fun mapSectionNameToIni(generalSectionName: String): String {
-        return if (sectionsMap.getBackward(generalSectionName) != null) {
-            sectionsMap.getBackward(generalSectionName).toString()
-        } else {
-            generalSectionName
-        }
-    }
-
-    fun getSettingsFile(fileName: String): File {
-        return File(
-            DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini"
-        )
-    }
-
-    private fun getCustomGameSettingsFile(gameId: String): File {
-        return File(DirectoryInitialization.userDirectory + "/GameSettings/" + gameId + ".ini")
-    }
-
-    private fun sectionFromLine(line: String, isCustomGame: Boolean): SettingSection {
-        var sectionName: String = line.substring(1, line.length - 1)
-        if (isCustomGame) {
-            sectionName = mapSectionNameToIni(sectionName)
-        }
-        return SettingSection(sectionName)
-    }
-
-    /**
-     * For a line of text, determines what type of data is being represented, and returns
-     * a Setting object containing this data.
-     *
-     * @param line    The line of text being parsed.
-     * @return A typed Setting containing the key/value contained in the line.
-     */
-    private fun settingFromLine(line: String): AbstractSetting? {
-        val splitLine = line.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
-        if (splitLine.size != 2) {
-            return null
-        }
-        val key = splitLine[0].trim { it <= ' ' }
-        val value = splitLine[1].trim { it <= ' ' }
-        if (value.isEmpty()) {
-            return null
-        }
-
-        val booleanSetting = BooleanSetting.from(key)
-        if (booleanSetting != null) {
-            booleanSetting.boolean = value.toBoolean()
-            return booleanSetting
-        }
-
-        val intSetting = IntSetting.from(key)
-        if (intSetting != null) {
-            intSetting.int = value.toInt()
-            return intSetting
-        }
-
-        val floatSetting = FloatSetting.from(key)
-        if (floatSetting != null) {
-            floatSetting.float = value.toFloat()
-            return floatSetting
-        }
-
-        val stringSetting = StringSetting.from(key)
-        if (stringSetting != null) {
-            stringSetting.string = value
-            return stringSetting
-        }
-
-        return null
-    }
-
-    /**
-     * Writes the contents of a Section HashMap to disk.
-     *
-     * @param parser  A Wini pointed at a file on disk.
-     * @param section A section containing settings to be written to the file.
-     */
-    private fun writeSection(parser: Wini, section: SettingSection) {
-        // Write the section header.
-        val header = section.name
-
-        // Write this section's values.
-        val settings = section.settings
-        val keySet: Set<String> = settings.keys
-        for (key in keySet) {
-            val setting = settings[key]
-            parser.put(header, setting!!.key, setting.valueAsString)
-        }
-
-        BooleanSetting.values().forEach {
-            if (!keySet.contains(it.key)) {
-                parser.put(header, it.key, it.valueAsString)
-            }
-        }
-        IntSetting.values().forEach {
-            if (!keySet.contains(it.key)) {
-                parser.put(header, it.key, it.valueAsString)
-            }
-        }
-        StringSetting.values().forEach {
-            if (!keySet.contains(it.key)) {
-                parser.put(header, it.key, it.valueAsString)
-            }
-        }
-    }
+    fun getSettingsFile(fileName: String): File =
+        File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
 }
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 956c35c0a8..53f19c4f84 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
@@ -29,6 +29,7 @@ import androidx.fragment.app.Fragment
 import androidx.lifecycle.Lifecycle
 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
@@ -38,6 +39,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.slider.Slider
 import kotlinx.coroutines.Dispatchers
 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
@@ -46,7 +48,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
 import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
 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.ui.SettingsActivity
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -158,7 +159,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                 }
 
                 R.id.menu_settings -> {
-                    SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
+                    val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+                        null,
+                        SettingsFile.FILE_NAME_CONFIG
+                    )
+                    binding.root.findNavController().navigate(action)
                     true
                 }
 
@@ -230,7 +235,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
     override fun onResume() {
         super.onResume()
         if (!DirectoryInitialization.areDirectoriesReady) {
-            DirectoryInitialization.start(requireContext())
+            DirectoryInitialization.start()
         }
 
         updateScreenLayout()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index d5e7934915..cbbe14d220 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -25,17 +25,18 @@ import androidx.core.view.updatePadding
 import androidx.documentfile.provider.DocumentFile
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
+import androidx.navigation.findNavController
 import androidx.navigation.fragment.findNavController
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.transition.MaterialSharedAxis
 import org.yuzu.yuzu_emu.BuildConfig
+import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
 import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
 import org.yuzu.yuzu_emu.features.DocumentProvider
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.model.HomeSetting
 import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -74,7 +75,13 @@ class HomeSettingsFragment : Fragment() {
                     R.string.advanced_settings,
                     R.string.settings_description,
                     R.drawable.ic_settings,
-                    { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
+                    {
+                        val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+                            null,
+                            SettingsFile.FILE_NAME_CONFIG
+                        )
+                        binding.root.findNavController().navigate(action)
+                    }
                 )
             )
             add(
@@ -90,7 +97,13 @@ class HomeSettingsFragment : Fragment() {
                     R.string.preferences_theme,
                     R.string.theme_and_color_description,
                     R.drawable.ic_palette,
-                    { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
+                    {
+                        val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+                            null,
+                            Settings.SECTION_THEME
+                        )
+                        binding.root.findNavController().navigate(action)
+                    }
                 )
             )
             add(
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
new file mode 100644
index 0000000000..d18ec69749
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt
@@ -0,0 +1,235 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.slider.Slider
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
+import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+
+class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener {
+    private var type = 0
+    private var position = 0
+
+    private var defaultCancelListener =
+        DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> closeDialog() }
+
+    private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+    private lateinit var sliderBinding: DialogSliderBinding
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        type = requireArguments().getInt(TYPE)
+        position = requireArguments().getInt(POSITION)
+
+        if (settingsViewModel.clickedItem == null) dismiss()
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        return when (type) {
+            TYPE_RESET_SETTING -> {
+                MaterialAlertDialogBuilder(requireContext())
+                    .setMessage(R.string.reset_setting_confirmation)
+                    .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+                        settingsViewModel.clickedItem!!.setting.reset()
+                        settingsViewModel.setAdapterItemChanged(position)
+                        settingsViewModel.shouldSave = true
+                    }
+                    .setNegativeButton(android.R.string.cancel, null)
+                    .create()
+            }
+
+            SettingsItem.TYPE_SINGLE_CHOICE -> {
+                val item = settingsViewModel.clickedItem as SingleChoiceSetting
+                val value = getSelectionForSingleChoiceValue(item)
+                MaterialAlertDialogBuilder(requireContext())
+                    .setTitle(item.nameId)
+                    .setSingleChoiceItems(item.choicesId, value, this)
+                    .create()
+            }
+
+            SettingsItem.TYPE_SLIDER -> {
+                sliderBinding = DialogSliderBinding.inflate(layoutInflater)
+                val item = settingsViewModel.clickedItem as SliderSetting
+
+                settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units)
+                sliderBinding.slider.apply {
+                    valueFrom = item.min.toFloat()
+                    valueTo = item.max.toFloat()
+                    value = settingsViewModel.sliderProgress.value.toFloat()
+                    addOnChangeListener { _: Slider, value: Float, _: Boolean ->
+                        settingsViewModel.setSliderTextValue(value, item.units)
+                    }
+                }
+
+                MaterialAlertDialogBuilder(requireContext())
+                    .setTitle(item.nameId)
+                    .setView(sliderBinding.root)
+                    .setPositiveButton(android.R.string.ok, this)
+                    .setNegativeButton(android.R.string.cancel, defaultCancelListener)
+                    .create()
+            }
+
+            SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
+                val item = settingsViewModel.clickedItem as StringSingleChoiceSetting
+                MaterialAlertDialogBuilder(requireContext())
+                    .setTitle(item.nameId)
+                    .setSingleChoiceItems(item.choices, item.selectValueIndex, this)
+                    .create()
+            }
+
+            else -> super.onCreateDialog(savedInstanceState)
+        }
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return when (type) {
+            SettingsItem.TYPE_SLIDER -> sliderBinding.root
+            else -> super.onCreateView(inflater, container, savedInstanceState)
+        }
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        when (type) {
+            SettingsItem.TYPE_SLIDER -> {
+                viewLifecycleOwner.lifecycleScope.launch {
+                    repeatOnLifecycle(Lifecycle.State.CREATED) {
+                        settingsViewModel.sliderTextValue.collect {
+                            sliderBinding.textValue.text = it
+                        }
+                    }
+                    repeatOnLifecycle(Lifecycle.State.CREATED) {
+                        settingsViewModel.sliderProgress.collect {
+                            sliderBinding.slider.value = it.toFloat()
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    override fun onClick(dialog: DialogInterface, which: Int) {
+        when (settingsViewModel.clickedItem) {
+            is SingleChoiceSetting -> {
+                val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
+                val value = getValueForSingleChoiceSelection(scSetting, which)
+                if (scSetting.selectedValue != value) {
+                    settingsViewModel.shouldSave = true
+                }
+                scSetting.selectedValue = value
+            }
+
+            is StringSingleChoiceSetting -> {
+                val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
+                val value = scSetting.getValueAt(which)
+                if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
+                scSetting.selectedValue = value
+            }
+
+            is SliderSetting -> {
+                val sliderSetting = settingsViewModel.clickedItem as SliderSetting
+                if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
+                    settingsViewModel.shouldSave = true
+                }
+                sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
+            }
+        }
+        closeDialog()
+    }
+
+    private fun closeDialog() {
+        settingsViewModel.setAdapterItemChanged(position)
+        settingsViewModel.clickedItem = null
+        settingsViewModel.setSliderProgress(-1f)
+        dismiss()
+    }
+
+    private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int {
+        val valuesId = item.valuesId
+        return if (valuesId > 0) {
+            val valuesArray = requireContext().resources.getIntArray(valuesId)
+            valuesArray[which]
+        } else {
+            which
+        }
+    }
+
+    private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int {
+        val value = item.selectedValue
+        val valuesId = item.valuesId
+        if (valuesId > 0) {
+            val valuesArray = requireContext().resources.getIntArray(valuesId)
+            for (index in valuesArray.indices) {
+                val current = valuesArray[index]
+                if (current == value) {
+                    return index
+                }
+            }
+        } else {
+            return value
+        }
+        return -1
+    }
+
+    companion object {
+        const val TAG = "SettingsDialogFragment"
+
+        const val TYPE_RESET_SETTING = -1
+
+        const val TITLE = "Title"
+        const val TYPE = "Type"
+        const val POSITION = "Position"
+
+        fun newInstance(
+            settingsViewModel: SettingsViewModel,
+            clickedItem: SettingsItem,
+            type: Int,
+            position: Int
+        ): SettingsDialogFragment {
+            when (type) {
+                SettingsItem.TYPE_HEADER,
+                SettingsItem.TYPE_SWITCH,
+                SettingsItem.TYPE_SUBMENU,
+                SettingsItem.TYPE_DATETIME_SETTING,
+                SettingsItem.TYPE_RUNNABLE ->
+                    throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!")
+
+                SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress(
+                    (clickedItem as SliderSetting).selectedValue.toFloat()
+                )
+            }
+            settingsViewModel.clickedItem = clickedItem
+
+            val args = Bundle()
+            args.putInt(TYPE, type)
+            args.putInt(POSITION, position)
+            val fragment = SettingsDialogFragment()
+            fragment.arguments = args
+            return fragment
+        }
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
new file mode 100644
index 0000000000..55b6a0367b
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -0,0 +1,184 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
+import androidx.core.widget.doOnTextChanged
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.divider.MaterialDividerItemDecoration
+import com.google.android.material.transition.MaterialSharedAxis
+import info.debatty.java.stringsimilarity.Cosine
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.model.SettingsViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
+
+class SettingsSearchFragment : Fragment() {
+    private var _binding: FragmentSettingsSearchBinding? = null
+    private val binding get() = _binding!!
+
+    private var settingsAdapter: SettingsAdapter? = null
+
+    private val settingsViewModel: SettingsViewModel by activityViewModels()
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
+        returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        _binding = FragmentSettingsSearchBinding.inflate(layoutInflater)
+        return binding.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        settingsViewModel.setIsUsingSearch(true)
+
+        if (savedInstanceState != null) {
+            binding.searchText.setText(savedInstanceState.getString(SEARCH_TEXT))
+        }
+
+        settingsAdapter = SettingsAdapter(this, requireContext())
+
+        val dividerDecoration = MaterialDividerItemDecoration(
+            requireContext(),
+            LinearLayoutManager.VERTICAL
+        )
+        dividerDecoration.isLastItemDecorated = false
+        binding.settingsList.apply {
+            adapter = settingsAdapter
+            layoutManager = LinearLayoutManager(requireContext())
+            addItemDecoration(dividerDecoration)
+        }
+
+        focusSearch()
+
+        binding.backButton.setOnClickListener { settingsViewModel.setShouldNavigateBack(true) }
+        binding.searchBackground.setOnClickListener { focusSearch() }
+        binding.clearButton.setOnClickListener { binding.searchText.setText("") }
+        binding.searchText.doOnTextChanged { _, _, _, _ ->
+            search()
+            binding.settingsList.smoothScrollToPosition(0)
+        }
+        settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
+            if (it) {
+                settingsViewModel.setShouldReloadSettingsList(false)
+                search()
+            }
+        }
+
+        search()
+
+        setInsets()
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        super.onSaveInstanceState(outState)
+        outState.putString(SEARCH_TEXT, binding.searchText.text.toString())
+    }
+
+    private fun search() {
+        val searchTerm = binding.searchText.text.toString().lowercase()
+        binding.clearButton.visibility =
+            if (searchTerm.isEmpty()) View.INVISIBLE else View.VISIBLE
+        if (searchTerm.isEmpty()) {
+            binding.noResultsView.visibility = View.VISIBLE
+            settingsAdapter?.submitList(emptyList())
+            return
+        }
+
+        val baseList = SettingsItem.settingsItems
+        val similarityAlgorithm = if (searchTerm.length > 2) Cosine() else Cosine(1)
+        val sortedList: List<SettingsItem> = baseList.mapNotNull { item ->
+            val title = getString(item.value.nameId).lowercase()
+            val similarity = similarityAlgorithm.similarity(searchTerm, title)
+            if (similarity > 0.08) {
+                Pair(similarity, item)
+            } else {
+                null
+            }
+        }.sortedByDescending { it.first }.mapNotNull {
+            val item = it.second.value
+            val pairedSettingKey = item.setting.pairedSettingKey
+            val optionalSetting: SettingsItem? = if (pairedSettingKey.isNotEmpty()) {
+                val pairedSettingValue = NativeConfig.getBoolean(pairedSettingKey, false)
+                if (pairedSettingValue) it.second.value else null
+            } else {
+                it.second.value
+            }
+            optionalSetting
+        }
+        settingsAdapter?.submitList(sortedList)
+        binding.noResultsView.visibility =
+            if (sortedList.isEmpty()) View.VISIBLE else View.INVISIBLE
+    }
+
+    private fun focusSearch() {
+        binding.searchText.requestFocus()
+        val imm = requireActivity()
+            .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
+        imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
+    }
+
+    private fun setInsets() =
+        ViewCompat.setOnApplyWindowInsetsListener(
+            binding.root
+        ) { _: View, windowInsets: WindowInsetsCompat ->
+            val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
+            val sideMargin = resources.getDimensionPixelSize(R.dimen.spacing_medlarge)
+            val topMargin = resources.getDimensionPixelSize(R.dimen.spacing_chip)
+
+            val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+            val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+
+            val leftInsets = barInsets.left + cutoutInsets.left
+            val rightInsets = barInsets.right + cutoutInsets.right
+
+            binding.settingsList.updatePadding(bottom = barInsets.bottom + extraListSpacing)
+            binding.frameSearch.updatePadding(
+                left = leftInsets + sideMargin,
+                top = barInsets.top + topMargin,
+                right = rightInsets + sideMargin
+            )
+            binding.noResultsView.updatePadding(
+                left = leftInsets,
+                right = rightInsets,
+                bottom = barInsets.bottom
+            )
+
+            val mlpSettingsList = binding.settingsList.layoutParams as ViewGroup.MarginLayoutParams
+            mlpSettingsList.leftMargin = leftInsets + sideMargin
+            mlpSettingsList.rightMargin = rightInsets + sideMargin
+            binding.settingsList.layoutParams = mlpSettingsList
+
+            val mlpDivider = binding.divider.layoutParams as ViewGroup.MarginLayoutParams
+            mlpDivider.leftMargin = leftInsets + sideMargin
+            mlpDivider.rightMargin = rightInsets + sideMargin
+            binding.divider.layoutParams = mlpDivider
+
+            windowInsets
+        }
+
+    companion object {
+        const val SEARCH_TEXT = "SearchText"
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
new file mode 100644
index 0000000000..d16d15fa6a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -0,0 +1,96 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
+
+class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
+    var game: Game? = null
+
+    var shouldSave = false
+
+    var clickedItem: SettingsItem? = null
+
+    private val _toolbarTitle = MutableLiveData("")
+    val toolbarTitle: LiveData<String> get() = _toolbarTitle
+
+    private val _shouldRecreate = MutableLiveData(false)
+    val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
+
+    private val _shouldNavigateBack = MutableLiveData(false)
+    val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
+
+    private val _shouldShowResetSettingsDialog = MutableLiveData(false)
+    val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
+
+    private val _shouldReloadSettingsList = MutableLiveData(false)
+    val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
+
+    private val _isUsingSearch = MutableLiveData(false)
+    val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
+
+    val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
+
+    val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
+
+    val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
+
+    fun setToolbarTitle(value: String) {
+        _toolbarTitle.value = value
+    }
+
+    fun setShouldRecreate(value: Boolean) {
+        _shouldRecreate.value = value
+    }
+
+    fun setShouldNavigateBack(value: Boolean) {
+        _shouldNavigateBack.value = value
+    }
+
+    fun setShouldShowResetSettingsDialog(value: Boolean) {
+        _shouldShowResetSettingsDialog.value = value
+    }
+
+    fun setShouldReloadSettingsList(value: Boolean) {
+        _shouldReloadSettingsList.value = value
+    }
+
+    fun setIsUsingSearch(value: Boolean) {
+        _isUsingSearch.value = value
+    }
+
+    fun setSliderTextValue(value: Float, units: String) {
+        savedStateHandle[KEY_SLIDER_PROGRESS] = value
+        savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
+            YuzuApplication.appContext.getString(R.string.value_with_units),
+            value.toInt().toString(),
+            units
+        )
+    }
+
+    fun setSliderProgress(value: Float) {
+        savedStateHandle[KEY_SLIDER_PROGRESS] = value
+    }
+
+    fun setAdapterItemChanged(value: Int) {
+        savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
+    }
+
+    fun clear() {
+        game = null
+        shouldSave = false
+    }
+
+    companion object {
+        const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
+        const val KEY_SLIDER_PROGRESS = "SliderProgress"
+        const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index aaf3a0ec1e..7735452e51 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -33,14 +33,13 @@ import java.io.IOException
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.activities.EmulationActivity
 import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
 import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
 import org.yuzu.yuzu_emu.features.settings.model.Settings
-import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
-import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
 import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
 import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
@@ -54,7 +53,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
 
     private val homeViewModel: HomeViewModel by viewModels()
     private val gamesViewModel: GamesViewModel by viewModels()
-    private val settingsViewModel: SettingsViewModel by viewModels()
 
     override var themeId: Int = 0
 
@@ -62,8 +60,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
         val splashScreen = installSplashScreen()
         splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
 
-        settingsViewModel.settings.loadSettings()
-
         ThemeHelper.setTheme(this)
 
         super.onCreate(savedInstanceState)
@@ -109,11 +105,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
             when (it.itemId) {
                 R.id.gamesFragment -> gamesViewModel.setShouldScrollToTop(true)
                 R.id.searchFragment -> gamesViewModel.setSearchFocused(true)
-                R.id.homeSettingsFragment -> SettingsActivity.launch(
-                    this,
-                    SettingsFile.FILE_NAME_CONFIG,
-                    ""
-                )
+                R.id.homeSettingsFragment -> {
+                    val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+                        null,
+                        SettingsFile.FILE_NAME_CONFIG
+                    )
+                    navHostFragment.navController.navigate(action)
+                }
             }
         }
 
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
deleted file mode 100644
index 9cfda74ee0..0000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.yuzu.yuzu_emu.utils
-
-class BiMap<K, V> {
-    private val forward: MutableMap<K, V> = HashMap()
-    private val backward: MutableMap<V, K> = HashMap()
-
-    @Synchronized
-    fun add(key: K, value: V) {
-        forward[key] = value
-        backward[value] = key
-    }
-
-    @Synchronized
-    fun getForward(key: K): V? {
-        return forward[key]
-    }
-
-    @Synchronized
-    fun getBackward(key: V): K? {
-        return backward[key]
-    }
-}
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 2ee63697eb..3c9f6bad0f 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,18 +3,18 @@
 
 package org.yuzu.yuzu_emu.utils
 
-import android.content.Context
 import java.io.IOException
 import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.YuzuApplication
 
 object DirectoryInitialization {
     private var userPath: String? = null
 
     var areDirectoriesReady: Boolean = false
 
-    fun start(context: Context) {
+    fun start() {
         if (!areDirectoriesReady) {
-            initializeInternalStorage(context)
+            initializeInternalStorage()
             NativeLibrary.initializeEmulation()
             areDirectoriesReady = true
         }
@@ -26,9 +26,9 @@ object DirectoryInitialization {
             return userPath
         }
 
-    private fun initializeInternalStorage(context: Context) {
+    private fun initializeInternalStorage() {
         try {
-            userPath = context.getExternalFilesDir(null)!!.canonicalPath
+            userPath = YuzuApplication.appContext.getExternalFilesDir(null)!!.canonicalPath
             NativeLibrary.setAppDirectory(userPath!!)
         } catch (e: IOException) {
             e.printStackTrace()
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
new file mode 100644
index 0000000000..9425f8b991
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+object NativeConfig {
+    external fun getBoolean(key: String, getDefault: Boolean): Boolean
+    external fun setBoolean(key: String, value: Boolean)
+
+    external fun getByte(key: String, getDefault: Boolean): Byte
+    external fun setByte(key: String, value: Byte)
+
+    external fun getShort(key: String, getDefault: Boolean): Short
+    external fun setShort(key: String, value: Short)
+
+    external fun getInt(key: String, getDefault: Boolean): Int
+    external fun setInt(key: String, value: Int)
+
+    external fun getFloat(key: String, getDefault: Boolean): Float
+    external fun setFloat(key: String, value: Float)
+
+    external fun getLong(key: String, getDefault: Boolean): Long
+    external fun setLong(key: String, value: Long)
+
+    external fun getString(key: String, getDefault: Boolean): String
+    external fun setString(key: String, value: String)
+
+    external fun getIsRuntimeModifiable(key: String): Boolean
+
+    external fun getConfigHeader(category: Int): String
+
+    external fun getPairedSettingKey(key: String): String
+}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index e2ed08e9fc..e15d1480b3 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -14,6 +14,8 @@ add_library(yuzu-android SHARED
     id_cache.cpp
     id_cache.h
     native.cpp
+    native_config.cpp
+    uisettings.cpp
 )
 
 set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 9de9bd93e2..34b425cb45 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -16,18 +16,20 @@
 #include "input_common/main.h"
 #include "jni/config.h"
 #include "jni/default_ini.h"
+#include "uisettings.h"
 
 namespace FS = Common::FS;
 
-Config::Config(std::optional<std::filesystem::path> config_path)
-    : config_loc{config_path.value_or(FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "config.ini")},
-      config{std::make_unique<INIReader>(FS::PathToUTF8String(config_loc))} {
-    Reload();
+Config::Config(const std::string& config_name, ConfigType config_type)
+    : type(config_type), global{config_type == ConfigType::GlobalConfig} {
+    Initialize(config_name);
 }
 
 Config::~Config() = default;
 
 bool Config::LoadINI(const std::string& default_contents, bool retry) {
+    void(FS::CreateParentDir(config_loc));
+    config = std::make_unique<INIReader>(FS::PathToUTF8String(config_loc));
     const auto config_loc_str = FS::PathToUTF8String(config_loc);
     if (config->ParseError() < 0) {
         if (retry) {
@@ -301,9 +303,28 @@ void Config::ReadValues() {
 
     // Network
     ReadSetting("Network", Settings::values.network_interface);
+
+    // Android
+    ReadSetting("Android", AndroidSettings::values.picture_in_picture);
+    ReadSetting("Android", AndroidSettings::values.screen_layout);
 }
 
-void Config::Reload() {
+void Config::Initialize(const std::string& config_name) {
+    const auto fs_config_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir);
+    const auto config_file = fmt::format("{}.ini", config_name);
+
+    switch (type) {
+    case ConfigType::GlobalConfig:
+        config_loc = FS::PathToUTF8String(fs_config_loc / config_file);
+        break;
+    case ConfigType::PerGameConfig:
+        config_loc = FS::PathToUTF8String(fs_config_loc / "custom" / FS::ToU8String(config_file));
+        break;
+    case ConfigType::InputProfile:
+        config_loc = FS::PathToUTF8String(fs_config_loc / "input" / config_file);
+        LoadINI(DefaultINI::android_config_file);
+        return;
+    }
     LoadINI(DefaultINI::android_config_file);
     ReadValues();
 }
diff --git a/src/android/app/src/main/jni/config.h b/src/android/app/src/main/jni/config.h
index 0d7d6e94df..e1e8f47ed2 100644
--- a/src/android/app/src/main/jni/config.h
+++ b/src/android/app/src/main/jni/config.h
@@ -13,25 +13,35 @@
 class INIReader;
 
 class Config {
-    std::filesystem::path config_loc;
-    std::unique_ptr<INIReader> config;
-
     bool LoadINI(const std::string& default_contents = "", bool retry = true);
-    void ReadValues();
 
 public:
-    explicit Config(std::optional<std::filesystem::path> config_path = std::nullopt);
+    enum class ConfigType {
+        GlobalConfig,
+        PerGameConfig,
+        InputProfile,
+    };
+
+    explicit Config(const std::string& config_name = "config",
+                    ConfigType config_type = ConfigType::GlobalConfig);
     ~Config();
 
-    void Reload();
+    void Initialize(const std::string& config_name);
 
 private:
     /**
-     * Applies a value read from the sdl2_config to a Setting.
+     * Applies a value read from the config to a Setting.
      *
      * @param group The name of the INI group
      * @param setting The yuzu setting to modify
      */
     template <typename Type, bool ranged>
     void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
+
+    void ReadValues();
+
+    const ConfigType type;
+    std::unique_ptr<INIReader> config;
+    std::string config_loc;
+    const bool global;
 };
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 7e17833a0c..b2adfdedaa 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -824,34 +824,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadSettings(JNIEnv* env, jclass cl
     Config{};
 }
 
-jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserSetting(JNIEnv* env, jclass clazz,
-                                                             jstring j_game_id, jstring j_section,
-                                                             jstring j_key) {
-    std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
-    std::string_view section = env->GetStringUTFChars(j_section, 0);
-    std::string_view key = env->GetStringUTFChars(j_key, 0);
-
-    env->ReleaseStringUTFChars(j_game_id, game_id.data());
-    env->ReleaseStringUTFChars(j_section, section.data());
-    env->ReleaseStringUTFChars(j_key, key.data());
-
-    return env->NewStringUTF("");
-}
-
-void Java_org_yuzu_yuzu_1emu_NativeLibrary_setUserSetting(JNIEnv* env, jclass clazz,
-                                                          jstring j_game_id, jstring j_section,
-                                                          jstring j_key, jstring j_value) {
-    std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
-    std::string_view section = env->GetStringUTFChars(j_section, 0);
-    std::string_view key = env->GetStringUTFChars(j_key, 0);
-    std::string_view value = env->GetStringUTFChars(j_value, 0);
-
-    env->ReleaseStringUTFChars(j_game_id, game_id.data());
-    env->ReleaseStringUTFChars(j_section, section.data());
-    env->ReleaseStringUTFChars(j_key, key.data());
-    env->ReleaseStringUTFChars(j_value, value.data());
-}
-
 void Java_org_yuzu_yuzu_1emu_NativeLibrary_initGameIni(JNIEnv* env, jclass clazz,
                                                        jstring j_game_id) {
     std::string_view game_id = env->GetStringUTFChars(j_game_id, 0);
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
new file mode 100644
index 0000000000..8a704960cd
--- /dev/null
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -0,0 +1,237 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <string>
+
+#include <jni.h>
+
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "jni/android_common/android_common.h"
+#include "jni/config.h"
+#include "uisettings.h"
+
+template <typename T>
+Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
+    auto key = GetJString(env, jkey);
+    auto basicSetting = Settings::values.linkage.by_key[key];
+    auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
+    if (basicSetting != 0) {
+        return static_cast<Settings::Setting<T>*>(basicSetting);
+    }
+    if (basicAndroidSetting != 0) {
+        return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
+    }
+    LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
+    return nullptr;
+}
+
+extern "C" {
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
+                                                               jstring jkey, jboolean getDefault) {
+    auto setting = getSetting<bool>(env, jkey);
+    if (setting == nullptr) {
+        return false;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey,
+                                                           jboolean value) {
+    auto setting = getSetting<bool>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(static_cast<bool>(value));
+}
+
+jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey,
+                                                         jboolean getDefault) {
+    auto setting = getSetting<u8>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey,
+                                                        jbyte value) {
+    auto setting = getSetting<u8>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey,
+                                                           jboolean getDefault) {
+    auto setting = getSetting<u16>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey,
+                                                         jshort value) {
+    auto setting = getSetting<u16>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
+                                                       jboolean getDefault) {
+    auto setting = getSetting<int>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey,
+                                                       jint value) {
+    auto setting = getSetting<int>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey,
+                                                           jboolean getDefault) {
+    auto setting = getSetting<float>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey,
+                                                         jfloat value) {
+    auto setting = getSetting<float>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey,
+                                                         jboolean getDefault) {
+    auto setting = getSetting<long>(env, jkey);
+    if (setting == nullptr) {
+        return -1;
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return setting->GetDefault();
+    }
+
+    return setting->GetValue();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey,
+                                                        jlong value) {
+    auto setting = getSetting<long>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+    setting->SetGlobal(true);
+    setting->SetValue(value);
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey,
+                                                             jboolean getDefault) {
+    auto setting = getSetting<std::string>(env, jkey);
+    if (setting == nullptr) {
+        return ToJString(env, "");
+    }
+    setting->SetGlobal(true);
+
+    if (static_cast<bool>(getDefault)) {
+        return ToJString(env, setting->GetDefault());
+    }
+
+    return ToJString(env, setting->GetValue());
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey,
+                                                          jstring value) {
+    auto setting = getSetting<std::string>(env, jkey);
+    if (setting == nullptr) {
+        return;
+    }
+
+    setting->SetGlobal(true);
+    setting->SetValue(GetJString(env, value));
+}
+
+jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj,
+                                                                           jstring jkey) {
+    auto key = GetJString(env, jkey);
+    auto setting = Settings::values.linkage.by_key[key];
+    if (setting != 0) {
+        return setting->RuntimeModfiable();
+    }
+    LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
+    return true;
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj,
+                                                                   jint jcategory) {
+    auto category = static_cast<Settings::Category>(jcategory);
+    return ToJString(env, Settings::TranslateCategory(category));
+}
+
+jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj,
+                                                                       jstring jkey) {
+    auto setting = getSetting<std::string>(env, jkey);
+    if (setting == nullptr) {
+        return ToJString(env, "");
+    }
+    if (setting->PairedSetting() == nullptr) {
+        return ToJString(env, "");
+    }
+
+    return ToJString(env, setting->PairedSetting()->GetLabel());
+}
+
+} // extern "C"
diff --git a/src/android/app/src/main/jni/uisettings.cpp b/src/android/app/src/main/jni/uisettings.cpp
new file mode 100644
index 0000000000..f2f0bad50b
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.cpp
@@ -0,0 +1,10 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "uisettings.h"
+
+namespace AndroidSettings {
+
+Values values;
+
+} // namespace AndroidSettings
diff --git a/src/android/app/src/main/jni/uisettings.h b/src/android/app/src/main/jni/uisettings.h
new file mode 100644
index 0000000000..494654af75
--- /dev/null
+++ b/src/android/app/src/main/jni/uisettings.h
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <common/settings_common.h>
+#include "common/common_types.h"
+#include "common/settings_setting.h"
+
+namespace AndroidSettings {
+
+struct Values {
+    Settings::Linkage linkage;
+
+    // Android
+    Settings::Setting<bool> picture_in_picture{linkage, true, "picture_in_picture",
+                                               Settings::Category::Android};
+    Settings::Setting<s32> screen_layout{linkage,
+                                         5,
+                                         "screen_layout",
+                                         Settings::Category::Android,
+                                         Settings::Specialization::Default,
+                                         true,
+                                         true};
+};
+
+extern Values values;
+
+} // namespace AndroidSettings
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
deleted file mode 100644
index 9f49c133a0..0000000000
--- a/src/android/app/src/main/res/anim-ldrtl/anim_pop_settings_fragment_out.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="125"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="1"
-        android:toAlpha="0" />
-
-    <translate
-        android:duration="125"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromXDelta="0"
-        android:toXDelta="-75" />
-
-</set>
diff --git a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
deleted file mode 100644
index 82fd719dba..0000000000
--- a/src/android/app/src/main/res/anim-ldrtl/anim_settings_fragment_in.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="0"
-        android:toAlpha="1" />
-
-    <translate
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromXDelta="-200"
-        android:toXDelta="0" />
-
-</set>
diff --git a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
deleted file mode 100644
index 5892128f18..0000000000
--- a/src/android/app/src/main/res/anim/anim_pop_settings_fragment_out.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="125"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="1"
-        android:toAlpha="0" />
-
-    <translate
-        android:duration="125"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromXDelta="0"
-        android:toXDelta="75" />
-
-</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
deleted file mode 100644
index 98e0cf8bd6..0000000000
--- a/src/android/app/src/main/res/anim/anim_settings_fragment_in.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="0"
-        android:toAlpha="1" />
-
-    <translate
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromXDelta="200"
-        android:toXDelta="0" />
-
-</set>
diff --git a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml b/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
deleted file mode 100644
index 77a40a4d1b..0000000000
--- a/src/android/app/src/main/res/anim/anim_settings_fragment_out.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha
-        android:duration="@android:integer/config_shortAnimTime"
-        android:interpolator="@android:anim/decelerate_interpolator"
-        android:fromAlpha="1"
-        android:toAlpha="0" />
-
-</set>
diff --git a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml b/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
deleted file mode 100644
index 4612aee134..0000000000
--- a/src/android/app/src/main/res/animator/menu_slide_in_from_start.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <objectAnimator
-        android:propertyName="translationX"
-        android:valueType="floatType"
-        android:valueFrom="-1280dp"
-        android:valueTo="0"
-        android:interpolator="@android:interpolator/decelerate_quad"
-        android:duration="300"/>
-
-    <objectAnimator
-        android:propertyName="alpha"
-        android:valueType="floatType"
-        android:valueFrom="0"
-        android:valueTo="1"
-        android:interpolator="@android:interpolator/accelerate_quad"
-        android:duration="300"/>
-
-</set>
\ No newline at end of file
diff --git a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml b/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
deleted file mode 100644
index c004789466..0000000000
--- a/src/android/app/src/main/res/animator/menu_slide_out_to_start.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <!-- This animation is used ONLY when a submenu is replaced. -->
-    <objectAnimator
-        android:propertyName="translationX"
-        android:valueType="floatType"
-        android:valueFrom="0"
-        android:valueTo="-1280dp"
-        android:interpolator="@android:interpolator/decelerate_quad"
-        android:duration="200"/>
-
-    <objectAnimator
-        android:propertyName="alpha"
-        android:valueType="floatType"
-        android:valueFrom="1"
-        android:valueTo="0"
-        android:interpolator="@android:interpolator/decelerate_quad"
-        android:duration="200"/>
-
-</set>
\ No newline at end of file
diff --git a/src/android/app/src/main/res/layout/activity_settings.xml b/src/android/app/src/main/res/layout/activity_settings.xml
index 14ae83b041..8a026a30ac 100644
--- a/src/android/app/src/main/res/layout/activity_settings.xml
+++ b/src/android/app/src/main/res/layout/activity_settings.xml
@@ -1,42 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout
-    android:id="@+id/coordinator_main"
+<androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/constraint_settings"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="?attr/colorSurface">
 
-    <com.google.android.material.appbar.AppBarLayout
-        android:id="@+id/appbar_settings"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:fitsSystemWindows="true"
-        app:elevation="0dp">
-
-        <com.google.android.material.appbar.CollapsingToolbarLayout
-            style="?attr/collapsingToolbarLayoutMediumStyle"
-            android:id="@+id/toolbar_settings_layout"
-            android:layout_width="match_parent"
-            android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
-            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
-
-            <com.google.android.material.appbar.MaterialToolbar
-                android:id="@+id/toolbar_settings"
-                android:layout_width="match_parent"
-                android:layout_height="?attr/actionBarSize"
-                app:layout_collapseMode="pin" />
-
-        </com.google.android.material.appbar.CollapsingToolbarLayout>
-
-    </com.google.android.material.appbar.AppBarLayout>
-
-    <FrameLayout
-        android:id="@+id/frame_content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginHorizontal="12dp"
-        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/fragment_container"
+        android:name="androidx.navigation.fragment.NavHostFragment"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:defaultNavHost="true"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:layout="@layout/fragment_settings" />
 
     <View
         android:id="@+id/navigation_bar_shade"
@@ -45,6 +27,8 @@
         android:background="@android:color/transparent"
         android:clickable="false"
         android:focusable="false"
-        android:layout_gravity="bottom|center_horizontal" />
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
 
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_settings.xml b/src/android/app/src/main/res/layout/fragment_settings.xml
index 1677203476..ebedbf1ec4 100644
--- a/src/android/app/src/main/res/layout/fragment_settings.xml
+++ b/src/android/app/src/main/res/layout/fragment_settings.xml
@@ -1,14 +1,41 @@
 <?xml version="1.0" encoding="utf-8"?>
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/coordinator_main"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:background="?attr/colorSurface">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/appbar_settings"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fitsSystemWindows="true"
+        app:elevation="0dp">
+
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            android:id="@+id/toolbar_settings_layout"
+            style="?attr/collapsingToolbarLayoutMediumStyle"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
+
+            <com.google.android.material.appbar.MaterialToolbar
+                android:id="@+id/toolbar_settings"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                app:layout_collapseMode="pin"
+                app:navigationIcon="@drawable/ic_back" />
+
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+
+    </com.google.android.material.appbar.AppBarLayout>
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/list_settings"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="?attr/colorSurface"
-        android:clipToPadding="false" />
+        android:clipToPadding="false"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
 
-</FrameLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_settings_search.xml b/src/android/app/src/main/res/layout/fragment_settings_search.xml
new file mode 100644
index 0000000000..c779ed2fcd
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_settings_search.xml
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <RelativeLayout
+        android:id="@+id/relativeLayout"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/divider">
+
+        <LinearLayout
+            android:id="@+id/no_results_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="vertical">
+
+            <ImageView
+                android:id="@+id/icon_no_results"
+                android:layout_width="match_parent"
+                android:layout_height="80dp"
+                android:src="@drawable/ic_search" />
+
+            <com.google.android.material.textview.MaterialTextView
+                android:id="@+id/notice_text"
+                style="@style/TextAppearance.Material3.TitleLarge"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center"
+                android:paddingTop="8dp"
+                android:text="@string/search_settings"
+                tools:visibility="visible" />
+
+        </LinearLayout>
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/settings_list"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipToPadding="false" />
+
+    </RelativeLayout>
+
+    <FrameLayout
+        android:id="@+id/frame_search"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:clipToPadding="false"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <com.google.android.material.card.MaterialCardView
+            android:id="@+id/search_background"
+            style="?attr/materialCardViewFilledStyle"
+            android:layout_width="match_parent"
+            android:layout_height="56dp"
+            app:cardCornerRadius="28dp">
+
+            <LinearLayout
+                android:id="@+id/search_container"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_marginEnd="56dp"
+                android:orientation="horizontal">
+
+                <Button
+                    android:id="@+id/back_button"
+                    style="?attr/materialIconButtonFilledTonalStyle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:layout_marginStart="8dp"
+                    app:backgroundTint="@android:color/transparent"
+                    app:icon="@drawable/ic_back" />
+
+                <EditText
+                    android:id="@+id/search_text"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:background="@android:color/transparent"
+                    android:hint="@string/search_settings"
+                    android:imeOptions="flagNoFullscreen"
+                    android:inputType="text"
+                    android:maxLines="1" />
+
+            </LinearLayout>
+
+            <Button
+                android:id="@+id/clear_button"
+                style="?attr/materialIconButtonFilledTonalStyle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical|end"
+                android:layout_marginEnd="8dp"
+                android:visibility="invisible"
+                app:backgroundTint="@android:color/transparent"
+                app:icon="@drawable/ic_clear"
+                tools:visibility="visible" />
+
+        </com.google.android.material.card.MaterialCardView>
+
+    </FrameLayout>
+
+    <com.google.android.material.divider.MaterialDivider
+        android:id="@+id/divider"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="20dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/frame_search" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/menu/menu_settings.xml b/src/android/app/src/main/res/menu/menu_settings.xml
index 1fe7aa6d4c..21501a471f 100644
--- a/src/android/app/src/main/res/menu/menu_settings.xml
+++ b/src/android/app/src/main/res/menu/menu_settings.xml
@@ -1,2 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<menu />
\ No newline at end of file
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/action_search"
+        android:icon="@drawable/ic_search"
+        android:title="@string/home_search"
+        app:showAsAction="always" />
+
+</menu>
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml
index cd1d36a12a..c7be37f9b2 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -17,4 +17,21 @@
             android:defaultValue="@null" />
     </fragment>
 
+    <activity
+        android:id="@+id/settingsActivity"
+        android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
+        android:label="SettingsActivity">
+        <argument
+            android:name="game"
+            app:argType="org.yuzu.yuzu_emu.model.Game"
+            app:nullable="true" />
+        <argument
+            android:name="menuTag"
+            app:argType="string" />
+    </activity>
+
+    <action
+        android:id="@+id/action_global_settingsActivity"
+        app:destination="@id/settingsActivity" />
+
 </navigation>
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 42f987fed4..2085430bf8 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -72,4 +72,21 @@
         app:destination="@id/emulationActivity"
         app:launchSingleTop="true" />
 
+    <activity
+        android:id="@+id/settingsActivity"
+        android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity"
+        android:label="SettingsActivity">
+        <argument
+            android:name="game"
+            app:argType="org.yuzu.yuzu_emu.model.Game"
+            app:nullable="true" />
+        <argument
+            android:name="menuTag"
+            app:argType="string" />
+    </activity>
+
+    <action
+        android:id="@+id/action_global_settingsActivity"
+        app:destination="@id/settingsActivity" />
+
 </navigation>
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
new file mode 100644
index 0000000000..88e1b45877
--- /dev/null
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/settings_navigation"
+    app:startDestination="@id/settingsFragment">
+
+    <fragment
+        android:id="@+id/settingsFragment"
+        android:name="org.yuzu.yuzu_emu.features.settings.ui.SettingsFragment"
+        android:label="SettingsFragment">
+        <argument
+            android:name="menuTag"
+            app:argType="string" />
+        <argument
+            android:name="game"
+            app:argType="org.yuzu.yuzu_emu.model.Game"
+            app:nullable="true" />
+        <action
+            android:id="@+id/action_settingsFragment_to_settingsSearchFragment"
+            app:destination="@id/settingsSearchFragment" />
+    </fragment>
+
+    <action
+        android:id="@+id/action_global_settingsFragment"
+        app:destination="@id/settingsFragment" />
+
+    <fragment
+        android:id="@+id/settingsSearchFragment"
+        android:name="org.yuzu.yuzu_emu.fragments.SettingsSearchFragment"
+        android:label="SettingsSearchFragment" />
+
+</navigation>
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 200b99185f..dc10159c9b 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -243,10 +243,10 @@
         <item>@string/cubeb</item>
         <item>@string/string_null</item>
     </string-array>
-    <string-array name="outputEngineValues">
-        <item>auto</item>
-        <item>cubeb</item>
-        <item>null</item>
-    </string-array>
+    <integer-array name="outputEngineValues">
+        <item>0</item>
+        <item>1</item>
+        <item>3</item>
+    </integer-array>
 
 </resources>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index de1b2909b5..d43891cecf 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -43,6 +43,7 @@
     <string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
     <string name="add_games_warning_help">https://yuzu-emu.org/help/quickstart/#dumping-games</string>
     <string name="home_search_games">Search games</string>
+    <string name="search_settings">Search settings</string>
     <string name="games_dir_selected">Games directory selected</string>
     <string name="install_prod_keys">Install prod.keys</string>
     <string name="install_prod_keys_description">Required to decrypt retail games</string>
@@ -74,6 +75,7 @@
     <string name="install_gpu_driver">Install GPU driver</string>
     <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
     <string name="advanced_settings">Advanced settings</string>
+    <string name="advanced_settings_game">Advanced settings: %1$s</string>
     <string name="settings_description">Configure emulator settings</string>
     <string name="search_recently_played">Recently played</string>
     <string name="search_recently_added">Recently added</string>
@@ -200,6 +202,7 @@
     <string name="ini_saved">Saved settings</string>
     <string name="gameid_saved">Saved settings for %1$s</string>
     <string name="error_saving">Error saving %1$s.ini: %2$s</string>
+    <string name="unimplemented_menu">Unimplemented Menu</string>
     <string name="loading">Loading…</string>
     <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
     <string name="reset_to_default">Reset to default</string>
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 5240568417..4ecaf550b1 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -159,6 +159,8 @@ float Volume() {
 
 const char* TranslateCategory(Category category) {
     switch (category) {
+    case Category::Android:
+        return "Android";
     case Category::Audio:
         return "Audio";
     case Category::Core:
diff --git a/src/common/settings_common.cpp b/src/common/settings_common.cpp
index 137b65d5f7..5960b78aa5 100644
--- a/src/common/settings_common.cpp
+++ b/src/common/settings_common.cpp
@@ -14,6 +14,7 @@ BasicSetting::BasicSetting(Linkage& linkage, const std::string& name, enum Categ
     : label{name}, category{category_}, id{linkage.count}, save{save_},
       runtime_modifiable{runtime_modifiable_}, specialization{specialization_},
       other_setting{other_setting_} {
+    linkage.by_key.insert({name, this});
     linkage.by_category[category].push_back(this);
     linkage.count++;
 }
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 3082e0ce1a..5b170dfd5d 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -12,6 +12,7 @@
 namespace Settings {
 
 enum class Category : u32 {
+    Android,
     Audio,
     Core,
     Cpu,
@@ -68,6 +69,7 @@ public:
     explicit Linkage(u32 initial_count = 0);
     ~Linkage();
     std::map<Category, std::vector<BasicSetting*>> by_category{};
+    std::map<std::string, Settings::BasicSetting*> by_key{};
     std::vector<std::function<void()>> restore_functions{};
     u32 count;
 };