mirror of
https://github.com/yuzu-emu/yuzu-android
synced 2024-12-23 11:51:21 -08:00
android: Add per-game settings
This commit is contained in:
parent
e975f3cde9
commit
2fce812026
@ -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.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
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.
|
* ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments.
|
||||||
@ -30,9 +31,19 @@ abstract class SettingsItem(
|
|||||||
val isEditable: Boolean
|
val isEditable: Boolean
|
||||||
get() {
|
get() {
|
||||||
if (!NativeLibrary.isRunning()) return true
|
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
|
return setting.isRuntimeModifiable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val needsRuntimeGlobal: Boolean
|
||||||
|
get() = NativeLibrary.isRunning() && !setting.global &&
|
||||||
|
!NativeConfig.isPerGameConfigLoaded()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE_HEADER = 0
|
const val TYPE_HEADER = 0
|
||||||
const val TYPE_SWITCH = 1
|
const val TYPE_SWITCH = 1
|
||||||
|
@ -19,10 +19,9 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.navArgs
|
import androidx.navigation.navArgs
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
||||||
@ -46,6 +45,9 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
binding = ActivitySettingsBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
if (!NativeConfig.isPerGameConfigLoaded() && args.game != null) {
|
||||||
|
SettingsFile.loadCustomConfig(args.game!!)
|
||||||
|
}
|
||||||
settingsViewModel.game = args.game
|
settingsViewModel.game = args.game
|
||||||
|
|
||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
@ -126,7 +128,6 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
// TODO: Load custom settings contextually
|
|
||||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||||
DirectoryInitialization.start()
|
DirectoryInitialization.start()
|
||||||
}
|
}
|
||||||
@ -134,24 +135,35 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
Log.info("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
|
||||||
NativeConfig.saveSettings()
|
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() {
|
fun onSettingsReset() {
|
||||||
// Delete settings file because the user may have changed values that do not exist in the UI
|
// Delete settings file because the user may have changed values that do not exist in the UI
|
||||||
NativeConfig.unloadConfig()
|
if (args.game == null) {
|
||||||
|
NativeConfig.unloadGlobalConfig()
|
||||||
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
|
val settingsFile = SettingsFile.getSettingsFile(SettingsFile.FILE_NAME_CONFIG)
|
||||||
if (!settingsFile.delete()) {
|
if (!settingsFile.delete()) {
|
||||||
throw IOException("Failed to delete $settingsFile")
|
throw IOException("Failed to delete $settingsFile")
|
||||||
}
|
}
|
||||||
NativeConfig.initializeConfig()
|
NativeConfig.initializeGlobalConfig()
|
||||||
|
} else {
|
||||||
|
NativeConfig.unloadPerGameConfig()
|
||||||
|
val settingsFile = SettingsFile.getCustomSettingsFile(args.game!!)
|
||||||
|
if (!settingsFile.delete()) {
|
||||||
|
throw IOException("Failed to delete $settingsFile")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
|
@ -196,6 +196,12 @@ class SettingsAdapter(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onClearClick(item: SettingsItem, position: Int) {
|
||||||
|
item.setting.global = true
|
||||||
|
notifyItemChanged(position)
|
||||||
|
settingsViewModel.setShouldReloadSettingsList(true)
|
||||||
|
}
|
||||||
|
|
||||||
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
|
private class DiffCallback : DiffUtil.ItemCallback<SettingsItem>() {
|
||||||
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
|
override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean {
|
||||||
return oldItem.setting.key == newItem.setting.key
|
return oldItem.setting.key == newItem.setting.key
|
||||||
|
@ -66,7 +66,13 @@ class SettingsFragment : Fragment() {
|
|||||||
args.menuTag
|
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 {
|
binding.listSettings.apply {
|
||||||
adapter = settingsAdapter
|
adapter = settingsAdapter
|
||||||
layoutManager = LinearLayoutManager(requireContext())
|
layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
@ -7,6 +7,7 @@ import android.content.SharedPreferences
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||||
@ -31,9 +32,17 @@ class SettingsFragmentPresenter(
|
|||||||
private val preferences: SharedPreferences
|
private val preferences: SharedPreferences
|
||||||
get() = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
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) {
|
fun ArrayList<SettingsItem>.add(key: String) {
|
||||||
val item = SettingsItem.settingsItems[key]!!
|
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
|
val pairedSettingKey = item.setting.pairedSettingKey
|
||||||
if (pairedSettingKey.isNotEmpty()) {
|
if (pairedSettingKey.isNotEmpty()) {
|
||||||
val pairedSettingValue = NativeConfig.getBoolean(
|
val pairedSettingValue = NativeConfig.getBoolean(
|
||||||
|
@ -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.DateTimeSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder(binding.root, adapter) {
|
||||||
@ -35,6 +36,17 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||||||
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
||||||
binding.textSettingValue.text = dateFormatter.format(zonedTime)
|
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)
|
setStyle(setting.isEditable, binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
|
|||||||
binding.textSettingDescription.visibility = View.GONE
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
}
|
}
|
||||||
binding.textSettingValue.visibility = View.GONE
|
binding.textSettingValue.visibility = View.GONE
|
||||||
|
binding.buttonClear.visibility = View.GONE
|
||||||
|
|
||||||
setStyle(setting.isEditable, binding)
|
setStyle(setting.isEditable, binding)
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ abstract class SettingViewHolder(itemView: View, protected val adapter: Settings
|
|||||||
binding.textSettingName.alpha = opacity
|
binding.textSettingName.alpha = opacity
|
||||||
binding.textSettingDescription.alpha = opacity
|
binding.textSettingDescription.alpha = opacity
|
||||||
binding.textSettingValue.alpha = opacity
|
binding.textSettingValue.alpha = opacity
|
||||||
|
binding.buttonClear.isEnabled = isEditable
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setStyle(isEditable: Boolean, binding: ListItemSettingSwitchBinding) {
|
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
|
val opacity = if (isEditable) 1.0f else 0.5f
|
||||||
binding.textSettingName.alpha = opacity
|
binding.textSettingName.alpha = opacity
|
||||||
binding.textSettingDescription.alpha = opacity
|
binding.textSettingDescription.alpha = opacity
|
||||||
|
binding.buttonClear.isEnabled = isEditable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.SingleChoiceSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
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)
|
setStyle(setting.isEditable, binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder(binding.root, adapter) {
|
||||||
@ -30,6 +31,17 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
|
|||||||
setting.units
|
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)
|
setStyle(setting.isEditable, binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ class SubmenuViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAd
|
|||||||
binding.textSettingDescription.visibility = View.GONE
|
binding.textSettingDescription.visibility = View.GONE
|
||||||
}
|
}
|
||||||
binding.textSettingValue.visibility = View.GONE
|
binding.textSettingValue.visibility = View.GONE
|
||||||
|
binding.buttonClear.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(clicked: View) {
|
override fun onClick(clicked: View) {
|
||||||
|
@ -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.SettingsItem
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
|
|
||||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
||||||
SettingViewHolder(binding.root, adapter) {
|
SettingViewHolder(binding.root, adapter) {
|
||||||
@ -29,7 +30,18 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
|
|||||||
binding.switchWidget.setOnCheckedChangeListener(null)
|
binding.switchWidget.setOnCheckedChangeListener(null)
|
||||||
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
|
binding.switchWidget.isChecked = setting.getIsChecked(setting.needsRuntimeGlobal)
|
||||||
binding.switchWidget.setOnCheckedChangeListener { _: CompoundButton, _: Boolean ->
|
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)
|
setStyle(setting.isEditable, binding)
|
||||||
|
@ -3,15 +3,27 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.features.settings.utils
|
package org.yuzu.yuzu_emu.features.settings.utils
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
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.
|
* Contains static methods for interacting with .ini files in which settings are stored.
|
||||||
*/
|
*/
|
||||||
object SettingsFile {
|
object SettingsFile {
|
||||||
const val FILE_NAME_CONFIG = "config"
|
const val FILE_NAME_CONFIG = "config.ini"
|
||||||
|
|
||||||
fun getSettingsFile(fileName: String): File =
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
|
|||||||
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||||
@ -127,6 +128,16 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
return
|
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.
|
// So this fragment doesn't restart on configuration changes; i.e. rotation.
|
||||||
retainInstance = true
|
retainInstance = true
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
@ -217,6 +228,15 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
true
|
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 -> {
|
R.id.menu_overlay_controls -> {
|
||||||
showOverlayOptions()
|
showOverlayOptions()
|
||||||
true
|
true
|
||||||
|
@ -13,6 +13,7 @@ import org.yuzu.yuzu_emu.R
|
|||||||
import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
|
import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
|
||||||
import org.yuzu.yuzu_emu.model.GameDir
|
import org.yuzu.yuzu_emu.model.GameDir
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
|
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
|
||||||
|
|
||||||
class GameFolderPropertiesDialogFragment : DialogFragment() {
|
class GameFolderPropertiesDialogFragment : DialogFragment() {
|
||||||
@ -49,6 +50,11 @@ class GameFolderPropertiesDialogFragment : DialogFragment() {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
NativeConfig.saveGlobalConfig()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
outState.putBoolean(DEEP_SCAN, deepScan)
|
outState.putBoolean(DEEP_SCAN, deepScan)
|
||||||
|
@ -304,6 +304,11 @@ class SetupFragment : Fragment() {
|
|||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
NativeConfig.saveGlobalConfig()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
if (_binding != null) {
|
if (_binding != null) {
|
||||||
|
@ -168,6 +168,7 @@ class GamesViewModel : ViewModel() {
|
|||||||
fun onCloseGameFoldersFragment() =
|
fun onCloseGameFoldersFragment() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
NativeConfig.saveGlobalConfig()
|
||||||
getGameDirs(true)
|
getGameDirs(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,8 +68,4 @@ class SettingsViewModel : ViewModel() {
|
|||||||
fun setAdapterItemChanged(value: Int) {
|
fun setAdapterItemChanged(value: Int) {
|
||||||
_adapterItemChanged.value = value
|
_adapterItemChanged.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
|
||||||
game = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,9 @@ import androidx.navigation.ui.setupWithNavController
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FilenameFilter
|
import java.io.FilenameFilter
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
@ -258,13 +255,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
NativeConfig.saveSettings()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
EmulationActivity.stopForegroundService(this)
|
EmulationActivity.stopForegroundService(this)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
@ -677,7 +667,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear existing user data
|
// Clear existing user data
|
||||||
NativeConfig.unloadConfig()
|
NativeConfig.unloadGlobalConfig()
|
||||||
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
|
File(DirectoryInitialization.userDirectory!!).deleteRecursively()
|
||||||
|
|
||||||
// Copy archive to internal storage
|
// Copy archive to internal storage
|
||||||
@ -696,7 +686,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
|
|
||||||
// Reinitialize relevant data
|
// Reinitialize relevant data
|
||||||
NativeLibrary.initializeSystem(true)
|
NativeLibrary.initializeSystem(true)
|
||||||
NativeConfig.initializeConfig()
|
NativeConfig.initializeGlobalConfig()
|
||||||
gamesViewModel.reloadGames(false)
|
gamesViewModel.reloadGames(false)
|
||||||
|
|
||||||
return@newInstance getString(R.string.user_data_import_success)
|
return@newInstance getString(R.string.user_data_import_success)
|
||||||
|
@ -16,7 +16,7 @@ object DirectoryInitialization {
|
|||||||
if (!areDirectoriesReady) {
|
if (!areDirectoriesReady) {
|
||||||
initializeInternalStorage()
|
initializeInternalStorage()
|
||||||
NativeLibrary.initializeSystem(false)
|
NativeLibrary.initializeSystem(false)
|
||||||
NativeConfig.initializeConfig()
|
NativeConfig.initializeGlobalConfig()
|
||||||
areDirectoriesReady = true
|
areDirectoriesReady = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,30 +7,54 @@ import org.yuzu.yuzu_emu.model.GameDir
|
|||||||
|
|
||||||
object NativeConfig {
|
object NativeConfig {
|
||||||
/**
|
/**
|
||||||
* Creates a Config object and opens the emulation config.
|
* Loads global config.
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@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
|
@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
|
@Synchronized
|
||||||
external fun reloadSettings()
|
external fun reloadGlobalConfig()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves settings values in memory to disk.
|
* Saves global settings values in memory to disk.
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@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
|
@Synchronized
|
||||||
external fun getBoolean(key: String, needsGlobal: Boolean): Boolean
|
external fun getBoolean(key: String, needsGlobal: Boolean): Boolean
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <common/fs/fs_util.h>
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
#include "android_config.h"
|
#include "android_config.h"
|
||||||
@ -12,17 +13,19 @@
|
|||||||
#include "frontend_common/config.h"
|
#include "frontend_common/config.h"
|
||||||
#include "jni/android_common/android_common.h"
|
#include "jni/android_common/android_common.h"
|
||||||
#include "jni/id_cache.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>
|
template <typename T>
|
||||||
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
||||||
auto key = GetJString(env, jkey);
|
auto key = GetJString(env, jkey);
|
||||||
auto basicSetting = Settings::values.linkage.by_key[key];
|
auto basicSetting = Settings::values.linkage.by_key[key];
|
||||||
auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
|
|
||||||
if (basicSetting != 0) {
|
if (basicSetting != 0) {
|
||||||
return static_cast<Settings::Setting<T>*>(basicSetting);
|
return static_cast<Settings::Setting<T>*>(basicSetting);
|
||||||
}
|
}
|
||||||
|
auto basicAndroidSetting = AndroidSettings::values.linkage.by_key[key];
|
||||||
if (basicAndroidSetting != 0) {
|
if (basicAndroidSetting != 0) {
|
||||||
return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
|
return static_cast<Settings::Setting<T>*>(basicAndroidSetting);
|
||||||
}
|
}
|
||||||
@ -32,20 +35,43 @@ Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
config = std::make_unique<AndroidConfig>();
|
global_config = std::make_unique<AndroidConfig>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadConfig(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
config.reset();
|
global_config.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadSettings(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
config->AndroidConfig::ReloadAllValues();
|
global_config->AndroidConfig::ReloadAllValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveSettings(JNIEnv* env, jobject obj) {
|
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
|
||||||
config->AndroidConfig::SaveAllValues();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getBoolean(JNIEnv* env, jobject obj,
|
||||||
|
@ -62,6 +62,16 @@
|
|||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
tools:text="1x" />
|
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>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -10,22 +10,23 @@
|
|||||||
android:minHeight="72dp"
|
android:minHeight="72dp"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
<LinearLayout
|
||||||
android:id="@+id/switch_widget"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
android:orientation="vertical">
|
||||||
android:layout_centerVertical="true" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentTop="true"
|
android:orientation="horizontal">
|
||||||
android:layout_centerVertical="true"
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:layout_toStartOf="@+id/switch_widget"
|
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/text_setting_name"
|
android:id="@+id/text_setting_name"
|
||||||
@ -48,4 +49,24 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</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="16dp"
|
||||||
|
android:text="@string/clear"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
@ -11,6 +11,11 @@
|
|||||||
android:icon="@drawable/ic_settings"
|
android:icon="@drawable/ic_settings"
|
||||||
android:title="@string/preferences_settings" />
|
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
|
<item
|
||||||
android:id="@+id/menu_overlay_controls"
|
android:id="@+id/menu_overlay_controls"
|
||||||
android:icon="@drawable/ic_controller"
|
android:icon="@drawable/ic_controller"
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||||
app:nullable="true"
|
app:nullable="true"
|
||||||
android:defaultValue="@null" />
|
android:defaultValue="@null" />
|
||||||
|
<argument
|
||||||
|
android:name="custom"
|
||||||
|
app:argType="boolean"
|
||||||
|
android:defaultValue="false" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
|
@ -77,6 +77,10 @@
|
|||||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||||
app:nullable="true"
|
app:nullable="true"
|
||||||
android:defaultValue="@null" />
|
android:defaultValue="@null" />
|
||||||
|
<argument
|
||||||
|
android:name="custom"
|
||||||
|
app:argType="boolean"
|
||||||
|
android:defaultValue="false" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<action
|
<action
|
||||||
|
Loading…
Reference in New Issue
Block a user