summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authort895 <clombardo169@gmail.com>2023-12-11 02:38:58 +0100
committert895 <clombardo169@gmail.com>2023-12-12 23:25:36 +0100
commit2fce81202608cbf4b75fd46e19180fee60e47a53 (patch)
tree6c334f5fc3abe10b35ad5d88687e900dd4c1c0ae
parentandroid: Add Game properties (diff)
downloadyuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.tar
yuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.tar.gz
yuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.tar.bz2
yuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.tar.lz
yuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.tar.xz
yuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.tar.zst
yuzu-2fce81202608cbf4b75fd46e19180fee60e47a53.zip
-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
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.ShortSetting
+import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
@@ -30,9 +31,19 @@ abstract class SettingsItem(
val isEditable: Boolean
get() {
if (!NativeLibrary.isRunning()) return true
+
+ // Prevent editing settings that were modified in per-game config while editing global
+ // config
+ if (!NativeConfig.isPerGameConfigLoaded() && !setting.global) {
+ return false
+ }
return setting.isRuntimeModifiable
}
+ val needsRuntimeGlobal: Boolean
+ get() = NativeLibrary.isRunning() && !setting.global &&
+ !NativeConfig.isPerGameConfigLoaded()
+
companion object {
const val TYPE_HEADER = 0
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
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navArgs
import com.google.android.material.color.MaterialColors
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.NativeLibrary
import java.io.IOException
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -46,6 +45,9 @@ class SettingsActivity : AppCompatActivity() {
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root)
+ if (!NativeConfig.isPerGameConfigLoaded() && args.game != null) {
+ SettingsFile.loadCustomConfig(args.game!!)
+ }
settingsViewModel.game = args.game
val navHostFragment =
@@ -126,7 +128,6 @@ class SettingsActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
- // TODO: Load custom settings contextually
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
@@ -134,24 +135,35 @@ class SettingsActivity : AppCompatActivity() {
override fun onStop() {
super.onStop()
- CoroutineScope(Dispatchers.IO).launch {
- NativeConfig.saveSettings()
+ Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
+ if (isFinishing) {
+ NativeLibrary.applySettings()
+ if (args.game == null) {
+ NativeConfig.saveGlobalConfig()
+ } else if (NativeConfig.isPerGameConfigLoaded()) {
+ NativeLibrary.logSettings()
+ NativeConfig.savePerGameConfig()
+ NativeConfig.unloadPerGameConfig()
+ }
}
}
- override fun onDestroy() {
- settingsViewModel.clear()
- super.onDestroy()
- }
-
fun onSettingsReset() {
// Delete settings file because the user may have changed values that do not exist in the UI
- NativeConfig.unloadConfig()
- val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
- if (!settingsFile.delete()) {
- throw IOException("Failed to delete $settingsFile")
+ if (args.game == null) {
+ NativeConfig.unloadGlobalConfig()
+ val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
+ if (!settingsFile.delete()) {
+ throw IOException("Failed to delete $settingsFile")
+ }
+ NativeConfig.initializeGlobalConfig()
+ } else {
+ NativeConfig.unloadPerGameConfig()
+ val settingsFile = SettingsFile.getCustomSettingsFile(args.game!!)
+ if (!settingsFile.delete()) {
+ throw IOException("Failed to delete $settingsFile")
+ }
}
- NativeConfig.initializeConfig()
Toast.makeText(
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(
return true
}
+ fun onClearClick(item: SettingsItem, position: Int) {
+ item.setting.global = true
+ notifyItemChanged(position)
+ settingsViewModel.setShouldReloadSettingsList(true)
+ }
+
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
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() {
args.menuTag
)
- binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
+ binding.toolbarSettingsLayout.title = if (args.menuTag == Settings.MenuTag.SECTION_ROOT &&
+ args.game != null
+ ) {
+ args.game!!.title
+ } else {
+ getString(args.menuTag.titleId)
+ }
binding.listSettings.apply {
adapter = settingsAdapter
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
import android.os.Build
import android.widget.Toast
import androidx.preference.PreferenceManager
+import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
@@ -31,9 +32,17 @@ class SettingsFragmentPresenter(
private val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- // Extension for populating settings list based on paired settings
+ // Extension for altering settings list based on each setting's properties
fun ArrayList<SettingsItem>.add(key: String) {
val item = SettingsItem.settingsItems[key]!!
+ if (settingsViewModel.game != null && !item.setting.isSwitchable) {
+ return
+ }
+
+ if (!NativeConfig.isPerGameConfigLoaded() && !NativeLibrary.isRunning()) {
+ item.setting.global = true
+ }
+
val pairedSettingKey = item.setting.pairedSettingKey
if (pairedSettingKey.isNotEmpty()) {
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
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.utils.NativeConfig
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -35,6 +36,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
binding.textSettingValue.text = dateFormatter.format(zonedTime)
+ binding.buttonClear.visibility = if (setting.setting.global ||
+ !NativeConfig.isPerGameConfigLoaded()
+ ) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
+ binding.buttonClear.setOnClickListener {
+ adapter.onClearClick(setting, bindingAdapterPosition)
+ }
+
setStyle(setting.isEditable, binding)
}
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
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.GONE
+ binding.buttonClear.visibility = View.GONE
setStyle(setting.isEditable, binding)
}
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
binding.textSettingName.alpha = opacity
binding.textSettingDescription.alpha = opacity
binding.textSettingValue.alpha = opacity
+ binding.buttonClear.isEnabled = isEditable
}
fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
@@ -48,5 +49,6 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
val opacity = if (isEditable) 1.0f else 0.5f
binding.textSettingName.alpha = opacity
binding.textSettingDescription.alpha = opacity
+ binding.buttonClear.isEnabled = isEditable
}
}
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
import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.utils.NativeConfig
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -43,6 +44,17 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
}
}
+ binding.buttonClear.visibility = if (setting.setting.global ||
+ !NativeConfig.isPerGameConfigLoaded()
+ ) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
+ binding.buttonClear.setOnClickListener {
+ adapter.onClearClick(setting, bindingAdapterPosition)
+ }
+
setStyle(setting.isEditable, binding)
}
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
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.utils.NativeConfig
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -30,6 +31,17 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
setting.units
)
+ binding.buttonClear.visibility = if (setting.setting.global ||
+ !NativeConfig.isPerGameConfigLoaded()
+ ) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
+ binding.buttonClear.setOnClickListener {
+ adapter.onClearClick(setting, bindingAdapterPosition)
+ }
+
setStyle(setting.isEditable, binding)
}
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
binding.textSettingDescription.visibility = View.GONE
}
binding.textSettingValue.visibility = View.GONE
+ binding.buttonClear.visibility = View.GONE
}
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
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
+import org.yuzu.yuzu_emu.utils.NativeConfig
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
SettingViewHolder(binding.root, adapter) {
@@ -29,7 +30,18 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
binding.switchWidget.setOnCheckedChangeListener(null)
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
- adapter.onBooleanClick(item, binding.switchWidget.isChecked)
+ adapter.onBooleanClick(item, binding.switchWidget.isChecked, bindingAdapterPosition)
+ }
+
+ binding.buttonClear.visibility = if (setting.setting.global ||
+ !NativeConfig.isPerGameConfigLoaded()
+ ) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
+ binding.buttonClear.setOnClickListener {
+ adapter.onClearClick(setting, bindingAdapterPosition)
}
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 @@
package org.yuzu.yuzu_emu.features.settings.utils
+import android.net.Uri
+import org.yuzu.yuzu_emu.model.Game
import java.io.*
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
+import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.utils.NativeConfig
/**
* Contains static methods for interacting with .ini files in which settings are stored.
*/
object SettingsFile {
- const val FILE_NAME_CONFIG = "config"
+ const val FILE_NAME_CONFIG = "config.ini"
fun getSettingsFile(fileName: String): File =
- File(DirectoryInitialization.userDirectory + "/config/" + fileName + ".ini")
+ File(DirectoryInitialization.userDirectory + "/config/" + fileName)
+
+ fun getCustomSettingsFile(game: Game): File =
+ File(DirectoryInitialization.userDirectory + "/config/custom/" + game.settingsName + ".ini")
+
+ fun loadCustomConfig(game: Game) {
+ val fileName = FileUtil.getFilename(Uri.parse(game.path))
+ NativeConfig.initializePerGameConfig(game.programId, fileName)
+ }
}
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
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel
@@ -127,6 +128,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
return
}
+ if (args.custom) {
+ SettingsFile.loadCustomConfig(args.game!!)
+ NativeConfig.unloadPerGameConfig()
+ } else {
+ NativeConfig.reloadGlobalConfig()
+ }
+
+ // Install the selected driver asynchronously as the game starts
+ driverViewModel.onLaunchGame()
+
// So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -217,6 +228,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
true
}
+ R.id.menu_settings_per_game -> {
+ val action = HomeNavigationDirections.actionGlobalSettingsActivity(
+ args.game,
+ Settings.MenuTag.SECTION_ROOT
+ )
+ binding.root.findNavController().navigate(action)
+ true
+ }
+
R.id.menu_overlay_controls -> {
showOverlayOptions()
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
import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
class GameFolderPropertiesDialogFragment : DialogFragment() {
@@ -49,6 +50,11 @@ class GameFolderPropertiesDialogFragment : DialogFragment() {
.show()
}
+ override fun onStop() {
+ super.onStop()
+ NativeConfig.saveGlobalConfig()
+ }
+
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
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() {
setInsets()
}
+ override fun onStop() {
+ super.onStop()
+ NativeConfig.saveGlobalConfig()
+ }
+
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
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() {
fun onCloseGameFoldersFragment() =
viewModelScope.launch {
withContext(Dispatchers.IO) {
+ NativeConfig.saveGlobalConfig()
getGameDirs(true)
}
}
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() {
fun setAdapterItemChanged(value: Int) {
_adapterItemChanged.value = value
}
-
- fun clear() {
- game = null
- }
}
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
import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors
import com.google.android.material.navigation.NavigationBarView
-import kotlinx.coroutines.CoroutineScope
import java.io.File
import java.io.FilenameFilter
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.HomeNavigationDirections
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
@@ -258,13 +255,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
super.onResume()
}
- override fun onStop() {
- super.onStop()
- CoroutineScope(Dispatchers.IO).launch {
- NativeConfig.saveSettings()
- }
- }
-
override fun onDestroy() {
EmulationActivity.stopForegroundService(this)
super.onDestroy()
@@ -677,7 +667,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
// Clear existing user data
- NativeConfig.unloadConfig()
+ NativeConfig.unloadGlobalConfig()
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
// Copy archive to internal storage
@@ -696,7 +686,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Reinitialize relevant data
NativeLibrary.initializeSystem(true)
- NativeConfig.initializeConfig()
+ NativeConfig.initializeGlobalConfig()
gamesViewModel.reloadGames(false)
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 {
if (!areDirectoriesReady) {
initializeInternalStorage()
NativeLibrary.initializeSystem(false)
- NativeConfig.initializeConfig()
+ NativeConfig.initializeGlobalConfig()
areDirectoriesReady = true
}
}
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
object NativeConfig {
/**
- * Creates a Config object and opens the emulation config.
+ * Loads global config.
*/
@Synchronized
- external fun initializeConfig()
+ external fun initializeGlobalConfig()
/**
- * Destroys the stored config object. This automatically saves the existing config.
+ * Destroys the stored global config object. This does not save the existing config.
*/
@Synchronized
- external fun unloadConfig()
+ external fun unloadGlobalConfig()
/**
- * Reads values saved to the config file and saves them.
+ * Reads values in the global config file and saves them.
*/
@Synchronized
- external fun reloadSettings()
+ external fun reloadGlobalConfig()
/**
- * Saves settings values in memory to disk.
+ * Saves global settings values in memory to disk.
*/
@Synchronized
- external fun saveSettings()
+ external fun saveGlobalConfig()
- external fun getBoolean(key: String, getDefault: Boolean): Boolean
+ /**
+ * Creates per-game config for the specified parameters. Must be unloaded once per-game config
+ * is closed with [unloadPerGameConfig]. All switchable values that [NativeConfig] gets/sets
+ * will follow the per-game config until the global config is reloaded.
+ *
+ * @param programId String representation of the u64 programId
+ * @param fileName Filename of the game, including its extension
+ */
+ @Synchronized
+ external fun initializePerGameConfig(programId: String, fileName: String)
+
+ @Synchronized
+ external fun isPerGameConfigLoaded(): Boolean
+
+ /**
+ * Saves per-game settings values in memory to disk.
+ */
+ @Synchronized
+ external fun savePerGameConfig()
+
+ /**
+ * Destroys the stored per-game config object. This does not save the config.
+ */
+ @Synchronized
+ external fun unloadPerGameConfig()
@Synchronized
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 @@
#include <string>
+#include <common/fs/fs_util.h>
#include <jni.h>
#include "android_config.h"
@@ -12,17 +13,19 @@
#include "frontend_common/config.h"
#include "jni/android_common/android_common.h"
#include "jni/id_cache.h"
+#include "native.h"
-std::unique_ptr<AndroidConfig> config;
+std::unique_ptr<AndroidConfig> global_config;
+std::unique_ptr<AndroidConfig> per_game_config;
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);
}
+ auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
if (basicAndroidSetting != 0) {
return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
}
@@ -32,20 +35,43 @@ Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
extern "C" {
-void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeConfig(JNIEnv* env, jobject obj) {
- config = std::make_unique<AndroidConfig>();
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
+ global_config = std::make_unique<AndroidConfig>();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
+ global_config.reset();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
+ global_config->AndroidConfig::ReloadAllValues();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
+ global_config->AndroidConfig::SaveAllValues();
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj,
+ jstring jprogramId,
+ jstring jfileName) {
+ auto program_id = EmulationSession::GetProgramId(env, jprogramId);
+ auto file_name = GetJString(env, jfileName);
+ const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id);
+ per_game_config =
+ std::make_unique<AndroidConfig>(config_file_name, Config::ConfigType::PerGameConfig);
}
-void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadConfig(JNIEnv* env, jobject obj) {
- config.reset();
+jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_isPerGameConfigLoaded(JNIEnv* env,
+ jobject obj) {
+ return per_game_config != nullptr;
}
-void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadSettings(JNIEnv* env, jobject obj) {
- config->AndroidConfig::ReloadAllValues();
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_savePerGameConfig(JNIEnv* env, jobject obj) {
+ per_game_config->AndroidConfig::SaveAllValues();
}
-void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveSettings(JNIEnv* env, jobject obj) {
- config->AndroidConfig::SaveAllValues();
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadPerGameConfig(JNIEnv* env, jobject obj) {
+ per_game_config.reset();
}
jboolean 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 @@
android:textSize="13sp"
tools:text="1x" />
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button_clear"
+ style="@style/Widget.Material3.Button.TonalButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:visibility="gone"
+ android:text="@string/clear"
+ tools:visibility="visible" />
+
</LinearLayout>
</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 @@
android:minHeight="72dp"
android:padding="16dp">
- <com.google.android.material.materialswitch.MaterialSwitch
- android:id="@+id/switch_widget"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true" />
-
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_centerVertical="true"
- android:layout_marginEnd="24dp"
- android:layout_toStartOf="@+id/switch_widget"
- android:gravity="center_vertical"
android:orientation="vertical">
- <com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_setting_name"
- style="@style/TextAppearance.Material3.HeadlineMedium"
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textAlignment="viewStart"
- android:textSize="17sp"
- app:lineHeight="28dp"
- tools:text="@string/frame_limit_enable" />
-
- <com.google.android.material.textview.MaterialTextView
- android:id="@+id/text_setting_description"
- style="@style/TextAppearance.Material3.BodySmall"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="24dp"
+ android:gravity="center_vertical"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_name"
+ style="@style/TextAppearance.Material3.HeadlineMedium"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textSize="17sp"
+ app:lineHeight="28dp"
+ tools:text="@string/frame_limit_enable" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/text_setting_description"
+ style="@style/TextAppearance.Material3.BodySmall"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacing_small"
+ android:textAlignment="viewStart"
+ tools:text="@string/frame_limit_enable_description" />
+
+ </LinearLayout>
+
+ <com.google.android.material.materialswitch.MaterialSwitch
+ android:id="@+id/switch_widget"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"/>
+
+ </LinearLayout>
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/button_clear"
+ style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/spacing_small"
- android:textAlignment="viewStart"
- tools:text="@string/frame_limit_enable_description" />
+ android:layout_marginTop="16dp"
+ android:text="@string/clear"
+ android:visibility="gone"
+ tools:visibility="visible" />
</LinearLayout>
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 @@
android:title="@string/preferences_settings" />
<item
+ android:id="@+id/menu_settings_per_game"
+ android:icon="@drawable/ic_settings_outline"
+ android:title="@string/per_game_settings" />
+
+ <item
android:id="@+id/menu_overlay_controls"
android:icon="@drawable/ic_controller"
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 @@
app:argType="org.yuzu.yuzu_emu.model.Game"
app:nullable="true"
android:defaultValue="@null" />
+ <argument
+ android:name="custom"
+ app:argType="boolean"
+ android:defaultValue="false" />
</fragment>
<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 @@
app:argType="org.yuzu.yuzu_emu.model.Game"
app:nullable="true"
android:defaultValue="@null" />
+ <argument
+ android:name="custom"
+ app:argType="boolean"
+ android:defaultValue="false" />
</activity>
<action