From 2fce81202608cbf4b75fd46e19180fee60e47a53 Mon Sep 17 00:00:00 2001 From: t895 Date: Sun, 10 Dec 2023 20:38:58 -0500 Subject: [PATCH] android: Add per-game settings --- .../settings/model/view/SettingsItem.kt | 11 +++ .../features/settings/ui/SettingsActivity.kt | 42 +++++++---- .../features/settings/ui/SettingsAdapter.kt | 6 ++ .../features/settings/ui/SettingsFragment.kt | 8 +- .../settings/ui/SettingsFragmentPresenter.kt | 11 ++- .../ui/viewholder/DateTimeViewHolder.kt | 12 +++ .../ui/viewholder/RunnableViewHolder.kt | 1 + .../ui/viewholder/SettingViewHolder.kt | 2 + .../ui/viewholder/SingleChoiceViewHolder.kt | 12 +++ .../ui/viewholder/SliderViewHolder.kt | 12 +++ .../ui/viewholder/SubmenuViewHolder.kt | 1 + .../ui/viewholder/SwitchSettingViewHolder.kt | 14 +++- .../features/settings/utils/SettingsFile.kt | 16 +++- .../yuzu_emu/fragments/EmulationFragment.kt | 20 +++++ .../GameFolderPropertiesDialogFragment.kt | 6 ++ .../yuzu/yuzu_emu/fragments/SetupFragment.kt | 5 ++ .../org/yuzu/yuzu_emu/model/GamesViewModel.kt | 1 + .../yuzu/yuzu_emu/model/SettingsViewModel.kt | 4 - .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 14 +--- .../yuzu_emu/utils/DirectoryInitialization.kt | 2 +- .../org/yuzu/yuzu_emu/utils/NativeConfig.kt | 42 ++++++++--- .../app/src/main/jni/native_config.cpp | 46 +++++++++--- .../src/main/res/layout/list_item_setting.xml | 10 +++ .../res/layout/list_item_setting_switch.xml | 73 ++++++++++++------- .../app/src/main/res/menu/menu_in_game.xml | 5 ++ .../res/navigation/emulation_navigation.xml | 4 + .../main/res/navigation/home_navigation.xml | 4 + 27 files changed, 302 insertions(+), 82 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() { 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.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 +#include #include #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 config; +std::unique_ptr global_config; +std::unique_ptr per_game_config; template Settings::Setting* 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*>(basicSetting); } + auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key]; if (basicAndroidSetting != 0) { return static_cast*>(basicAndroidSetting); } @@ -32,20 +35,43 @@ Settings::Setting* getSetting(JNIEnv* env, jstring jkey) { extern "C" { -void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeConfig(JNIEnv* env, jobject obj) { - config = std::make_unique(); +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) { + global_config = std::make_unique(); } -void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadConfig(JNIEnv* env, jobject obj) { - config.reset(); +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) { + global_config.reset(); } -void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadSettings(JNIEnv* env, jobject obj) { - config->AndroidConfig::ReloadAllValues(); +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_saveSettings(JNIEnv* env, jobject obj) { - config->AndroidConfig::SaveAllValues(); +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(config_file_name, Config::ConfigType::PerGameConfig); +} + +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_savePerGameConfig(JNIEnv* env, jobject obj) { + per_game_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" /> + + 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"> - - - + android:orientation="horizontal"> - + + + + + + + + + + + + + android:layout_marginTop="16dp" + android:text="@string/clear" + android:visibility="gone" + tools:visibility="visible" /> 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 @@ -11,6 +11,11 @@ android:icon="@drawable/ic_settings" android:title="@string/preferences_settings" /> + + + +