summaryrefslogtreecommitdiff
path: root/src/android
diff options
context:
space:
mode:
authorGravatar t8952023-12-10 20:38:58 -0500
committerGravatar t8952023-12-12 17:25:36 -0500
commit2fce81202608cbf4b75fd46e19180fee60e47a53 (patch)
tree6c334f5fc3abe10b35ad5d88687e900dd4c1c0ae /src/android
parentandroid: Add Game properties (diff)
downloadyuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.tar.gz
yuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.tar.xz
yuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.zip
android: Add per-game settings
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt42
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt12
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt20
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt42
-rw-r--r--src/android/app/src/main/jni/native_config.cpp46
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting.xml10
-rw-r--r--src/android/app/src/main/res/layout/list_item_setting_switch.xml75
-rw-r--r--src/android/app/src/main/res/menu/menu_in_game.xml5
-rw-r--r--src/android/app/src/main/res/navigation/emulation_navigation.xml4
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml4
27 files changed, 303 insertions, 83 deletions
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 384527294..28d8dea60 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
@@ -12,6 +12,7 @@ import org.yuzu.yuzu_emu.features.settings.model.ByteSetting
12import org.yuzu.yuzu_emu.features.settings.model.IntSetting 12import org.yuzu.yuzu_emu.features.settings.model.IntSetting
13import org.yuzu.yuzu_emu.features.settings.model.LongSetting 13import org.yuzu.yuzu_emu.features.settings.model.LongSetting
14import org.yuzu.yuzu_emu.features.settings.model.ShortSetting 14import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
15import org.yuzu.yuzu_emu.utils.NativeConfig
15 16
16/** 17/**
17 * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. 18 * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -30,9 +31,19 @@ abstract class SettingsItem(
30 val isEditable: Boolean 31 val isEditable: Boolean
31 get() { 32 get() {
32 if (!NativeLibrary.isRunning()) return true 33 if (!NativeLibrary.isRunning()) return true
34
35 // Prevent editing settings that were modified in per-game config while editing global
36 // config
37 if (!NativeConfig.isPerGameConfigLoaded() && !setting.global) {
38 return false
39 }
33 return setting.isRuntimeModifiable 40 return setting.isRuntimeModifiable
34 } 41 }
35 42
43 val needsRuntimeGlobal: Boolean
44 get() = NativeLibrary.isRunning() && !setting.global &&
45 !NativeConfig.isPerGameConfigLoaded()
46
36 companion object { 47 companion object {
37 const val TYPE_HEADER = 0 48 const val TYPE_HEADER = 0
38 const val TYPE_SWITCH = 1 49 const val TYPE_SWITCH = 1
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 64bfc6dd0..6f072241a 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
@@ -19,10 +19,9 @@ import androidx.lifecycle.repeatOnLifecycle
19import androidx.navigation.fragment.NavHostFragment 19import androidx.navigation.fragment.NavHostFragment
20import androidx.navigation.navArgs 20import androidx.navigation.navArgs
21import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
22import kotlinx.coroutines.CoroutineScope
23import kotlinx.coroutines.Dispatchers
24import kotlinx.coroutines.flow.collectLatest 22import kotlinx.coroutines.flow.collectLatest
25import kotlinx.coroutines.launch 23import kotlinx.coroutines.launch
24import org.yuzu.yuzu_emu.NativeLibrary
26import java.io.IOException 25import java.io.IOException
27import org.yuzu.yuzu_emu.R 26import org.yuzu.yuzu_emu.R
28import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 27import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -46,6 +45,9 @@ class SettingsActivity : AppCompatActivity() {
46 binding = ActivitySettingsBinding.inflate(layoutInflater) 45 binding = ActivitySettingsBinding.inflate(layoutInflater)
47 setContentView(binding.root) 46 setContentView(binding.root)
48 47
48 if (!NativeConfig.isPerGameConfigLoaded() && args.game != null) {
49 SettingsFile.loadCustomConfig(args.game!!)
50 }
49 settingsViewModel.game = args.game 51 settingsViewModel.game = args.game
50 52
51 val navHostFragment = 53 val navHostFragment =
@@ -126,7 +128,6 @@ class SettingsActivity : AppCompatActivity() {
126 128
127 override fun onStart() { 129 override fun onStart() {
128 super.onStart() 130 super.onStart()
129 // TODO: Load custom settings contextually
130 if (!DirectoryInitialization.areDirectoriesReady) { 131 if (!DirectoryInitialization.areDirectoriesReady) {
131 DirectoryInitialization.start() 132 DirectoryInitialization.start()
132 } 133 }
@@ -134,24 +135,35 @@ class SettingsActivity : AppCompatActivity() {
134 135
135 override fun onStop() { 136 override fun onStop() {
136 super.onStop() 137 super.onStop()
137 CoroutineScope(Dispatchers.IO).launch { 138 Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
138 NativeConfig.saveSettings() 139 if (isFinishing) {
140 NativeLibrary.applySettings()
141 if (args.game == null) {
142 NativeConfig.saveGlobalConfig()
143 } else if (NativeConfig.isPerGameConfigLoaded()) {
144 NativeLibrary.logSettings()
145 NativeConfig.savePerGameConfig()
146 NativeConfig.unloadPerGameConfig()
147 }
139 } 148 }
140 } 149 }
141 150
142 override fun onDestroy() {
143 settingsViewModel.clear()
144 super.onDestroy()
145 }
146
147 fun onSettingsReset() { 151 fun onSettingsReset() {
148 // Delete settings file because the user may have changed values that do not exist in the UI 152 // Delete settings file because the user may have changed values that do not exist in the UI
149 NativeConfig.unloadConfig() 153 if (args.game == null) {
150 val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG) 154 NativeConfig.unloadGlobalConfig()
151 if (!settingsFile.delete()) { 155 val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
152 throw IOException("Failed to delete $settingsFile") 156 if (!settingsFile.delete()) {
157 throw IOException("Failed to delete $settingsFile")
158 }
159 NativeConfig.initializeGlobalConfig()
160 } else {
161 NativeConfig.unloadPerGameConfig()
162 val settingsFile = SettingsFile.getCustomSettingsFile(args.game!!)
163 if (!settingsFile.delete()) {
164 throw IOException("Failed to delete $settingsFile")
165 }
153 } 166 }
154 NativeConfig.initializeConfig()
155 167
156 Toast.makeText( 168 Toast.makeText(
157 applicationContext, 169 applicationContext,
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 3f23c064e..be9b3031b 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
@@ -196,6 +196,12 @@ class SettingsAdapter(
196 return true 196 return true
197 } 197 }
198 198
199 fun onClearClick(item: SettingsItem, position: Int) {
200 item.setting.global = true
201 notifyItemChanged(position)
202 settingsViewModel.setShouldReloadSettingsList(true)
203 }
204
199 private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() { 205 private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
200 override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { 206 override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
201 return oldItem.setting.key == newItem.setting.key 207 return oldItem.setting.key == newItem.setting.key
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 769baf744..d7ab0b5d9 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
@@ -66,7 +66,13 @@ class SettingsFragment : Fragment() {
66 args.menuTag 66 args.menuTag
67 ) 67 )
68 68
69 binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId) 69 binding.toolbarSettingsLayout.title = if (args.menuTag == Settings.MenuTag.SECTION_ROOT &&
70 args.game != null
71 ) {
72 args.game!!.title
73 } else {
74 getString(args.menuTag.titleId)
75 }
70 binding.listSettings.apply { 76 binding.listSettings.apply {
71 adapter = settingsAdapter 77 adapter = settingsAdapter
72 layoutManager = LinearLayoutManager(requireContext()) 78 layoutManager = LinearLayoutManager(requireContext())
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 12a389b37..a7e965589 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
@@ -7,6 +7,7 @@ import android.content.SharedPreferences
7import android.os.Build 7import android.os.Build
8import android.widget.Toast 8import android.widget.Toast
9import androidx.preference.PreferenceManager 9import androidx.preference.PreferenceManager
10import org.yuzu.yuzu_emu.NativeLibrary
10import org.yuzu.yuzu_emu.R 11import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication 12import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting 13import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
@@ -31,9 +32,17 @@ class SettingsFragmentPresenter(
31 private val preferences: SharedPreferences 32 private val preferences: SharedPreferences
32 get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 33 get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
33 34
34 // Extension for populating settings list based on paired settings 35 // Extension for altering settings list based on each setting's properties
35 fun ArrayList<SettingsItem>.add(key: String) { 36 fun ArrayList<SettingsItem>.add(key: String) {
36 val item = SettingsItem.settingsItems[key]!! 37 val item = SettingsItem.settingsItems[key]!!
38 if (settingsViewModel.game != null && !item.setting.isSwitchable) {
39 return
40 }
41
42 if (!NativeConfig.isPerGameConfigLoaded() && !NativeLibrary.isRunning()) {
43 item.setting.global = true
44 }
45
37 val pairedSettingKey = item.setting.pairedSettingKey 46 val pairedSettingKey = item.setting.pairedSettingKey
38 if (pairedSettingKey.isNotEmpty()) { 47 if (pairedSettingKey.isNotEmpty()) {
39 val pairedSettingValue = NativeConfig.getBoolean( 48 val pairedSettingValue = NativeConfig.getBoolean(
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 4e159a799..5ad0899dd 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
@@ -13,6 +13,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
13import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting 13import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
14import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 14import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
15import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter 15import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
16import org.yuzu.yuzu_emu.utils.NativeConfig
16 17
17class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : 18class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
18 SettingViewHolder(binding.root, adapter) { 19 SettingViewHolder(binding.root, adapter) {
@@ -35,6 +36,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
35 val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) 36 val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
36 binding.textSettingValue.text = dateFormatter.format(zonedTime) 37 binding.textSettingValue.text = dateFormatter.format(zonedTime)
37 38
39 binding.buttonClear.visibility = if (setting.setting.global ||
40 !NativeConfig.isPerGameConfigLoaded()
41 ) {
42 View.GONE
43 } else {
44 View.VISIBLE
45 }
46 binding.buttonClear.setOnClickListener {
47 adapter.onClearClick(setting, bindingAdapterPosition)
48 }
49
38 setStyle(setting.isEditable, binding) 50 setStyle(setting.isEditable, binding)
39 } 51 }
40 52
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
index 036195624..507184238 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/RunnableViewHolder.kt
@@ -38,6 +38,7 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
38 binding.textSettingDescription.visibility = View.GONE 38 binding.textSettingDescription.visibility = View.GONE
39 } 39 }
40 binding.textSettingValue.visibility = View.GONE 40 binding.textSettingValue.visibility = View.GONE
41 binding.buttonClear.visibility = View.GONE
41 42
42 setStyle(setting.isEditable, binding) 43 setStyle(setting.isEditable, binding)
43 } 44 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
index 0fd1d2eaa..d26887df8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SettingViewHolder.kt
@@ -41,6 +41,7 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
41 binding.textSettingName.alpha = opacity 41 binding.textSettingName.alpha = opacity
42 binding.textSettingDescription.alpha = opacity 42 binding.textSettingDescription.alpha = opacity
43 binding.textSettingValue.alpha = opacity 43 binding.textSettingValue.alpha = opacity
44 binding.buttonClear.isEnabled = isEditable
44 } 45 }
45 46
46 fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) { 47 fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
@@ -48,5 +49,6 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
48 val opacity = if (isEditable) 1.0f else 0.5f 49 val opacity = if (isEditable) 1.0f else 0.5f
49 binding.textSettingName.alpha = opacity 50 binding.textSettingName.alpha = opacity
50 binding.textSettingDescription.alpha = opacity 51 binding.textSettingDescription.alpha = opacity
52 binding.buttonClear.isEnabled = isEditable
51 } 53 }
52} 54}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
index 28c4d1777..02dab3785 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt
@@ -9,6 +9,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
9import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting 9import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
10import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting 10import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter 11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
12import org.yuzu.yuzu_emu.utils.NativeConfig
12 13
13class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : 14class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
14 SettingViewHolder(binding.root, adapter) { 15 SettingViewHolder(binding.root, adapter) {
@@ -43,6 +44,17 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
43 } 44 }
44 } 45 }
45 46
47 binding.buttonClear.visibility = if (setting.setting.global ||
48 !NativeConfig.isPerGameConfigLoaded()
49 ) {
50 View.GONE
51 } else {
52 View.VISIBLE
53 }
54 binding.buttonClear.setOnClickListener {
55 adapter.onClearClick(setting, bindingAdapterPosition)
56 }
57
46 setStyle(setting.isEditable, binding) 58 setStyle(setting.isEditable, binding)
47 } 59 }
48 60
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
index 67432f88e..596c18012 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SliderViewHolder.kt
@@ -9,6 +9,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
9import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 9import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
10import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting 10import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter 11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
12import org.yuzu.yuzu_emu.utils.NativeConfig
12 13
13class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : 14class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
14 SettingViewHolder(binding.root, adapter) { 15 SettingViewHolder(binding.root, adapter) {
@@ -30,6 +31,17 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
30 setting.units 31 setting.units
31 ) 32 )
32 33
34 binding.buttonClear.visibility = if (setting.setting.global ||
35 !NativeConfig.isPerGameConfigLoaded()
36 ) {
37 View.GONE
38 } else {
39 View.VISIBLE
40 }
41 binding.buttonClear.setOnClickListener {
42 adapter.onClearClick(setting, bindingAdapterPosition)
43 }
44
33 setStyle(setting.isEditable, binding) 45 setStyle(setting.isEditable, binding)
34 } 46 }
35 47
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
index 8100c65dd..20d35a17d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SubmenuViewHolder.kt
@@ -37,6 +37,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
37 binding.textSettingDescription.visibility = View.GONE 37 binding.textSettingDescription.visibility = View.GONE
38 } 38 }
39 binding.textSettingValue.visibility = View.GONE 39 binding.textSettingValue.visibility = View.GONE
40 binding.buttonClear.visibility = View.GONE
40 } 41 }
41 42
42 override fun onClick(clicked: View) { 43 override fun onClick(clicked: View) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index 98ed888cb..d26bf9374 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -9,6 +9,7 @@ import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
9import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 9import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
10import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting 10import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter 11import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
12import org.yuzu.yuzu_emu.utils.NativeConfig
12 13
13class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : 14class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
14 SettingViewHolder(binding.root, adapter) { 15 SettingViewHolder(binding.root, adapter) {
@@ -29,7 +30,18 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
29 binding.switchWidget.setOnCheckedChangeListener(null) 30 binding.switchWidget.setOnCheckedChangeListener(null)
30 binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal) 31 binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
31 binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean -> 32 binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
32 adapter.onBooleanClick(item, binding.switchWidget.isChecked) 33 adapter.onBooleanClick(item, binding.switchWidget.isChecked, bindingAdapterPosition)
34 }
35
36 binding.buttonClear.visibility = if (setting.setting.global ||
37 !NativeConfig.isPerGameConfigLoaded()
38 ) {
39 View.GONE
40 } else {
41 View.VISIBLE
42 }
43 binding.buttonClear.setOnClickListener {
44 adapter.onClearClick(setting, bindingAdapterPosition)
33 } 45 }
34 46
35 setStyle(setting.isEditable, binding) 47 setStyle(setting.isEditable, binding)
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 3ae5b4653..5d523be67 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,15 +3,27 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.utils 4package org.yuzu.yuzu_emu.features.settings.utils
5 5
6import android.net.Uri
7import org.yuzu.yuzu_emu.model.Game
6import java.io.* 8import java.io.*
7import org.yuzu.yuzu_emu.utils.DirectoryInitialization 9import org.yuzu.yuzu_emu.utils.DirectoryInitialization
10import org.yuzu.yuzu_emu.utils.FileUtil
11import org.yuzu.yuzu_emu.utils.NativeConfig
8 12
9/** 13/**
10 * Contains static methods for interacting with .ini files in which settings are stored. 14 * Contains static methods for interacting with .ini files in which settings are stored.
11 */ 15 */
12object SettingsFile { 16object SettingsFile {
13 const val FILE_NAME_CONFIG = "config" 17 const val FILE_NAME_CONFIG = "config.ini"
14 18
15 fun getSettingsFile(fileName: String): File = 19 fun getSettingsFile(fileName: String): File =
16 File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini") 20 File(DirectoryInitialization.userDirectory + "/config/" + fileName)
21
22 fun getCustomSettingsFile(game: Game): File =
23 File(DirectoryInitialization.userDirectory + "/config/custom/" + game.settingsName + ".ini")
24
25 fun loadCustomConfig(game: Game) {
26 val fileName = FileUtil.getFilename(Uri.parse(game.path))
27 NativeConfig.initializePerGameConfig(game.programId, fileName)
28 }
17} 29}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index b09df7db3..6466442d5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -52,6 +52,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
52import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 52import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
53import org.yuzu.yuzu_emu.features.settings.model.IntSetting 53import org.yuzu.yuzu_emu.features.settings.model.IntSetting
54import org.yuzu.yuzu_emu.features.settings.model.Settings 54import org.yuzu.yuzu_emu.features.settings.model.Settings
55import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
55import org.yuzu.yuzu_emu.model.DriverViewModel 56import org.yuzu.yuzu_emu.model.DriverViewModel
56import org.yuzu.yuzu_emu.model.Game 57import org.yuzu.yuzu_emu.model.Game
57import org.yuzu.yuzu_emu.model.EmulationViewModel 58import org.yuzu.yuzu_emu.model.EmulationViewModel
@@ -127,6 +128,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
127 return 128 return
128 } 129 }
129 130
131 if (args.custom) {
132 SettingsFile.loadCustomConfig(args.game!!)
133 NativeConfig.unloadPerGameConfig()
134 } else {
135 NativeConfig.reloadGlobalConfig()
136 }
137
138 // Install the selected driver asynchronously as the game starts
139 driverViewModel.onLaunchGame()
140
130 // So this fragment doesn't restart on configuration changes; i.e. rotation. 141 // So this fragment doesn't restart on configuration changes; i.e. rotation.
131 retainInstance = true 142 retainInstance = true
132 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 143 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -217,6 +228,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
217 true 228 true
218 } 229 }
219 230
231 R.id.menu_settings_per_game -> {
232 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
233 args.game,
234 Settings.MenuTag.SECTION_ROOT
235 )
236 binding.root.findNavController().navigate(action)
237 true
238 }
239
220 R.id.menu_overlay_controls -> { 240 R.id.menu_overlay_controls -> {
221 showOverlayOptions() 241 showOverlayOptions()
222 true 242 true
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt
index b6c2e4635..1ea1e036e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt
@@ -13,6 +13,7 @@ import org.yuzu.yuzu_emu.R
13import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding 13import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
14import org.yuzu.yuzu_emu.model.GameDir 14import org.yuzu.yuzu_emu.model.GameDir
15import org.yuzu.yuzu_emu.model.GamesViewModel 15import org.yuzu.yuzu_emu.model.GamesViewModel
16import org.yuzu.yuzu_emu.utils.NativeConfig
16import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable 17import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
17 18
18class GameFolderPropertiesDialogFragment : DialogFragment() { 19class GameFolderPropertiesDialogFragment : DialogFragment() {
@@ -49,6 +50,11 @@ class GameFolderPropertiesDialogFragment : DialogFragment() {
49 .show() 50 .show()
50 } 51 }
51 52
53 override fun onStop() {
54 super.onStop()
55 NativeConfig.saveGlobalConfig()
56 }
57
52 override fun onSaveInstanceState(outState: Bundle) { 58 override fun onSaveInstanceState(outState: Bundle) {
53 super.onSaveInstanceState(outState) 59 super.onSaveInstanceState(outState)
54 outState.putBoolean(DEEP_SCAN, deepScan) 60 outState.putBoolean(DEEP_SCAN, deepScan)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index eb5edaa10..064342cdd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -304,6 +304,11 @@ class SetupFragment : Fragment() {
304 setInsets() 304 setInsets()
305 } 305 }
306 306
307 override fun onStop() {
308 super.onStop()
309 NativeConfig.saveGlobalConfig()
310 }
311
307 override fun onSaveInstanceState(outState: Bundle) { 312 override fun onSaveInstanceState(outState: Bundle) {
308 super.onSaveInstanceState(outState) 313 super.onSaveInstanceState(outState)
309 if (_binding != null) { 314 if (_binding != null) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index fd925235b..eaec09b24 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -168,6 +168,7 @@ class GamesViewModel : ViewModel() {
168 fun onCloseGameFoldersFragment() = 168 fun onCloseGameFoldersFragment() =
169 viewModelScope.launch { 169 viewModelScope.launch {
170 withContext(Dispatchers.IO) { 170 withContext(Dispatchers.IO) {
171 NativeConfig.saveGlobalConfig()
171 getGameDirs(true) 172 getGameDirs(true)
172 } 173 }
173 } 174 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index ccc981e95..5cb6a5d57 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -68,8 +68,4 @@ class SettingsViewModel : ViewModel() {
68 fun setAdapterItemChanged(value: Int) { 68 fun setAdapterItemChanged(value: Int) {
69 _adapterItemChanged.value = value 69 _adapterItemChanged.value = value
70 } 70 }
71
72 fun clear() {
73 game = null
74 }
75} 71}
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 09ddd1bbd..b4117d761 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
@@ -28,12 +28,9 @@ import androidx.navigation.ui.setupWithNavController
28import androidx.preference.PreferenceManager 28import androidx.preference.PreferenceManager
29import com.google.android.material.color.MaterialColors 29import com.google.android.material.color.MaterialColors
30import com.google.android.material.navigation.NavigationBarView 30import com.google.android.material.navigation.NavigationBarView
31import kotlinx.coroutines.CoroutineScope
32import java.io.File 31import java.io.File
33import java.io.FilenameFilter 32import java.io.FilenameFilter
34import kotlinx.coroutines.Dispatchers
35import kotlinx.coroutines.launch 33import kotlinx.coroutines.launch
36import kotlinx.coroutines.withContext
37import org.yuzu.yuzu_emu.HomeNavigationDirections 34import org.yuzu.yuzu_emu.HomeNavigationDirections
38import org.yuzu.yuzu_emu.NativeLibrary 35import org.yuzu.yuzu_emu.NativeLibrary
39import org.yuzu.yuzu_emu.R 36import org.yuzu.yuzu_emu.R
@@ -258,13 +255,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
258 super.onResume() 255 super.onResume()
259 } 256 }
260 257
261 override fun onStop() {
262 super.onStop()
263 CoroutineScope(Dispatchers.IO).launch {
264 NativeConfig.saveSettings()
265 }
266 }
267
268 override fun onDestroy() { 258 override fun onDestroy() {
269 EmulationActivity.stopForegroundService(this) 259 EmulationActivity.stopForegroundService(this)
270 super.onDestroy() 260 super.onDestroy()
@@ -677,7 +667,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
677 } 667 }
678 668
679 // Clear existing user data 669 // Clear existing user data
680 NativeConfig.unloadConfig() 670 NativeConfig.unloadGlobalConfig()
681 File(DirectoryInitialization.userDirectory!!).deleteRecursively() 671 File(DirectoryInitialization.userDirectory!!).deleteRecursively()
682 672
683 // Copy archive to internal storage 673 // Copy archive to internal storage
@@ -696,7 +686,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
696 686
697 // Reinitialize relevant data 687 // Reinitialize relevant data
698 NativeLibrary.initializeSystem(true) 688 NativeLibrary.initializeSystem(true)
699 NativeConfig.initializeConfig() 689 NativeConfig.initializeGlobalConfig()
700 gamesViewModel.reloadGames(false) 690 gamesViewModel.reloadGames(false)
701 691
702 return@newInstance getString(R.string.user_data_import_success) 692 return@newInstance getString(R.string.user_data_import_success)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
index 21270fc84..0197fd712 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
@@ -16,7 +16,7 @@ object DirectoryInitialization {
16 if (!areDirectoriesReady) { 16 if (!areDirectoriesReady) {
17 initializeInternalStorage() 17 initializeInternalStorage()
18 NativeLibrary.initializeSystem(false) 18 NativeLibrary.initializeSystem(false)
19 NativeConfig.initializeConfig() 19 NativeConfig.initializeGlobalConfig()
20 areDirectoriesReady = true 20 areDirectoriesReady = true
21 } 21 }
22 } 22 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
index 7d629b7d5..2d3d8ec79 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt
@@ -7,30 +7,54 @@ import org.yuzu.yuzu_emu.model.GameDir
7 7
8object NativeConfig { 8object NativeConfig {
9 /** 9 /**
10 * Creates a Config object and opens the emulation config. 10 * Loads global config.
11 */ 11 */
12 @Synchronized 12 @Synchronized
13 external fun initializeConfig() 13 external fun initializeGlobalConfig()
14 14
15 /** 15 /**
16 * Destroys the stored config object. This automatically saves the existing config. 16 * Destroys the stored global config object. This does not save the existing config.
17 */ 17 */
18 @Synchronized 18 @Synchronized
19 external fun unloadConfig() 19 external fun unloadGlobalConfig()
20 20
21 /** 21 /**
22 * Reads values saved to the config file and saves them. 22 * Reads values in the global config file and saves them.
23 */ 23 */
24 @Synchronized 24 @Synchronized
25 external fun reloadSettings() 25 external fun reloadGlobalConfig()
26 26
27 /** 27 /**
28 * Saves settings values in memory to disk. 28 * Saves global settings values in memory to disk.
29 */ 29 */
30 @Synchronized 30 @Synchronized
31 external fun saveSettings() 31 external fun saveGlobalConfig()
32 32
33 external fun getBoolean(key: String, getDefault: Boolean): Boolean 33 /**
34 * Creates per-game config for the specified parameters. Must be unloaded once per-game config
35 * is closed with [unloadPerGameConfig]. All switchable values that [NativeConfig] gets/sets
36 * will follow the per-game config until the global config is reloaded.
37 *
38 * @param programId String representation of the u64 programId
39 * @param fileName Filename of the game, including its extension
40 */
41 @Synchronized
42 external fun initializePerGameConfig(programId: String, fileName: String)
43
44 @Synchronized
45 external fun isPerGameConfigLoaded(): Boolean
46
47 /**
48 * Saves per-game settings values in memory to disk.
49 */
50 @Synchronized
51 external fun savePerGameConfig()
52
53 /**
54 * Destroys the stored per-game config object. This does not save the config.
55 */
56 @Synchronized
57 external fun unloadPerGameConfig()
34 58
35 @Synchronized 59 @Synchronized
36 external fun getBoolean(key: String, needsGlobal: Boolean): Boolean 60 external fun getBoolean(key: String, needsGlobal: Boolean): Boolean
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 7f2485720..56989bde8 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -3,6 +3,7 @@
3 3
4#include <string> 4#include <string>
5 5
6#include <common/fs/fs_util.h>
6#include <jni.h> 7#include <jni.h>
7 8
8#include "android_config.h" 9#include "android_config.h"
@@ -12,17 +13,19 @@
12#include "frontend_common/config.h" 13#include "frontend_common/config.h"
13#include "jni/android_common/android_common.h" 14#include "jni/android_common/android_common.h"
14#include "jni/id_cache.h" 15#include "jni/id_cache.h"
16#include "native.h"
15 17
16std::unique_ptr<AndroidConfig> config; 18std::unique_ptr<AndroidConfig> global_config;
19std::unique_ptr<AndroidConfig> per_game_config;
17 20
18template <typename T> 21template <typename T>
19Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) { 22Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
20 auto key = GetJString(env, jkey); 23 auto key = GetJString(env, jkey);
21 auto basicSetting = Settings::values.linkage.by_key[key]; 24 auto basicSetting = Settings::values.linkage.by_key[key];
22 auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
23 if (basicSetting != 0) { 25 if (basicSetting != 0) {
24 return static_cast<Settings::Setting<T>*>(basicSetting); 26 return static_cast<Settings::Setting<T>*>(basicSetting);
25 } 27 }
28 auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
26 if (basicAndroidSetting != 0) { 29 if (basicAndroidSetting != 0) {
27 return static_cast<Settings::Setting<T>*>(basicAndroidSetting); 30 return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
28 } 31 }
@@ -32,20 +35,43 @@ Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
32 35
33extern "C" { 36extern "C" {
34 37
35void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeConfig(JNIEnv* env, jobject obj) { 38void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
36 config = std::make_unique<AndroidConfig>(); 39 global_config = std::make_unique<AndroidConfig>();
40}
41
42void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
43 global_config.reset();
44}
45
46void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
47 global_config->AndroidConfig::ReloadAllValues();
48}
49
50void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
51 global_config->AndroidConfig::SaveAllValues();
52}
53
54void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj,
55 jstring jprogramId,
56 jstring jfileName) {
57 auto program_id = EmulationSession::GetProgramId(env, jprogramId);
58 auto file_name = GetJString(env, jfileName);
59 const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
60 per_game_config =
61 std::make_unique<AndroidConfig>(config_file_name, Config::ConfigType::PerGameConfig);
37} 62}
38 63
39void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadConfig(JNIEnv* env, jobject obj) { 64jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_isPerGameConfigLoaded(JNIEnv* env,
40 config.reset(); 65 jobject obj) {
66 return per_game_config != nullptr;
41} 67}
42 68
43void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadSettings(JNIEnv* env, jobject obj) { 69void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_savePerGameConfig(JNIEnv* env, jobject obj) {
44 config->AndroidConfig::ReloadAllValues(); 70 per_game_config->AndroidConfig::SaveAllValues();
45} 71}
46 72
47void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveSettings(JNIEnv* env, jobject obj) { 73void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadPerGameConfig(JNIEnv* env, jobject obj) {
48 config->AndroidConfig::SaveAllValues(); 74 per_game_config.reset();
49} 75}
50 76
51jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj, 77jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
diff --git a/src/android/app/src/main/res/layout/list_item_setting.xml b/src/android/app/src/main/res/layout/list_item_setting.xml
index 544280e75..1f80682f1 100644
--- a/src/android/app/src/main/res/layout/list_item_setting.xml
+++ b/src/android/app/src/main/res/layout/list_item_setting.xml
@@ -62,6 +62,16 @@
62 android:textSize="13sp" 62 android:textSize="13sp"
63 tools:text="1x" /> 63 tools:text="1x" />
64 64
65 <com.google.android.material.button.MaterialButton
66 android:id="@+id/button_clear"
67 style="@style/Widget.Material3.Button.TonalButton"
68 android:layout_width="wrap_content"
69 android:layout_height="wrap_content"
70 android:layout_marginTop="16dp"
71 android:visibility="gone"
72 android:text="@string/clear"
73 tools:visibility="visible" />
74
65 </LinearLayout> 75 </LinearLayout>
66 76
67 </LinearLayout> 77 </LinearLayout>
diff --git a/src/android/app/src/main/res/layout/list_item_setting_switch.xml b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
index a8f5aff78..5cb84182e 100644
--- a/src/android/app/src/main/res/layout/list_item_setting_switch.xml
+++ b/src/android/app/src/main/res/layout/list_item_setting_switch.xml
@@ -10,41 +10,62 @@
10 android:minHeight="72dp" 10 android:minHeight="72dp"
11 android:padding="16dp"> 11 android:padding="16dp">
12 12
13 <com.google.android.material.materialswitch.MaterialSwitch
14 android:id="@+id/switch_widget"
15 android:layout_width="wrap_content"
16 android:layout_height="wrap_content"
17 android:layout_alignParentEnd="true"
18 android:layout_centerVertical="true" />
19
20 <LinearLayout 13 <LinearLayout
21 android:layout_width="match_parent" 14 android:layout_width="match_parent"
22 android:layout_height="wrap_content" 15 android:layout_height="wrap_content"
23 android:layout_alignParentTop="true"
24 android:layout_centerVertical="true"
25 android:layout_marginEnd="24dp"
26 android:layout_toStartOf="@+id/switch_widget"
27 android:gravity="center_vertical"
28 android:orientation="vertical"> 16 android:orientation="vertical">
29 17
30 <com.google.android.material.textview.MaterialTextView 18 <LinearLayout
31 android:id="@+id/text_setting_name" 19 android:layout_width="match_parent"
32 style="@style/TextAppearance.Material3.HeadlineMedium"
33 android:layout_width="wrap_content"
34 android:layout_height="wrap_content" 20 android:layout_height="wrap_content"
35 android:textAlignment="viewStart" 21 android:orientation="horizontal">
36 android:textSize="17sp" 22
37 app:lineHeight="28dp" 23 <LinearLayout
38 tools:text="@string/frame_limit_enable" /> 24 android:layout_width="0dp"
39 25 android:layout_height="wrap_content"
40 <com.google.android.material.textview.MaterialTextView 26 android:layout_marginEnd="24dp"
41 android:id="@+id/text_setting_description" 27 android:gravity="center_vertical"
42 style="@style/TextAppearance.Material3.BodySmall" 28 android:orientation="vertical"
29 android:layout_weight="1">
30
31 <com.google.android.material.textview.MaterialTextView
32 android:id="@+id/text_setting_name"
33 style="@style/TextAppearance.Material3.HeadlineMedium"
34 android:layout_width="wrap_content"
35 android:layout_height="wrap_content"
36 android:textAlignment="viewStart"
37 android:textSize="17sp"
38 app:lineHeight="28dp"
39 tools:text="@string/frame_limit_enable" />
40
41 <com.google.android.material.textview.MaterialTextView
42 android:id="@+id/text_setting_description"
43 style="@style/TextAppearance.Material3.BodySmall"
44 android:layout_width="wrap_content"
45 android:layout_height="wrap_content"
46 android:layout_marginTop="@dimen/spacing_small"
47 android:textAlignment="viewStart"
48 tools:text="@string/frame_limit_enable_description" />
49
50 </LinearLayout>
51
52 <com.google.android.material.materialswitch.MaterialSwitch
53 android:id="@+id/switch_widget"
54 android:layout_width="wrap_content"
55 android:layout_height="wrap_content"
56 android:layout_gravity="center_vertical"/>
57
58 </LinearLayout>
59
60 <com.google.android.material.button.MaterialButton
61 android:id="@+id/button_clear"
62 style="@style/Widget.Material3.Button.TonalButton"
43 android:layout_width="wrap_content" 63 android:layout_width="wrap_content"
44 android:layout_height="wrap_content" 64 android:layout_height="wrap_content"
45 android:layout_marginTop="@dimen/spacing_small" 65 android:layout_marginTop="16dp"
46 android:textAlignment="viewStart" 66 android:text="@string/clear"
47 tools:text="@string/frame_limit_enable_description" /> 67 android:visibility="gone"
68 tools:visibility="visible" />
48 69
49 </LinearLayout> 70 </LinearLayout>
50 71
diff --git a/src/android/app/src/main/res/menu/menu_in_game.xml b/src/android/app/src/main/res/menu/menu_in_game.xml
index f98f727b6..ac6ab06ff 100644
--- a/src/android/app/src/main/res/menu/menu_in_game.xml
+++ b/src/android/app/src/main/res/menu/menu_in_game.xml
@@ -12,6 +12,11 @@
12 android:title="@string/preferences_settings" /> 12 android:title="@string/preferences_settings" />
13 13
14 <item 14 <item
15 android:id="@+id/menu_settings_per_game"
16 android:icon="@drawable/ic_settings_outline"
17 android:title="@string/per_game_settings" />
18
19 <item
15 android:id="@+id/menu_overlay_controls" 20 android:id="@+id/menu_overlay_controls"
16 android:icon="@drawable/ic_controller" 21 android:icon="@drawable/ic_controller"
17 android:title="@string/emulation_input_overlay" /> 22 android:title="@string/emulation_input_overlay" />
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml
index cfc494b3f..2f8c3fa0d 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -15,6 +15,10 @@
15 app:argType="org.yuzu.yuzu_emu.model.Game" 15 app:argType="org.yuzu.yuzu_emu.model.Game"
16 app:nullable="true" 16 app:nullable="true"
17 android:defaultValue="@null" /> 17 android:defaultValue="@null" />
18 <argument
19 android:name="custom"
20 app:argType="boolean"
21 android:defaultValue="false" />
18 </fragment> 22 </fragment>
19 23
20 <activity 24 <activity
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 1c69bf0db..226cf5600 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -77,6 +77,10 @@
77 app:argType="org.yuzu.yuzu_emu.model.Game" 77 app:argType="org.yuzu.yuzu_emu.model.Game"
78 app:nullable="true" 78 app:nullable="true"
79 android:defaultValue="@null" /> 79 android:defaultValue="@null" />
80 <argument
81 android:name="custom"
82 app:argType="boolean"
83 android:defaultValue="false" />
80 </activity> 84 </activity>
81 85
82 <action 86 <action