From 6c8f2b355ace41e33e8a1ad2f95d2816893a953b Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Wed, 16 Aug 2023 02:36:56 -0400
Subject: [PATCH] android: Expose interface for getting settings from native
 code

Completely removes code related to parsing the settings file on the java side. Now all settings are accessed via NativeConfig.kt and config.cpp has been modified to be closer to the core counterpart. Since the core currently uses QSettings, we can't remove reliance from Wini yet. This also includes simplifications to each settings interface to get closer to native code and prepare for per-game settings.
---
 .../java/org/yuzu/yuzu_emu/NativeLibrary.kt   |   8 +-
 .../yuzu_emu/activities/EmulationActivity.kt  |   6 -
 .../settings/model/AbstractBooleanSetting.kt  |   4 +-
 ...ngsViewModel.kt => AbstractByteSetting.kt} |   6 +-
 .../settings/model/AbstractFloatSetting.kt    |   4 +-
 .../settings/model/AbstractIntSetting.kt      |   4 +-
 .../settings/model/AbstractLongSetting.kt     |  10 +
 .../settings/model/AbstractSetting.kt         |  13 +-
 .../settings/model/AbstractShortSetting.kt    |  10 +
 .../settings/model/AbstractStringSetting.kt   |   4 +-
 .../features/settings/model/BooleanSetting.kt |  53 ++-
 .../features/settings/model/ByteSetting.kt    |  25 ++
 .../features/settings/model/FloatSetting.kt   |  32 +-
 .../features/settings/model/IntSetting.kt     | 149 ++-------
 .../features/settings/model/LongSetting.kt    |  25 ++
 .../features/settings/model/SettingSection.kt |  37 --
 .../features/settings/model/Settings.kt       | 316 ++++++++----------
 .../features/settings/model/ShortSetting.kt   |  25 ++
 .../features/settings/model/StringSetting.kt  |  36 +-
 .../settings/model/view/DateTimeSetting.kt    |  18 +-
 .../settings/model/view/SettingsItem.kt       |   2 +-
 .../model/view/SingleChoiceSetting.kt         |   2 +-
 .../settings/model/view/SliderSetting.kt      |  24 +-
 .../model/view/StringSingleChoiceSetting.kt   |   2 +-
 .../settings/model/view/SwitchSetting.kt      |   4 +-
 .../features/settings/ui/SettingsActivity.kt  |  30 +-
 .../settings/ui/SettingsActivityPresenter.kt  |  15 +-
 .../settings/ui/SettingsActivityView.kt       |  19 --
 .../features/settings/ui/SettingsAdapter.kt   |  62 ++--
 .../features/settings/ui/SettingsFragment.kt  |   9 -
 .../settings/ui/SettingsFragmentPresenter.kt  | 181 +++++-----
 .../settings/ui/SettingsFragmentView.kt       |  16 -
 .../ui/viewholder/DateTimeViewHolder.kt       |   2 +-
 .../features/settings/utils/SettingsFile.kt   | 255 ++------------
 .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt |   4 -
 .../java/org/yuzu/yuzu_emu/utils/BiMap.kt     |  25 --
 .../org/yuzu/yuzu_emu/utils/NativeConfig.kt   |  31 ++
 src/android/app/src/main/jni/CMakeLists.txt   |   2 +
 src/android/app/src/main/jni/config.cpp       |  31 +-
 src/android/app/src/main/jni/config.h         |  24 +-
 src/android/app/src/main/jni/native.cpp       |  28 --
 .../app/src/main/jni/native_config.cpp        | 224 +++++++++++++
 src/android/app/src/main/jni/uisettings.cpp   |  10 +
 src/android/app/src/main/jni/uisettings.h     |  29 ++
 .../app/src/main/res/values/arrays.xml        |  10 +-
 .../app/src/main/res/values/strings.xml       |   1 +
 src/common/settings.cpp                       |   2 +
 src/common/settings_common.cpp                |   1 +
 src/common/settings_common.h                  |   2 +
 49 files changed, 866 insertions(+), 966 deletions(-)
 rename src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/{SettingsViewModel.kt => AbstractByteSetting.kt} (59%)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt
 delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/SettingSection.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt
 delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/BiMap.kt
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
 create mode 100644 src/android/app/src/main/jni/native_config.cpp
 create mode 100644 src/android/app/src/main/jni/uisettings.cpp
 create mode 100644 src/android/app/src/main/jni/uisettings.h

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 9c32e044c..5a7cf4ed7 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/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 7461fb093..6f52a7a8d 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)
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 a6e9833ee..aeda8d222 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 bd9233d62..606519ad8 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 6fe4bc263..974925eed 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 892b7dcfe..89b285b10 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 000000000..4873942db
--- /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 258580209..7afed95ad 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,17 @@
 
 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 category: Settings.Category
     val defaultValue: Any
+    val valueAsString: String
+        get() = ""
+
+    val isRuntimeModifiable: Boolean
+        get() = NativeConfig.getIsRuntimeModifiable(key!!)
+
+    fun reset() = run { }
 }
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 000000000..91407ccbb
--- /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 0d02c5997..c8935cc48 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 d41933766..f7528642e 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,34 @@
 
 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
 ) : 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),
+    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),
+    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 { 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 000000000..6ec0a765e
--- /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 e5545a916..0181d06f2 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 4427a7d9d..a64757207 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,34 @@
 
 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
 ) : 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),
+    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 { 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 000000000..c526fc4cf
--- /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 474f598a9..000000000
--- 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 a6251bafd..0702236e8 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 000000000..c9a0c664c
--- /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 6621289fd..9bb3e66d4 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 bc0bf7788..7c858916e 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,29 @@
 
 package org.yuzu.yuzu_emu.features.settings.model.view
 
+import org.yuzu.yuzu_emu.features.settings.model.AbstractLongSetting
 import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
-import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
 
 class DateTimeSetting(
     setting: AbstractSetting?,
     titleId: Int,
     descriptionId: Int,
     val key: String? = null,
-    private val defaultValue: String? = null
+    private val defaultValue: Long? = null
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_DATETIME_SETTING
 
-    val value: String
+    val value: Long
         get() = if (setting != null) {
-            val setting = setting as AbstractStringSetting
-            setting.string
+            val setting = setting as AbstractLongSetting
+            setting.long
         } else {
             defaultValue!!
         }
 
-    fun setSelectedValue(datetime: String): AbstractStringSetting {
-        val stringSetting = setting as AbstractStringSetting
-        stringSetting.string = datetime
-        return stringSetting
+    fun setSelectedValue(datetime: Long): AbstractLongSetting {
+        val longSetting = setting as AbstractLongSetting
+        longSetting.setLong(datetime)
+        return longSetting
     }
 }
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 07520849e..a6cba977c 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
@@ -23,7 +23,7 @@ abstract class SettingsItem(
     val isEditable: Boolean
         get() {
             if (!NativeLibrary.isRunning()) return true
-            return setting?.isRuntimeEditable ?: false
+            return setting?.isRuntimeModifiable ?: false
         }
 
     companion object {
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 7306ec458..b6a8c4612 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
@@ -33,7 +33,7 @@ class SingleChoiceSetting(
      */
     fun setSelectedValue(selection: Int): AbstractIntSetting {
         val intSetting = setting as AbstractIntSetting
-        intSetting.int = selection
+        intSetting.setInt(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 92d0167ae..e71a29e35 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,10 +3,12 @@
 
 package org.yuzu.yuzu_emu.features.settings.model.view
 
+import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting
 import kotlin.math.roundToInt
 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.AbstractShortSetting
 import org.yuzu.yuzu_emu.utils.Log
 
 class SliderSetting(
@@ -17,14 +19,16 @@ class SliderSetting(
     val max: Int,
     val units: String,
     val key: String? = null,
-    val defaultValue: Int? = null
+    val defaultValue: Any? = null
 ) : SettingsItem(setting, titleId, descriptionId) {
     override val type = TYPE_SLIDER
 
-    val selectedValue: Int
+    val selectedValue: Any
         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 -> {
@@ -43,7 +47,7 @@ class SliderSetting(
      */
     fun setSelectedValue(selection: Int): AbstractIntSetting {
         val intSetting = setting as AbstractIntSetting
-        intSetting.int = selection
+        intSetting.setInt(selection)
         return intSetting
     }
 
@@ -56,7 +60,19 @@ class SliderSetting(
      */
     fun setSelectedValue(selection: Float): AbstractFloatSetting {
         val floatSetting = setting as AbstractFloatSetting
-        floatSetting.float = selection
+        floatSetting.setFloat(selection)
         return floatSetting
     }
+
+    fun setSelectedValue(selection: Short): AbstractShortSetting {
+        val shortSetting = setting as AbstractShortSetting
+        shortSetting.setShort(selection)
+        return shortSetting
+    }
+
+    fun setSelectedValue(selection: Byte): AbstractByteSetting {
+        val byteSetting = setting as AbstractByteSetting
+        byteSetting.setByte(selection)
+        return byteSetting
+    }
 }
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 3b6731dcd..2195641e3 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
@@ -53,7 +53,7 @@ class StringSingleChoiceSetting(
      */
     fun setSelectedValue(selection: String): AbstractStringSetting {
         val stringSetting = setting as AbstractStringSetting
-        stringSetting.string = selection
+        stringSetting.setString(selection)
         return stringSetting
     }
 }
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 90b198718..4ed8070e5 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
@@ -49,14 +49,14 @@ class SwitchSetting(
         // Try integer setting
         try {
             val setting = setting as AbstractIntSetting
-            setting.int = if (checked) 1 else 0
+            setting.setInt(if (checked) 1 else 0)
             return setting
         } catch (_: ClassCastException) {
         }
 
         // Try boolean setting
         val setting = setting as AbstractBooleanSetting
-        setting.boolean = checked
+        setting.setBoolean(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 e6fffc832..733a53c8c 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
@@ -21,12 +21,7 @@ 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.utils.*
 
@@ -35,10 +30,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
 
     private lateinit var binding: ActivitySettingsBinding
 
-    private val settingsViewModel: SettingsViewModel by viewModels()
-
-    override val settings: Settings get() = settingsViewModel.settings
-
     override fun onCreate(savedInstanceState: Bundle?) {
         ThemeHelper.setTheme(this)
 
@@ -171,14 +162,6 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         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()
     }
@@ -187,19 +170,18 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
         // 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()
-
         // 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()
     }
 
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
index 93e677b21..fdbad32bf 100644
--- 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
@@ -5,7 +5,6 @@ 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
@@ -14,8 +13,6 @@ 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
@@ -33,13 +30,7 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
     }
 
     private fun loadSettingsUI() {
-        if (!settings.isLoaded) {
-            if (!TextUtils.isEmpty(gameId)) {
-                settings.loadSettings(gameId, activityView)
-            } else {
-                settings.loadSettings(activityView)
-            }
-        }
+        // TODO: Load custom settings contextually
         activityView.showSettingsFragment(menuTag, false, gameId)
         activityView.onSettingsFileLoaded()
     }
@@ -67,9 +58,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
     fun onStop(finishing: Boolean) {
         if (finishing && shouldSave) {
             Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
-            settings.saveSettings(activityView)
+            Settings.saveSettings()
+            NativeLibrary.reloadSettings()
         }
-        NativeLibrary.reloadSettings()
     }
 
     fun onSettingChanged() {
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
index c186fc388..07a58b4ea 100644
--- 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
@@ -3,8 +3,6 @@
 
 package org.yuzu.yuzu_emu.features.settings.ui
 
-import org.yuzu.yuzu_emu.features.settings.model.Settings
-
 /**
  * Abstraction for the Activity that manages SettingsFragments.
  */
@@ -17,15 +15,6 @@ interface SettingsActivityView {
      */
     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.
      */
@@ -36,14 +25,6 @@ interface SettingsActivityView {
      */
     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.
      */
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 9711e2c51..e2e8d8bec 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
@@ -24,12 +24,10 @@ import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
 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.ByteSetting
 import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
+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.ui.viewholder.*
 
@@ -115,8 +113,7 @@ class SettingsAdapter(
     }
 
     fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
-        val setting = item.setChecked(checked)
-        fragmentView.putSetting(setting)
+        item.setChecked(checked)
         fragmentView.onSettingChanged()
     }
 
@@ -150,7 +147,7 @@ class SettingsAdapter(
     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()
@@ -183,13 +180,11 @@ 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) {
+            if (item.value != epochTime) {
                 fragmentView.onSettingChanged()
+                notifyItemChanged(clickedPosition)
+                item.setSelectedValue(epochTime)
             }
-            notifyItemChanged(clickedPosition)
-            val setting = item.setSelectedValue(rtcString)
-            fragmentView.putSetting(setting)
             clickedItem = null
         }
         datePicker.show(
@@ -201,7 +196,7 @@ class SettingsAdapter(
     fun onSliderClick(item: SliderSetting, position: Int) {
         clickedItem = item
         clickedPosition = position
-        sliderProgress = item.selectedValue
+        sliderProgress = item.selectedValue as Int
 
         val inflater = LayoutInflater.from(context)
         val sliderBinding = DialogSliderBinding.inflate(inflater)
@@ -249,8 +244,7 @@ class SettingsAdapter(
                 }
 
                 // 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)
+                scSetting.setSelectedValue(value)
                 closeDialog()
             }
 
@@ -258,8 +252,7 @@ class SettingsAdapter(
                 val scSetting = clickedItem as StringSingleChoiceSetting
                 val value = scSetting.getValueAt(which)
                 if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
-                val setting = scSetting.setSelectedValue(value!!)
-                fragmentView.putSetting(setting)
+                scSetting.setSelectedValue(value!!)
                 closeDialog()
             }
 
@@ -268,13 +261,25 @@ class SettingsAdapter(
                 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)
+                when (sliderSetting.setting) {
+                    is ByteSetting -> {
+                        val value = sliderProgress.toByte()
+                        sliderSetting.setSelectedValue(value)
+                    }
+
+                    is ShortSetting -> {
+                        val value = sliderProgress.toShort()
+                        sliderSetting.setSelectedValue(value)
+                    }
+
+                    is FloatSetting -> {
+                        val value = sliderProgress.toFloat()
+                        sliderSetting.setSelectedValue(value)
+                    }
+
+                    else -> {
+                        sliderSetting.setSelectedValue(sliderProgress)
+                    }
                 }
                 closeDialog()
             }
@@ -286,13 +291,8 @@ class SettingsAdapter(
     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
-                }
+            .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+                setting.reset()
                 notifyItemChanged(position)
                 fragmentView.onSettingChanged()
             }
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 70a74c4dd..dc1bf6eb1 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
@@ -15,7 +15,6 @@ import androidx.fragment.app.Fragment
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.divider.MaterialDividerItemDecoration
 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
 
 class SettingsFragment : Fragment(), SettingsFragmentView {
@@ -89,14 +88,6 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
         )
     }
 
-    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()
     }
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 59c1d9d54..2bab9e542 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
@@ -6,16 +6,18 @@ package org.yuzu.yuzu_emu.features.settings.ui
 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
@@ -27,7 +29,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
     private var settingsList: ArrayList<SettingsItem>? = null
 
     private val settingsActivity get() = fragmentView.activityView as SettingsActivity
-    private val settings get() = fragmentView.activityView!!.settings
 
     private lateinit var preferences: SharedPreferences
 
@@ -41,17 +42,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         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")
@@ -69,7 +59,12 @@ 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
             }
         }
@@ -135,23 +130,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         sl.apply {
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_USE_SPEED_LIMIT,
+                    BooleanSetting.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
+                    BooleanSetting.RENDERER_USE_SPEED_LIMIT.key,
+                    BooleanSetting.RENDERER_USE_SPEED_LIMIT.defaultValue
                 )
             )
             add(
                 SliderSetting(
-                    IntSetting.RENDERER_SPEED_LIMIT,
+                    ShortSetting.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
+                    ShortSetting.RENDERER_SPEED_LIMIT.key,
+                    ShortSetting.RENDERER_SPEED_LIMIT.defaultValue
                 )
             )
             add(
@@ -182,11 +177,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         sl.apply {
             add(
                 SwitchSetting(
-                    IntSetting.USE_DOCKED_MODE,
+                    BooleanSetting.USE_DOCKED_MODE,
                     R.string.use_docked_mode,
                     R.string.use_docked_mode_description,
-                    IntSetting.USE_DOCKED_MODE.key,
-                    IntSetting.USE_DOCKED_MODE.defaultValue
+                    BooleanSetting.USE_DOCKED_MODE.key,
+                    BooleanSetting.USE_DOCKED_MODE.defaultValue
                 )
             )
             add(
@@ -222,11 +217,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
             add(
                 DateTimeSetting(
-                    StringSetting.CUSTOM_RTC,
+                    LongSetting.CUSTOM_RTC,
                     R.string.set_custom_rtc,
                     0,
-                    StringSetting.CUSTOM_RTC.key,
-                    StringSetting.CUSTOM_RTC.defaultValue
+                    LongSetting.CUSTOM_RTC.key,
+                    LongSetting.CUSTOM_RTC.defaultValue
                 )
             )
         }
@@ -314,38 +309,38 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_USE_DISK_SHADER_CACHE,
+                    BooleanSetting.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
+                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.key,
+                    BooleanSetting.RENDERER_USE_DISK_SHADER_CACHE.defaultValue
                 )
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_FORCE_MAX_CLOCK,
+                    BooleanSetting.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
+                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK.key,
+                    BooleanSetting.RENDERER_FORCE_MAX_CLOCK.defaultValue
                 )
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_ASYNCHRONOUS_SHADERS,
+                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS,
                     R.string.renderer_asynchronous_shaders,
                     R.string.renderer_asynchronous_shaders_description,
-                    IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
-                    IntSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
+                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key,
+                    BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.defaultValue
                 )
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_REACTIVE_FLUSHING,
+                    BooleanSetting.RENDERER_REACTIVE_FLUSHING,
                     R.string.renderer_reactive_flushing,
                     R.string.renderer_reactive_flushing_description,
-                    IntSetting.RENDERER_REACTIVE_FLUSHING.key,
-                    IntSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
+                    BooleanSetting.RENDERER_REACTIVE_FLUSHING.key,
+                    BooleanSetting.RENDERER_REACTIVE_FLUSHING.defaultValue
                 )
             )
         }
@@ -355,26 +350,26 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
         sl.apply {
             add(
-                StringSingleChoiceSetting(
-                    StringSetting.AUDIO_OUTPUT_ENGINE,
+                SingleChoiceSetting(
+                    IntSetting.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
+                    R.array.outputEngineEntries,
+                    R.array.outputEngineValues,
+                    IntSetting.AUDIO_OUTPUT_ENGINE.key,
+                    IntSetting.AUDIO_OUTPUT_ENGINE.defaultValue
                 )
             )
             add(
                 SliderSetting(
-                    IntSetting.AUDIO_VOLUME,
+                    ByteSetting.AUDIO_VOLUME,
                     R.string.audio_volume,
                     R.string.audio_volume_description,
                     0,
                     100,
                     "%",
-                    IntSetting.AUDIO_VOLUME.key,
-                    IntSetting.AUDIO_VOLUME.defaultValue
+                    ByteSetting.AUDIO_VOLUME.key,
+                    ByteSetting.AUDIO_VOLUME.defaultValue
                 )
             )
         }
@@ -384,19 +379,19 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
         settingsActivity.setToolbarTitle(settingsActivity.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 fun setInt(value: Int) {
+                    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 category = Settings.Category.UiGeneral
+                override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Any = 0
             }
 
@@ -423,19 +418,19 @@ 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 fun setInt(value: Int) {
+                    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 category = Settings.Category.UiGeneral
+                override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Any = -1
             }
 
@@ -450,20 +445,19 @@ 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 fun setBoolean(value: Boolean) {
+                    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 category = Settings.Category.UiGeneral
+                override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Any = false
             }
 
@@ -494,11 +488,11 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
             add(
                 SwitchSetting(
-                    IntSetting.RENDERER_DEBUG,
+                    BooleanSetting.RENDERER_DEBUG,
                     R.string.renderer_debug,
                     R.string.renderer_debug_description,
-                    IntSetting.RENDERER_DEBUG.key,
-                    IntSetting.RENDERER_DEBUG.defaultValue
+                    BooleanSetting.RENDERER_DEBUG.key,
+                    BooleanSetting.RENDERER_DEBUG.defaultValue
                 )
             )
 
@@ -514,17 +508,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
             )
 
             val fastmem = object : AbstractBooleanSetting {
-                override var boolean: Boolean
+                override val boolean: Boolean
                     get() =
                         BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
-                    set(value) {
-                        BooleanSetting.FASTMEM.boolean = value
-                        BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
-                    }
+
+                override fun setBoolean(value: Boolean) {
+                    BooleanSetting.FASTMEM.setBoolean(value)
+                    BooleanSetting.FASTMEM_EXCLUSIVES.setBoolean(value)
+                }
+
                 override val key: String? = null
-                override val section: String = Settings.SECTION_CPU
-                override val isRuntimeEditable: Boolean = false
-                override val valueAsString: String = ""
+                override val category = Settings.Category.Cpu
+                override val isRuntimeModifiable: Boolean = false
                 override val defaultValue: Any = true
             }
             add(
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
index 1ebe35eaa..a4d7a80aa 100644
--- 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
@@ -3,7 +3,6 @@
 
 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
 
 /**
@@ -36,21 +35,6 @@ interface SettingsFragmentView {
      */
     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.
      */
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 79572fc06..eb25ea4fb 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)
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 70a52df5d..2b04d666a 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/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index aaf3a0ec1..d8dbf1f45 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
@@ -39,7 +39,6 @@ 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
@@ -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)
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 9cfda74ee..000000000
--- 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/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
new file mode 100644
index 000000000..d4d981f9e
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -0,0 +1,31 @@
+// 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
+}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index e2ed08e9f..e15d1480b 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 9de9bd93e..34b425cb4 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 0d7d6e94d..e1e8f47ed 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 7e17833a0..b2adfdeda 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 000000000..6123b3d08
--- /dev/null
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -0,0 +1,224 @@
+// 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));
+}
+
+} // 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 000000000..f2f0bad50
--- /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 000000000..494654af7
--- /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/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 200b99185..dc10159c9 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 de1b2909b..df76563fc 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -200,6 +200,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 524056841..4ecaf550b 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 137b65d5f..5960b78aa 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 3082e0ce1..5b170dfd5 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;
 };