diff options
Diffstat (limited to 'src')
207 files changed, 10354 insertions, 2402 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 5721327e7..f763c657e 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -174,7 +174,8 @@ android { "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work "-DYUZU_USE_BUNDLED_VCPKG=ON", "-DYUZU_USE_BUNDLED_FFMPEG=ON", - "-DYUZU_ENABLE_LTO=ON" + "-DYUZU_ENABLE_LTO=ON", + "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" ) abiFilters("arm64-v8a", "x86_64") diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index e0f01127c..010c44951 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -230,8 +230,6 @@ object NativeLibrary { */ external fun onTouchReleased(finger_id: Int) - external fun initGameIni(gameID: String?) - external fun setAppDirectory(directory: String) /** @@ -241,6 +239,8 @@ object NativeLibrary { */ external fun installFileToNand(filename: String, extension: String): Int + external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean + external fun initializeGpuDriver( hookLibDir: String?, customDriverDir: String?, @@ -252,18 +252,11 @@ object NativeLibrary { external fun initializeSystem(reload: Boolean) - external fun defaultCPUCore(): Int - /** * Begins emulation. */ external fun run(path: String?) - /** - * Begins emulation from the specified savestate. - */ - external fun run(path: String?, savestatePath: String?, deleteSavestate: Boolean) - // Surface Handling external fun surfaceChanged(surf: Surface?) @@ -304,10 +297,9 @@ object NativeLibrary { */ external fun getCpuBackend(): String - /** - * Notifies the core emulation that the orientation has changed. - */ - external fun notifyOrientationChange(layout_option: Int, rotation: Int) + external fun applySettings() + + external fun logSettings() enum class CoreError { ErrorSystemFiles, @@ -539,6 +531,35 @@ object NativeLibrary { external fun isFirmwareAvailable(): Boolean /** + * Checks the PatchManager for any addons that are available + * + * @param path Path to game file. Can be a [Uri]. + * @param programId String representation of a game's program ID + * @return Array of pairs where the first value is the name of an addon and the second is the version + */ + external fun getAddonsForFile(path: String, programId: String): Array<Pair<String, String>>? + + /** + * Gets the save location for a specific game + * + * @param programId String representation of a game's program ID + * @return Save data path that may not exist yet + */ + external fun getSavePath(programId: String): String + + /** + * Adds a file to the manual filesystem provider in our EmulationSession instance + * @param path Path to the file we're adding. Can be a string representation of a [Uri] or + * a normal path + */ + external fun addFileToFilesystemProvider(path: String) + + /** + * Clears all files added to the manual filesystem provider in our EmulationSession instance + */ + external fun clearFilesystemProvider() + + /** * Button type for use in onTouchEvent */ object ButtonType { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index f41d7bdbf..9b08f008d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -172,7 +172,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { override fun onUserLeaveHint() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - if (BooleanSetting.PICTURE_IN_PICTURE.boolean && !isInPictureInPictureMode) { + if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) { val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() enterPictureInPictureMode(pictureInPictureParamsBuilder.build()) @@ -284,7 +284,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): PictureInPictureParams.Builder { - val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) { + val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.getInt()) { 0 -> Rational(16, 9) 1 -> Rational(4, 3) 2 -> Rational(21, 9) @@ -331,7 +331,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { pictureInPictureActions.add(pauseRemoteAction) } - if (BooleanSetting.AUDIO_MUTED.boolean) { + if (BooleanSetting.AUDIO_MUTED.getBoolean()) { val unmuteIcon = Icon.createWithResource( this@EmulationActivity, R.drawable.ic_pip_unmute @@ -376,7 +376,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { val isEmulationActive = emulationViewModel.emulationStarted.value && !emulationViewModel.isEmulationStopping.value pictureInPictureParamsBuilder.setAutoEnterEnabled( - BooleanSetting.PICTURE_IN_PICTURE.boolean && isEmulationActive + BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && isEmulationActive ) } setPictureInPictureParams(pictureInPictureParamsBuilder.build()) @@ -390,9 +390,13 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() } if (intent.action == actionUnmute) { - if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false) + if (BooleanSetting.AUDIO_MUTED.getBoolean()) { + BooleanSetting.AUDIO_MUTED.setBoolean(false) + } } else if (intent.action == actionMute) { - if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true) + if (!BooleanSetting.AUDIO_MUTED.getBoolean()) { + BooleanSetting.AUDIO_MUTED.setBoolean(true) + } } buildPictureInPictureParams() } @@ -423,7 +427,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { } catch (ignored: Exception) { } // Always resume audio, since there is no UI button - if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false) + if (BooleanSetting.AUDIO_MUTED.getBoolean()) { + BooleanSetting.AUDIO_MUTED.setBoolean(false) + } } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt new file mode 100644 index 000000000..15c7ca3c9 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.AsyncDifferConfig +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding +import org.yuzu.yuzu_emu.model.Addon + +class AddonAdapter : ListAdapter<Addon, AddonAdapter.AddonViewHolder>( + AsyncDifferConfig.Builder(DiffCallback()).build() +) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AddonViewHolder { + ListItemAddonBinding.inflate(LayoutInflater.from(parent.context), parent, false) + .also { return AddonViewHolder(it) } + } + + override fun getItemCount(): Int = currentList.size + + override fun onBindViewHolder(holder: AddonViewHolder, position: Int) = + holder.bind(currentList[position]) + + inner class AddonViewHolder(val binding: ListItemAddonBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind(addon: Addon) { + binding.root.setOnClickListener { + binding.addonSwitch.isChecked = !binding.addonSwitch.isChecked + } + binding.title.text = addon.title + binding.version.text = addon.version + binding.addonSwitch.setOnCheckedChangeListener { _, checked -> + addon.enabled = checked + } + binding.addonSwitch.isChecked = addon.enabled + } + } + + private class DiffCallback : DiffUtil.ItemCallback<Addon>() { + override fun areItemsTheSame(oldItem: Addon, newItem: Addon): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: Addon, newItem: Addon): Boolean { + return oldItem == newItem + } + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt index a21a705c1..4a05c5be9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AppletAdapter.kt @@ -15,7 +15,7 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication -import org.yuzu.yuzu_emu.databinding.CardAppletOptionBinding +import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding import org.yuzu.yuzu_emu.model.Applet import org.yuzu.yuzu_emu.model.AppletInfo import org.yuzu.yuzu_emu.model.Game @@ -28,7 +28,7 @@ class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) : parent: ViewGroup, viewType: Int ): AppletAdapter.AppletViewHolder { - CardAppletOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) + CardSimpleOutlinedBinding.inflate(LayoutInflater.from(parent.context), parent, false) .apply { root.setOnClickListener(this@AppletAdapter) } .also { return AppletViewHolder(it) } } @@ -65,7 +65,7 @@ class AppletAdapter(val activity: FragmentActivity, var applets: List<Applet>) : view.findNavController().navigate(action) } - inner class AppletViewHolder(val binding: CardAppletOptionBinding) : + inner class AppletViewHolder(val binding: CardSimpleOutlinedBinding) : RecyclerView.ViewHolder(binding.root) { lateinit var applet: Applet diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt index 0e818cab9..d290a656c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt @@ -42,7 +42,7 @@ class DriverAdapter(private val driverViewModel: DriverViewModel) : if (driverViewModel.selectedDriver > position) { driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1) } - if (GpuDriverHelper.customDriverData == driverData.second) { + if (GpuDriverHelper.customDriverSettingData == driverData.second) { driverViewModel.setSelectedDriverIndex(0) } driverViewModel.driversToDelete.add(driverData.first) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 2ef638559..a578f0de8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -44,19 +44,20 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils class GameAdapter(private val activity: AppCompatActivity) : ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()), - View.OnClickListener { + View.OnClickListener, + View.OnLongClickListener { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder { // Create a new view. val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false) binding.cardGame.setOnClickListener(this) + binding.cardGame.setOnLongClickListener(this) // Use that view to create a ViewHolder. return GameViewHolder(binding) } - override fun onBindViewHolder(holder: GameViewHolder, position: Int) { + override fun onBindViewHolder(holder: GameViewHolder, position: Int) = holder.bind(currentList[position]) - } override fun getItemCount(): Int = currentList.size @@ -125,8 +126,15 @@ class GameAdapter(private val activity: AppCompatActivity) : } } - val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game) + val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game, true) + view.findNavController().navigate(action) + } + + override fun onLongClick(view: View): Boolean { + val holder = view.tag as GameViewHolder + val action = HomeNavigationDirections.actionGlobalPerGamePropertiesFragment(holder.game) view.findNavController().navigate(action) + return true } inner class GameViewHolder(val binding: CardGameBinding) : @@ -157,7 +165,7 @@ class GameAdapter(private val activity: AppCompatActivity) : private class DiffCallback : DiffUtil.ItemCallback<Game>() { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { - return oldItem.programId == newItem.programId + return oldItem == newItem } override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt new file mode 100644 index 000000000..95841d786 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GamePropertiesAdapter.kt @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.adapters + +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.res.ResourcesCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.launch +import org.yuzu.yuzu_emu.databinding.CardInstallableIconBinding +import org.yuzu.yuzu_emu.databinding.CardSimpleOutlinedBinding +import org.yuzu.yuzu_emu.model.GameProperty +import org.yuzu.yuzu_emu.model.InstallableProperty +import org.yuzu.yuzu_emu.model.SubmenuProperty + +class GamePropertiesAdapter( + private val viewLifecycle: LifecycleOwner, + private var properties: List<GameProperty> +) : + RecyclerView.Adapter<GamePropertiesAdapter.GamePropertyViewHolder>() { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): GamePropertyViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + PropertyType.Submenu.ordinal -> { + SubmenuPropertyViewHolder( + CardSimpleOutlinedBinding.inflate( + inflater, + parent, + false + ) + ) + } + + else -> InstallablePropertyViewHolder( + CardInstallableIconBinding.inflate( + inflater, + parent, + false + ) + ) + } + } + + override fun getItemCount(): Int = properties.size + + override fun onBindViewHolder(holder: GamePropertyViewHolder, position: Int) = + holder.bind(properties[position]) + + override fun getItemViewType(position: Int): Int { + return when (properties[position]) { + is SubmenuProperty -> PropertyType.Submenu.ordinal + else -> PropertyType.Installable.ordinal + } + } + + sealed class GamePropertyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + abstract fun bind(property: GameProperty) + } + + inner class SubmenuPropertyViewHolder(val binding: CardSimpleOutlinedBinding) : + GamePropertyViewHolder(binding.root) { + override fun bind(property: GameProperty) { + val submenuProperty = property as SubmenuProperty + + binding.root.setOnClickListener { + submenuProperty.action.invoke() + } + + binding.title.setText(submenuProperty.titleId) + binding.description.setText(submenuProperty.descriptionId) + binding.icon.setImageDrawable( + ResourcesCompat.getDrawable( + binding.icon.context.resources, + submenuProperty.iconId, + binding.icon.context.theme + ) + ) + + binding.details.postDelayed({ + binding.details.isSelected = true + binding.details.ellipsize = TextUtils.TruncateAt.MARQUEE + }, 3000) + + if (submenuProperty.details != null) { + binding.details.visibility = View.VISIBLE + binding.details.text = submenuProperty.details.invoke() + } else if (submenuProperty.detailsFlow != null) { + binding.details.visibility = View.VISIBLE + viewLifecycle.lifecycleScope.launch { + viewLifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + submenuProperty.detailsFlow.collect { binding.details.text = it } + } + } + } else { + binding.details.visibility = View.GONE + } + } + } + + inner class InstallablePropertyViewHolder(val binding: CardInstallableIconBinding) : + GamePropertyViewHolder(binding.root) { + override fun bind(property: GameProperty) { + val installableProperty = property as InstallableProperty + + binding.title.setText(installableProperty.titleId) + binding.description.setText(installableProperty.descriptionId) + binding.icon.setImageDrawable( + ResourcesCompat.getDrawable( + binding.icon.context.resources, + installableProperty.iconId, + binding.icon.context.theme + ) + ) + + if (installableProperty.install != null) { + binding.buttonInstall.visibility = View.VISIBLE + binding.buttonInstall.setOnClickListener { installableProperty.install.invoke() } + } + if (installableProperty.export != null) { + binding.buttonExport.visibility = View.VISIBLE + binding.buttonExport.setOnClickListener { installableProperty.export.invoke() } + } + } + } + + enum class PropertyType { + Submenu, + Installable + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt index aeda8d222..0ba465356 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractBooleanSetting.kt @@ -4,7 +4,6 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractBooleanSetting : AbstractSetting { - val boolean: Boolean - + fun getBoolean(needsGlobal: Boolean = false): Boolean fun setBoolean(value: Boolean) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt index 606519ad8..cf6300535 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractByteSetting.kt @@ -4,7 +4,6 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractByteSetting : AbstractSetting { - val byte: Byte - + fun getByte(needsGlobal: Boolean = false): Byte fun setByte(value: Byte) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt index 974925eed..c6c0bcf34 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractFloatSetting.kt @@ -4,7 +4,6 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractFloatSetting : AbstractSetting { - val float: Float - + fun getFloat(needsGlobal: Boolean = false): Float fun setFloat(value: Float) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt index 89b285b10..826402c34 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractIntSetting.kt @@ -4,7 +4,6 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractIntSetting : AbstractSetting { - val int: Int - + fun getInt(needsGlobal: Boolean = false): Int fun setInt(value: Int) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt index 4873942db..2b62cc06b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractLongSetting.kt @@ -4,7 +4,6 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractLongSetting : AbstractSetting { - val long: Long - + fun getLong(needsGlobal: Boolean = false): Long fun setLong(value: Long) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt index 8b6d29fe5..3b78c7cf0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractSetting.kt @@ -7,12 +7,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig interface AbstractSetting { val key: String - val category: Settings.Category val defaultValue: Any - val androidDefault: Any? - get() = null - val valueAsString: String - get() = "" val isRuntimeModifiable: Boolean get() = NativeConfig.getIsRuntimeModifiable(key) @@ -20,5 +15,17 @@ interface AbstractSetting { val pairedSettingKey: String get() = NativeConfig.getPairedSettingKey(key) + val isSwitchable: Boolean + get() = NativeConfig.getIsSwitchable(key) + + var global: Boolean + get() = NativeConfig.usingGlobal(key) + set(value) = NativeConfig.setGlobal(key, value) + + val isSaveable: Boolean + get() = NativeConfig.getIsSaveable(key) + + fun getValueAsString(needsGlobal: Boolean = false): String + fun reset() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt index 91407ccbb..8bfa81e4a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractShortSetting.kt @@ -4,7 +4,6 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractShortSetting : AbstractSetting { - val short: Short - + fun getShort(needsGlobal: Boolean = false): Short fun setShort(value: Short) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt index c8935cc48..6ff8fd3f9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/AbstractStringSetting.kt @@ -4,7 +4,6 @@ package org.yuzu.yuzu_emu.features.settings.model interface AbstractStringSetting : AbstractSetting { - val string: String - + fun getString(needsGlobal: Boolean = false): String fun setString(value: String) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 8476ce867..16f06cd0a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -5,36 +5,34 @@ package org.yuzu.yuzu_emu.features.settings.model import org.yuzu.yuzu_emu.utils.NativeConfig -enum class BooleanSetting( - override val key: String, - override val category: Settings.Category, - override val androidDefault: Boolean? = null -) : AbstractBooleanSetting { - AUDIO_MUTED("audio_muted", Settings.Category.Audio), - CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu), - FASTMEM("cpuopt_fastmem", Settings.Category.Cpu), - FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu), - RENDERER_USE_SPEED_LIMIT("use_speed_limit", Settings.Category.Core), - USE_DOCKED_MODE("use_docked_mode", Settings.Category.System, false), - RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache", Settings.Category.Renderer), - RENDERER_FORCE_MAX_CLOCK("force_max_clock", Settings.Category.Renderer), - RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders", Settings.Category.Renderer), - RENDERER_REACTIVE_FLUSHING("use_reactive_flushing", Settings.Category.Renderer, false), - RENDERER_DEBUG("debug", Settings.Category.Renderer), - PICTURE_IN_PICTURE("picture_in_picture", Settings.Category.Android), - USE_CUSTOM_RTC("custom_rtc_enabled", Settings.Category.System); - - override val boolean: Boolean - get() = NativeConfig.getBoolean(key, false) - - override fun setBoolean(value: Boolean) = NativeConfig.setBoolean(key, value) - - override val defaultValue: Boolean by lazy { - androidDefault ?: NativeConfig.getBoolean(key, true) +enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { + AUDIO_MUTED("audio_muted"), + CPU_DEBUG_MODE("cpu_debug_mode"), + FASTMEM("cpuopt_fastmem"), + FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives"), + RENDERER_USE_SPEED_LIMIT("use_speed_limit"), + USE_DOCKED_MODE("use_docked_mode"), + RENDERER_USE_DISK_SHADER_CACHE("use_disk_shader_cache"), + RENDERER_FORCE_MAX_CLOCK("force_max_clock"), + RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"), + RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"), + RENDERER_DEBUG("debug"), + PICTURE_IN_PICTURE("picture_in_picture"), + USE_CUSTOM_RTC("custom_rtc_enabled"); + + override fun getBoolean(needsGlobal: Boolean): Boolean = + NativeConfig.getBoolean(key, needsGlobal) + + override fun setBoolean(value: Boolean) { + if (NativeConfig.isPerGameConfigLoaded()) { + global = false + } + NativeConfig.setBoolean(key, value) } - override val valueAsString: String - get() = if (boolean) "1" else "0" + override val defaultValue: Boolean by lazy { NativeConfig.getDefaultToString(key).toBoolean() } + + override fun getValueAsString(needsGlobal: Boolean): String = getBoolean(needsGlobal).toString() override fun reset() = NativeConfig.setBoolean(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt index 6ec0a765e..7b7fac211 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt @@ -5,21 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model import org.yuzu.yuzu_emu.utils.NativeConfig -enum class ByteSetting( - override val key: String, - override val category: Settings.Category -) : AbstractByteSetting { - AUDIO_VOLUME("volume", Settings.Category.Audio); +enum class ByteSetting(override val key: String) : AbstractByteSetting { + AUDIO_VOLUME("volume"); - override val byte: Byte - get() = NativeConfig.getByte(key, false) + override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal) - override fun setByte(value: Byte) = NativeConfig.setByte(key, value) + override fun setByte(value: Byte) { + if (NativeConfig.isPerGameConfigLoaded()) { + global = false + } + NativeConfig.setByte(key, value) + } - override val defaultValue: Byte by lazy { NativeConfig.getByte(key, true) } + override val defaultValue: Byte by lazy { NativeConfig.getDefaultToString(key).toByte() } - override val valueAsString: String - get() = byte.toString() + override fun getValueAsString(needsGlobal: Boolean): String = getByte(needsGlobal).toString() override fun reset() = NativeConfig.setByte(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt index 0181d06f2..4644824d8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/FloatSetting.kt @@ -5,22 +5,22 @@ package org.yuzu.yuzu_emu.features.settings.model import org.yuzu.yuzu_emu.utils.NativeConfig -enum class FloatSetting( - override val key: String, - override val category: Settings.Category -) : AbstractFloatSetting { +enum class FloatSetting(override val key: String) : AbstractFloatSetting { // No float settings currently exist - EMPTY_SETTING("", Settings.Category.UiGeneral); + EMPTY_SETTING(""); - override val float: Float - get() = NativeConfig.getFloat(key, false) + override fun getFloat(needsGlobal: Boolean): Float = NativeConfig.getFloat(key, false) - override fun setFloat(value: Float) = NativeConfig.setFloat(key, value) + override fun setFloat(value: Float) { + if (NativeConfig.isPerGameConfigLoaded()) { + global = false + } + NativeConfig.setFloat(key, value) + } - override val defaultValue: Float by lazy { NativeConfig.getFloat(key, true) } + override val defaultValue: Float by lazy { NativeConfig.getDefaultToString(key).toFloat() } - override val valueAsString: String - get() = float.toString() + override fun getValueAsString(needsGlobal: Boolean): String = getFloat(needsGlobal).toString() override fun reset() = NativeConfig.setFloat(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index ef10b209f..21e4e1afd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -5,36 +5,33 @@ package org.yuzu.yuzu_emu.features.settings.model import org.yuzu.yuzu_emu.utils.NativeConfig -enum class IntSetting( - override val key: String, - override val category: Settings.Category, - override val androidDefault: Int? = null -) : AbstractIntSetting { - CPU_BACKEND("cpu_backend", Settings.Category.Cpu), - CPU_ACCURACY("cpu_accuracy", Settings.Category.Cpu), - REGION_INDEX("region_index", Settings.Category.System), - LANGUAGE_INDEX("language_index", Settings.Category.System), - RENDERER_BACKEND("backend", Settings.Category.Renderer), - RENDERER_ACCURACY("gpu_accuracy", Settings.Category.Renderer, 0), - RENDERER_RESOLUTION("resolution_setup", Settings.Category.Renderer), - RENDERER_VSYNC("use_vsync", Settings.Category.Renderer), - RENDERER_SCALING_FILTER("scaling_filter", Settings.Category.Renderer), - RENDERER_ANTI_ALIASING("anti_aliasing", Settings.Category.Renderer), - RENDERER_SCREEN_LAYOUT("screen_layout", Settings.Category.Android), - RENDERER_ASPECT_RATIO("aspect_ratio", Settings.Category.Renderer), - AUDIO_OUTPUT_ENGINE("output_engine", Settings.Category.Audio); - - override val int: Int - get() = NativeConfig.getInt(key, false) - - override fun setInt(value: Int) = NativeConfig.setInt(key, value) - - override val defaultValue: Int by lazy { - androidDefault ?: NativeConfig.getInt(key, true) +enum class IntSetting(override val key: String) : AbstractIntSetting { + CPU_BACKEND("cpu_backend"), + CPU_ACCURACY("cpu_accuracy"), + REGION_INDEX("region_index"), + LANGUAGE_INDEX("language_index"), + RENDERER_BACKEND("backend"), + RENDERER_ACCURACY("gpu_accuracy"), + RENDERER_RESOLUTION("resolution_setup"), + RENDERER_VSYNC("use_vsync"), + RENDERER_SCALING_FILTER("scaling_filter"), + RENDERER_ANTI_ALIASING("anti_aliasing"), + RENDERER_SCREEN_LAYOUT("screen_layout"), + RENDERER_ASPECT_RATIO("aspect_ratio"), + AUDIO_OUTPUT_ENGINE("output_engine"); + + override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) + + override fun setInt(value: Int) { + if (NativeConfig.isPerGameConfigLoaded()) { + global = false + } + NativeConfig.setInt(key, value) } - override val valueAsString: String - get() = int.toString() + override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() } + + override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString() override fun reset() = NativeConfig.setInt(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt index c526fc4cf..e3efd516c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/LongSetting.kt @@ -5,21 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model import org.yuzu.yuzu_emu.utils.NativeConfig -enum class LongSetting( - override val key: String, - override val category: Settings.Category -) : AbstractLongSetting { - CUSTOM_RTC("custom_rtc", Settings.Category.System); +enum class LongSetting(override val key: String) : AbstractLongSetting { + CUSTOM_RTC("custom_rtc"); - override val long: Long - get() = NativeConfig.getLong(key, false) + override fun getLong(needsGlobal: Boolean): Long = NativeConfig.getLong(key, needsGlobal) - override fun setLong(value: Long) = NativeConfig.setLong(key, value) + override fun setLong(value: Long) { + if (NativeConfig.isPerGameConfigLoaded()) { + global = false + } + NativeConfig.setLong(key, value) + } - override val defaultValue: Long by lazy { NativeConfig.getLong(key, true) } + override val defaultValue: Long by lazy { NativeConfig.getDefaultToString(key).toLong() } - override val valueAsString: String - get() = long.toString() + override fun getValueAsString(needsGlobal: Boolean): String = getLong(needsGlobal).toString() override fun reset() = NativeConfig.setLong(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index e3cd66185..9551fc05e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -6,62 +6,11 @@ package org.yuzu.yuzu_emu.features.settings.model import org.yuzu.yuzu_emu.R object Settings { - enum class Category { - Android, - Audio, - Core, - Cpu, - CpuDebug, - CpuUnsafe, - Renderer, - RendererAdvanced, - RendererDebug, - System, - SystemAudio, - DataStorage, - Debugging, - DebuggingGraphics, - Miscellaneous, - Network, - WebService, - AddOns, - Controls, - Ui, - UiGeneral, - UiLayout, - UiGameList, - Screenshots, - Shortcuts, - Multiplayer, - Services, - Paths, - MaxEnum - } - - val settingsList = listOf<AbstractSetting>( - *BooleanSetting.values(), - *ByteSetting.values(), - *ShortSetting.values(), - *IntSetting.values(), - *FloatSetting.values(), - *LongSetting.values(), - *StringSetting.values() - ) - - const val SECTION_GENERAL = "General" - const val SECTION_SYSTEM = "System" - const val SECTION_RENDERER = "Renderer" - const val SECTION_AUDIO = "Audio" - const val SECTION_CPU = "Cpu" - const val SECTION_THEME = "Theme" - const val SECTION_DEBUG = "Debug" - enum class MenuTag(val titleId: Int) { SECTION_ROOT(R.string.advanced_settings), SECTION_SYSTEM(R.string.preferences_system), SECTION_RENDERER(R.string.preferences_graphics), SECTION_AUDIO(R.string.preferences_audio), - SECTION_CPU(R.string.cpu), SECTION_THEME(R.string.preferences_theme), SECTION_DEBUG(R.string.preferences_debug); } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt index c9a0c664c..16eb4ffdd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ShortSetting.kt @@ -5,21 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model import org.yuzu.yuzu_emu.utils.NativeConfig -enum class ShortSetting( - override val key: String, - override val category: Settings.Category -) : AbstractShortSetting { - RENDERER_SPEED_LIMIT("speed_limit", Settings.Category.Core); +enum class ShortSetting(override val key: String) : AbstractShortSetting { + RENDERER_SPEED_LIMIT("speed_limit"); - override val short: Short - get() = NativeConfig.getShort(key, false) + override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal) - override fun setShort(value: Short) = NativeConfig.setShort(key, value) + override fun setShort(value: Short) { + if (NativeConfig.isPerGameConfigLoaded()) { + global = false + } + NativeConfig.setShort(key, value) + } - override val defaultValue: Short by lazy { NativeConfig.getShort(key, true) } + override val defaultValue: Short by lazy { NativeConfig.getDefaultToString(key).toShort() } - override val valueAsString: String - get() = short.toString() + override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString() override fun reset() = NativeConfig.setShort(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt index 9bb3e66d4..a0d8cfede 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt @@ -5,22 +5,21 @@ package org.yuzu.yuzu_emu.features.settings.model import org.yuzu.yuzu_emu.utils.NativeConfig -enum class StringSetting( - override val key: String, - override val category: Settings.Category -) : AbstractStringSetting { - // No string settings currently exist - EMPTY_SETTING("", Settings.Category.UiGeneral); +enum class StringSetting(override val key: String) : AbstractStringSetting { + DRIVER_PATH("driver_path"); - override val string: String - get() = NativeConfig.getString(key, false) + override fun getString(needsGlobal: Boolean): String = NativeConfig.getString(key, needsGlobal) - override fun setString(value: String) = NativeConfig.setString(key, value) + override fun setString(value: String) { + if (NativeConfig.isPerGameConfigLoaded()) { + global = false + } + NativeConfig.setString(key, value) + } - override val defaultValue: String by lazy { NativeConfig.getString(key, true) } + override val defaultValue: String by lazy { NativeConfig.getDefaultToString(key) } - override val valueAsString: String - get() = string + override fun getValueAsString(needsGlobal: Boolean): String = getString(needsGlobal) override fun reset() = NativeConfig.setString(key, defaultValue) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt index 8bc164197..1d81f5f2b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/DateTimeSetting.kt @@ -12,7 +12,6 @@ class DateTimeSetting( ) : SettingsItem(longSetting, titleId, descriptionId) { override val type = TYPE_DATETIME_SETTING - var value: Long - get() = longSetting.long - set(value) = (setting as AbstractLongSetting).setLong(value) + fun getValue(needsGlobal: Boolean = false): Long = longSetting.getLong(needsGlobal) + fun setValue(value: Long) = (setting as AbstractLongSetting).setLong(value) } 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 e198b18a0..2e97aee2c 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 @@ -11,8 +11,8 @@ import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting import org.yuzu.yuzu_emu.features.settings.model.ByteSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.LongSetting -import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.ShortSetting +import org.yuzu.yuzu_emu.utils.NativeConfig /** * ViewModel abstraction for an Item in the RecyclerView powering SettingsFragments. @@ -30,10 +30,26 @@ abstract class SettingsItem( val isEditable: Boolean get() { + // Can't edit settings that aren't saveable in per-game config even if they are switchable + if (NativeConfig.isPerGameConfigLoaded() && !setting.isSaveable) { + return false + } + 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 @@ -48,8 +64,9 @@ abstract class SettingsItem( val emptySetting = object : AbstractSetting { override val key: String = "" - override val category: Settings.Category = Settings.Category.Ui override val defaultValue: Any = false + override val isSaveable = true + override fun getValueAsString(needsGlobal: Boolean): String = "" override fun reset() {} } @@ -270,9 +287,9 @@ abstract class SettingsItem( ) val fastmem = object : AbstractBooleanSetting { - override val boolean: Boolean - get() = - BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean + override fun getBoolean(needsGlobal: Boolean): Boolean = + BooleanSetting.FASTMEM.getBoolean() && + BooleanSetting.FASTMEM_EXCLUSIVES.getBoolean() override fun setBoolean(value: Boolean) { BooleanSetting.FASTMEM.setBoolean(value) @@ -280,9 +297,24 @@ abstract class SettingsItem( } override val key: String = FASTMEM_COMBINED - override val category = Settings.Category.Cpu override val isRuntimeModifiable: Boolean = false override val defaultValue: Boolean = true + override val isSwitchable: Boolean = true + override var global: Boolean + get() { + return BooleanSetting.FASTMEM.global && + BooleanSetting.FASTMEM_EXCLUSIVES.global + } + set(value) { + BooleanSetting.FASTMEM.global = value + BooleanSetting.FASTMEM_EXCLUSIVES.global = value + } + + override val isSaveable = true + + override fun getValueAsString(needsGlobal: Boolean): String = + getBoolean().toString() + override fun reset() = setBoolean(defaultValue) } put(SwitchSetting(fastmem, R.string.fastmem, 0)) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt index 705527a73..97a5a9e59 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt @@ -15,16 +15,11 @@ class SingleChoiceSetting( ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SINGLE_CHOICE - var selectedValue: Int - get() { - return when (setting) { - is AbstractIntSetting -> setting.int - else -> -1 - } - } - set(value) { - when (setting) { - is AbstractIntSetting -> setting.setInt(value) - } + fun getSelectedValue(needsGlobal: Boolean = false) = + when (setting) { + is AbstractIntSetting -> setting.getInt(needsGlobal) + else -> -1 } + + fun setSelectedValue(value: Int) = (setting as AbstractIntSetting).setInt(value) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt index c3b5df02c..b9b709bf7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt @@ -20,22 +20,20 @@ class SliderSetting( ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SLIDER - var selectedValue: Int - get() { - return when (setting) { - is AbstractByteSetting -> setting.byte.toInt() - is AbstractShortSetting -> setting.short.toInt() - is AbstractIntSetting -> setting.int - is AbstractFloatSetting -> setting.float.roundToInt() - else -> -1 - } + fun getSelectedValue(needsGlobal: Boolean = false) = + when (setting) { + is AbstractByteSetting -> setting.getByte(needsGlobal).toInt() + is AbstractShortSetting -> setting.getShort(needsGlobal).toInt() + is AbstractIntSetting -> setting.getInt(needsGlobal) + is AbstractFloatSetting -> setting.getFloat(needsGlobal).roundToInt() + else -> -1 } - set(value) { - when (setting) { - is AbstractByteSetting -> setting.setByte(value.toByte()) - is AbstractShortSetting -> setting.setShort(value.toShort()) - is AbstractIntSetting -> setting.setInt(value) - is AbstractFloatSetting -> setting.setFloat(value.toFloat()) - } + + fun setSelectedValue(value: Int) = + when (setting) { + is AbstractByteSetting -> setting.setByte(value.toByte()) + is AbstractShortSetting -> setting.setShort(value.toShort()) + is AbstractFloatSetting -> setting.setFloat(value.toFloat()) + else -> (setting as AbstractIntSetting).setInt(value) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt index 871dab4f3..ba7920f50 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt @@ -17,14 +17,13 @@ class StringSingleChoiceSetting( fun getValueAt(index: Int): String = if (index >= 0 && index < values.size) values[index] else "" - var selectedValue: String - get() = stringSetting.string - set(value) = stringSetting.setString(value) + fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal) + fun setSelectedValue(value: String) = stringSetting.setString(value) val selectValueIndex: Int get() { for (i in values.indices) { - if (values[i] == selectedValue) { + if (values[i] == getSelectedValue()) { return i } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt index 416967e64..44d47dd69 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SwitchSetting.kt @@ -14,18 +14,18 @@ class SwitchSetting( ) : SettingsItem(setting, titleId, descriptionId) { override val type = TYPE_SWITCH - var checked: Boolean - get() { - return when (setting) { - is AbstractIntSetting -> setting.int == 1 - is AbstractBooleanSetting -> setting.boolean - else -> false - } + fun getIsChecked(needsGlobal: Boolean = false): Boolean { + return when (setting) { + is AbstractIntSetting -> setting.getInt(needsGlobal) == 1 + is AbstractBooleanSetting -> setting.getBoolean(needsGlobal) + else -> false } - set(value) { - when (setting) { - is AbstractIntSetting -> setting.setInt(if (value) 1 else 0) - is AbstractBooleanSetting -> setting.setBoolean(value) - } + } + + fun setChecked(value: Boolean) { + when (setting) { + is AbstractIntSetting -> setting.setInt(if (value) 1 else 0) + is AbstractBooleanSetting -> setting.setBoolean(value) } + } } 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 af2c1e582..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 @@ -102,8 +102,9 @@ class SettingsAdapter( return currentList[position].type } - fun onBooleanClick(item: SwitchSetting, checked: Boolean) { - item.checked = checked + fun onBooleanClick(item: SwitchSetting, checked: Boolean, position: Int) { + item.setChecked(checked) + notifyItemChanged(position) settingsViewModel.setShouldReloadSettingsList(true) } @@ -126,7 +127,7 @@ class SettingsAdapter( } fun onDateTimeClick(item: DateTimeSetting, position: Int) { - val storedTime = item.value * 1000 + val storedTime = item.getValue() * 1000 // Helper to extract hour and minute from epoch time val calendar: Calendar = Calendar.getInstance() @@ -159,9 +160,9 @@ class SettingsAdapter( var epochTime: Long = datePicker.selection!! / 1000 epochTime += timePicker.hour.toLong() * 60 * 60 epochTime += timePicker.minute.toLong() * 60 - if (item.value != epochTime) { + if (item.getValue() != epochTime) { notifyItemChanged(position) - item.value = epochTime + item.setValue(epochTime) } } datePicker.show( @@ -195,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 7425728c6..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,12 +32,27 @@ 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(pairedSettingKey, false) + val pairedSettingValue = NativeConfig.getBoolean( + pairedSettingKey, + if (NativeLibrary.isRunning() && !NativeConfig.isPerGameConfigLoaded()) { + !NativeConfig.usingGlobal(pairedSettingKey) + } else { + NativeConfig.usingGlobal(pairedSettingKey) + } + ) if (!pairedSettingValue) return } add(item) @@ -153,8 +169,8 @@ class SettingsFragmentPresenter( private fun addThemeSettings(sl: ArrayList<SettingsItem>) { sl.apply { val theme: AbstractIntSetting = object : AbstractIntSetting { - override val int: Int - get() = preferences.getInt(Settings.PREF_THEME, 0) + override fun getInt(needsGlobal: Boolean): Int = + preferences.getInt(Settings.PREF_THEME, 0) override fun setInt(value: Int) { preferences.edit() @@ -164,8 +180,8 @@ class SettingsFragmentPresenter( } override val key: String = Settings.PREF_THEME - override val category = Settings.Category.UiGeneral override val isRuntimeModifiable: Boolean = false + override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() override val defaultValue: Int = 0 override fun reset() { preferences.edit() @@ -197,8 +213,8 @@ class SettingsFragmentPresenter( } val themeMode: AbstractIntSetting = object : AbstractIntSetting { - override val int: Int - get() = preferences.getInt(Settings.PREF_THEME_MODE, -1) + override fun getInt(needsGlobal: Boolean): Int = + preferences.getInt(Settings.PREF_THEME_MODE, -1) override fun setInt(value: Int) { preferences.edit() @@ -208,8 +224,8 @@ class SettingsFragmentPresenter( } override val key: String = Settings.PREF_THEME_MODE - override val category = Settings.Category.UiGeneral override val isRuntimeModifiable: Boolean = false + override fun getValueAsString(needsGlobal: Boolean): String = getInt().toString() override val defaultValue: Int = -1 override fun reset() { preferences.edit() @@ -230,8 +246,8 @@ class SettingsFragmentPresenter( ) val blackBackgrounds: AbstractBooleanSetting = object : AbstractBooleanSetting { - override val boolean: Boolean - get() = preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) + override fun getBoolean(needsGlobal: Boolean): Boolean = + preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) override fun setBoolean(value: Boolean) { preferences.edit() @@ -241,8 +257,10 @@ class SettingsFragmentPresenter( } override val key: String = Settings.PREF_BLACK_BACKGROUNDS - override val category = Settings.Category.UiGeneral override val isRuntimeModifiable: Boolean = false + override fun getValueAsString(needsGlobal: Boolean): String = + getBoolean().toString() + override val defaultValue: Boolean = false override fun reset() { preferences.edit() 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 525f013f8..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) { @@ -29,12 +30,23 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA } binding.textSettingValue.visibility = View.VISIBLE - val epochTime = setting.value + val epochTime = setting.getValue() val instant = Instant.ofEpochMilli(epochTime * 1000) val zonedTime = ZonedDateTime.ofInstant(instant, ZoneId.of("UTC")) 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 80d1b22c1..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) { @@ -29,20 +30,31 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti val resMgr = binding.textSettingValue.context.resources val values = resMgr.getIntArray(item.valuesId) for (i in values.indices) { - if (values[i] == item.selectedValue) { + if (values[i] == item.getSelectedValue()) { binding.textSettingValue.text = resMgr.getStringArray(item.choicesId)[i] break } } } else if (item is StringSingleChoiceSetting) { for (i in item.values.indices) { - if (item.values[i] == item.selectedValue) { + if (item.values[i] == item.getSelectedValue()) { binding.textSettingValue.text = item.choices[i] break } } } + 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 b83c90100..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) { @@ -26,10 +27,21 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda binding.textSettingValue.visibility = View.VISIBLE binding.textSettingValue.text = String.format( binding.textSettingValue.context.getString(R.string.value_with_units), - setting.selectedValue, + setting.getSelectedValue(), 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 57fdeaa20..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) { @@ -27,9 +28,20 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter } binding.switchWidget.setOnCheckedChangeListener(null) - binding.switchWidget.isChecked = setting.checked + 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/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt new file mode 100644 index 000000000..0dce8ad8d --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt @@ -0,0 +1,214 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import androidx.documentfile.provider.DocumentFile +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.findNavController +import androidx.navigation.fragment.navArgs +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.transition.MaterialSharedAxis +import kotlinx.coroutines.launch +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.adapters.AddonAdapter +import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding +import org.yuzu.yuzu_emu.model.AddonViewModel +import org.yuzu.yuzu_emu.model.HomeViewModel +import org.yuzu.yuzu_emu.utils.AddonUtil +import org.yuzu.yuzu_emu.utils.FileUtil.copyFilesTo +import java.io.File + +class AddonsFragment : Fragment() { + private var _binding: FragmentAddonsBinding? = null + private val binding get() = _binding!! + + private val homeViewModel: HomeViewModel by activityViewModels() + private val addonViewModel: AddonViewModel by activityViewModels() + + private val args by navArgs<AddonsFragmentArgs>() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + addonViewModel.onOpenAddons(args.game) + enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentAddonsBinding.inflate(inflater) + return binding.root + } + + // This is using the correct scope, lint is just acting up + @SuppressLint("UnsafeRepeatOnLifecycleDetector") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + homeViewModel.setNavigationVisibility(visible = false, animated = false) + homeViewModel.setStatusBarShadeVisibility(false) + + binding.toolbarAddons.setNavigationOnClickListener { + binding.root.findNavController().popBackStack() + } + + binding.toolbarAddons.title = getString(R.string.addons_game, args.game.title) + + binding.listAddons.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = AddonAdapter() + } + + viewLifecycleOwner.lifecycleScope.apply { + launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + addonViewModel.addonList.collect { + (binding.listAddons.adapter as AddonAdapter).submitList(it) + } + } + } + launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + addonViewModel.showModInstallPicker.collect { + if (it) { + installAddon.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) + addonViewModel.showModInstallPicker(false) + } + } + } + } + launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + addonViewModel.showModNoticeDialog.collect { + if (it) { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.addon_notice, + descriptionId = R.string.addon_notice_description, + positiveAction = { addonViewModel.showModInstallPicker(true) } + ).show(parentFragmentManager, MessageDialogFragment.TAG) + addonViewModel.showModNoticeDialog(false) + } + } + } + } + } + + binding.buttonInstall.setOnClickListener { + ContentTypeSelectionDialogFragment().show( + parentFragmentManager, + ContentTypeSelectionDialogFragment.TAG + ) + } + + setInsets() + } + + override fun onResume() { + super.onResume() + addonViewModel.refreshAddons() + } + + override fun onDestroy() { + super.onDestroy() + addonViewModel.onCloseAddons() + } + + val installAddon = + registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> + if (result == null) { + return@registerForActivityResult + } + + val externalAddonDirectory = DocumentFile.fromTreeUri(requireContext(), result) + if (externalAddonDirectory == null) { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.invalid_directory, + descriptionId = R.string.invalid_directory_description + ).show(parentFragmentManager, MessageDialogFragment.TAG) + return@registerForActivityResult + } + + val isValid = externalAddonDirectory.listFiles() + .any { AddonUtil.validAddonDirectories.contains(it.name) } + val errorMessage = MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.invalid_directory, + descriptionId = R.string.invalid_directory_description + ) + if (isValid) { + IndeterminateProgressDialogFragment.newInstance( + requireActivity(), + R.string.installing_game_content, + false + ) { + val parentDirectoryName = externalAddonDirectory.name + val internalAddonDirectory = + File(args.game.addonDir + parentDirectoryName) + try { + externalAddonDirectory.copyFilesTo(internalAddonDirectory) + } catch (_: Exception) { + return@newInstance errorMessage + } + addonViewModel.refreshAddons() + return@newInstance getString(R.string.addon_installed_successfully) + }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) + } else { + errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG) + } + } + + private fun setInsets() = + ViewCompat.setOnApplyWindowInsetsListener( + binding.root + ) { _: View, windowInsets: WindowInsetsCompat -> + val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + + val leftInsets = barInsets.left + cutoutInsets.left + val rightInsets = barInsets.right + cutoutInsets.right + + val mlpToolbar = binding.toolbarAddons.layoutParams as ViewGroup.MarginLayoutParams + mlpToolbar.leftMargin = leftInsets + mlpToolbar.rightMargin = rightInsets + binding.toolbarAddons.layoutParams = mlpToolbar + + val mlpAddonsList = binding.listAddons.layoutParams as ViewGroup.MarginLayoutParams + mlpAddonsList.leftMargin = leftInsets + mlpAddonsList.rightMargin = rightInsets + binding.listAddons.layoutParams = mlpAddonsList + binding.listAddons.updatePadding( + bottom = barInsets.bottom + + resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab) + ) + + val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab) + val mlpFab = + binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams + mlpFab.leftMargin = leftInsets + fabSpacing + mlpFab.rightMargin = rightInsets + fabSpacing + mlpFab.bottomMargin = barInsets.bottom + fabSpacing + binding.buttonInstall.layoutParams = mlpFab + + windowInsets + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ContentTypeSelectionDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ContentTypeSelectionDialogFragment.kt new file mode 100644 index 000000000..c1d8b9ea5 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ContentTypeSelectionDialogFragment.kt @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels +import androidx.preference.PreferenceManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.model.AddonViewModel +import org.yuzu.yuzu_emu.ui.main.MainActivity + +class ContentTypeSelectionDialogFragment : DialogFragment() { + private val addonViewModel: AddonViewModel by activityViewModels() + + private val preferences get() = + PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) + + private var selectedItem = 0 + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val launchOptions = + arrayOf(getString(R.string.updates_and_dlc), getString(R.string.mods_and_cheats)) + + if (savedInstanceState != null) { + selectedItem = savedInstanceState.getInt(SELECTED_ITEM) + } + + val mainActivity = requireActivity() as MainActivity + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.select_content_type) + .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> + when (selectedItem) { + 0 -> mainActivity.installGameUpdate.launch(arrayOf("*/*")) + else -> { + if (!preferences.getBoolean(MOD_NOTICE_SEEN, false)) { + preferences.edit().putBoolean(MOD_NOTICE_SEEN, true).apply() + addonViewModel.showModNoticeDialog(true) + return@setPositiveButton + } + addonViewModel.showModInstallPicker(true) + } + } + } + .setSingleChoiceItems(launchOptions, 0) { _: DialogInterface, i: Int -> + selectedItem = i + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putInt(SELECTED_ITEM, selectedItem) + } + + companion object { + const val TAG = "ContentTypeSelectionDialogFragment" + + private const val SELECTED_ITEM = "SelectedItem" + private const val MOD_NOTICE_SEEN = "ModNoticeSeen" + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt index df21d74b2..cc71254dc 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt @@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.findNavController +import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.GridLayoutManager import com.google.android.material.transition.MaterialSharedAxis import kotlinx.coroutines.flow.collectLatest @@ -36,6 +37,8 @@ class DriverManagerFragment : Fragment() { private val homeViewModel: HomeViewModel by activityViewModels() private val driverViewModel: DriverViewModel by activityViewModels() + private val args by navArgs<DriverManagerFragmentArgs>() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) @@ -57,7 +60,9 @@ class DriverManagerFragment : Fragment() { homeViewModel.setNavigationVisibility(visible = false, animated = true) homeViewModel.setStatusBarShadeVisibility(visible = false) - if (!driverViewModel.isInteractionAllowed) { + driverViewModel.onOpenDriverManager(args.game) + + if (!driverViewModel.isInteractionAllowed.value) { DriversLoadingDialogFragment().show( childFragmentManager, DriversLoadingDialogFragment.TAG @@ -102,10 +107,9 @@ class DriverManagerFragment : Fragment() { setInsets() } - // Start installing requested driver - override fun onStop() { - super.onStop() - driverViewModel.onCloseDriverManager() + override fun onDestroy() { + super.onDestroy() + driverViewModel.onCloseDriverManager(args.game) } private fun setInsets() = diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt index f8c34346a..6a47b29f0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt @@ -47,25 +47,9 @@ class DriversLoadingDialogFragment : DialogFragment() { viewLifecycleOwner.lifecycleScope.apply { launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.areDriversLoading.collect { checkForDismiss() } + driverViewModel.isInteractionAllowed.collect { if (it) dismiss() } } } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isDriverReady.collect { checkForDismiss() } - } - } - launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isDeletingDrivers.collect { checkForDismiss() } - } - } - } - } - - private fun checkForDismiss() { - if (driverViewModel.isInteractionAllowed) { - dismiss() } } 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 734c1d5ca..d7b38f62d 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,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { return } + // Always load custom settings when launching a game from an intent + if (args.custom || intentGame != null) { + SettingsFile.loadCustomConfig(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 +229,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 @@ -332,15 +353,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - driverViewModel.isDriverReady.collect { - if (it && !emulationState.isRunning) { - if (!DirectoryInitialization.areDirectoriesReady) { - DirectoryInitialization.start() - } - - updateScreenLayout() - - emulationState.run(emulationActivity!!.isActivityRecreated) + driverViewModel.isInteractionAllowed.collect { + if (it) { + onEmulationStart() } } } @@ -348,6 +363,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } } + private fun onEmulationStart() { + if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) { + if (!DirectoryInitialization.areDirectoriesReady) { + DirectoryInitialization.start() + } + + updateScreenLayout() + + emulationState.run(emulationActivity!!.isActivityRecreated) + } + } + override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) if (_binding == null) { @@ -435,7 +462,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { @SuppressLint("SourceLockedOrientationActivity") private fun updateOrientation() { emulationActivity?.let { - it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { + it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.getInt()) { Settings.LayoutOption_MobileLandscape -> ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE Settings.LayoutOption_MobilePortrait -> @@ -617,7 +644,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { @SuppressLint("SourceLockedOrientationActivity") private fun startConfiguringControls() { // Lock the current orientation to prevent editing inconsistencies - if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { + if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == Settings.LayoutOption_Unspecified) { emulationActivity?.let { it.requestedOrientation = if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { @@ -635,7 +662,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { binding.doneControlConfig.visibility = View.GONE binding.surfaceInputOverlay.setIsInEditMode(false) // Unlock the orientation if it was locked for editing - if (IntSetting.RENDERER_SCREEN_LAYOUT.int == Settings.LayoutOption_Unspecified) { + if (IntSetting.RENDERER_SCREEN_LAYOUT.getInt() == Settings.LayoutOption_Unspecified) { emulationActivity?.let { it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } 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/GameInfoFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt new file mode 100644 index 000000000..fa2a4c9f9 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameInfoFragment.kt @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.navigation.findNavController +import androidx.navigation.fragment.navArgs +import com.google.android.material.transition.MaterialSharedAxis +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.databinding.FragmentGameInfoBinding +import org.yuzu.yuzu_emu.model.HomeViewModel +import org.yuzu.yuzu_emu.utils.GameMetadata + +class GameInfoFragment : Fragment() { + private var _binding: FragmentGameInfoBinding? = null + private val binding get() = _binding!! + + private val homeViewModel: HomeViewModel by activityViewModels() + + private val args by navArgs<GameInfoFragmentArgs>() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + + // Check for an up-to-date version string + args.game.version = GameMetadata.getVersion(args.game.path, true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentGameInfoBinding.inflate(inflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + homeViewModel.setNavigationVisibility(visible = false, animated = false) + homeViewModel.setStatusBarShadeVisibility(false) + + binding.apply { + toolbarInfo.title = args.game.title + toolbarInfo.setNavigationOnClickListener { + view.findNavController().popBackStack() + } + + val pathString = Uri.parse(args.game.path).path ?: "" + path.setHint(R.string.path) + pathField.setText(pathString) + pathField.setOnClickListener { copyToClipboard(getString(R.string.path), pathString) } + + programId.setHint(R.string.program_id) + programIdField.setText(args.game.programIdHex) + programIdField.setOnClickListener { + copyToClipboard(getString(R.string.program_id), args.game.programIdHex) + } + + if (args.game.developer.isNotEmpty()) { + developer.setHint(R.string.developer) + developerField.setText(args.game.developer) + developerField.setOnClickListener { + copyToClipboard(getString(R.string.developer), args.game.developer) + } + } else { + developer.visibility = View.GONE + } + + version.setHint(R.string.version) + versionField.setText(args.game.version) + versionField.setOnClickListener { + copyToClipboard(getString(R.string.version), args.game.version) + } + + buttonCopy.setOnClickListener { + val details = """ + ${args.game.title} + ${getString(R.string.path)} - $pathString + ${getString(R.string.program_id)} - ${args.game.programIdHex} + ${getString(R.string.developer)} - ${args.game.developer} + ${getString(R.string.version)} - ${args.game.version} + """.trimIndent() + copyToClipboard(args.game.title, details) + } + } + + setInsets() + } + + private fun copyToClipboard(label: String, body: String) { + val clipBoard = + requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText(label, body) + clipBoard.setPrimaryClip(clip) + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + Toast.makeText( + requireContext(), + R.string.copied_to_clipboard, + Toast.LENGTH_SHORT + ).show() + } + } + + private fun setInsets() = + ViewCompat.setOnApplyWindowInsetsListener( + binding.root + ) { _: View, windowInsets: WindowInsetsCompat -> + val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + + val leftInsets = barInsets.left + cutoutInsets.left + val rightInsets = barInsets.right + cutoutInsets.right + + val mlpToolbar = binding.toolbarInfo.layoutParams as ViewGroup.MarginLayoutParams + mlpToolbar.leftMargin = leftInsets + mlpToolbar.rightMargin = rightInsets + binding.toolbarInfo.layoutParams = mlpToolbar + + val mlpScrollAbout = binding.scrollInfo.layoutParams as ViewGroup.MarginLayoutParams + mlpScrollAbout.leftMargin = leftInsets + mlpScrollAbout.rightMargin = rightInsets + binding.scrollInfo.layoutParams = mlpScrollAbout + + binding.contentInfo.updatePadding(bottom = barInsets.bottom) + + windowInsets + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt new file mode 100644 index 000000000..b1d3c0040 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt @@ -0,0 +1,456 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.annotation.SuppressLint +import android.os.Bundle +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.navigation.findNavController +import androidx.navigation.fragment.navArgs +import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.transition.MaterialSharedAxis +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.yuzu.yuzu_emu.HomeNavigationDirections +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.adapters.GamePropertiesAdapter +import org.yuzu.yuzu_emu.databinding.FragmentGamePropertiesBinding +import org.yuzu.yuzu_emu.features.settings.model.Settings +import org.yuzu.yuzu_emu.model.DriverViewModel +import org.yuzu.yuzu_emu.model.GameProperty +import org.yuzu.yuzu_emu.model.GamesViewModel +import org.yuzu.yuzu_emu.model.HomeViewModel +import org.yuzu.yuzu_emu.model.InstallableProperty +import org.yuzu.yuzu_emu.model.SubmenuProperty +import org.yuzu.yuzu_emu.model.TaskState +import org.yuzu.yuzu_emu.utils.DirectoryInitialization +import org.yuzu.yuzu_emu.utils.FileUtil +import org.yuzu.yuzu_emu.utils.GameIconUtils +import org.yuzu.yuzu_emu.utils.GpuDriverHelper +import org.yuzu.yuzu_emu.utils.MemoryUtil +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import java.io.File + +class GamePropertiesFragment : Fragment() { + private var _binding: FragmentGamePropertiesBinding? = null + private val binding get() = _binding!! + + private val homeViewModel: HomeViewModel by activityViewModels() + private val gamesViewModel: GamesViewModel by activityViewModels() + private val driverViewModel: DriverViewModel by activityViewModels() + + private val args by navArgs<GamePropertiesFragmentArgs>() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Y, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Y, false) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentGamePropertiesBinding.inflate(layoutInflater) + return binding.root + } + + // This is using the correct scope, lint is just acting up + @SuppressLint("UnsafeRepeatOnLifecycleDetector") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + homeViewModel.setNavigationVisibility(visible = false, animated = true) + homeViewModel.setStatusBarShadeVisibility(true) + + binding.buttonBack.setOnClickListener { + view.findNavController().popBackStack() + } + + GameIconUtils.loadGameIcon(args.game, binding.imageGameScreen) + binding.title.text = args.game.title + binding.title.postDelayed( + { + binding.title.ellipsize = TextUtils.TruncateAt.MARQUEE + binding.title.isSelected = true + }, + 3000 + ) + + binding.buttonStart.setOnClickListener { + LaunchGameDialogFragment.newInstance(args.game) + .show(childFragmentManager, LaunchGameDialogFragment.TAG) + } + + reloadList() + + viewLifecycleOwner.lifecycleScope.apply { + launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + homeViewModel.openImportSaves.collect { + if (it) { + importSaves.launch(arrayOf("application/zip")) + homeViewModel.setOpenImportSaves(false) + } + } + } + } + launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + homeViewModel.reloadPropertiesList.collect { + if (it) { + reloadList() + homeViewModel.reloadPropertiesList(false) + } + } + } + } + } + + setInsets() + } + + override fun onDestroy() { + super.onDestroy() + gamesViewModel.reloadGames(true) + } + + private fun reloadList() { + _binding ?: return + + driverViewModel.updateDriverNameForGame(args.game) + val properties = mutableListOf<GameProperty>().apply { + add( + SubmenuProperty( + R.string.info, + R.string.info_description, + R.drawable.ic_info_outline + ) { + val action = GamePropertiesFragmentDirections + .actionPerGamePropertiesFragmentToGameInfoFragment(args.game) + binding.root.findNavController().navigate(action) + } + ) + add( + SubmenuProperty( + R.string.preferences_settings, + R.string.per_game_settings_description, + R.drawable.ic_settings + ) { + val action = HomeNavigationDirections.actionGlobalSettingsActivity( + args.game, + Settings.MenuTag.SECTION_ROOT + ) + binding.root.findNavController().navigate(action) + } + ) + + if (GpuDriverHelper.supportsCustomDriverLoading()) { + add( + SubmenuProperty( + R.string.gpu_driver_manager, + R.string.install_gpu_driver_description, + R.drawable.ic_build, + detailsFlow = driverViewModel.selectedDriverTitle + ) { + val action = GamePropertiesFragmentDirections + .actionPerGamePropertiesFragmentToDriverManagerFragment(args.game) + binding.root.findNavController().navigate(action) + } + ) + } + + if (!args.game.isHomebrew) { + add( + SubmenuProperty( + R.string.add_ons, + R.string.add_ons_description, + R.drawable.ic_edit + ) { + val action = GamePropertiesFragmentDirections + .actionPerGamePropertiesFragmentToAddonsFragment(args.game) + binding.root.findNavController().navigate(action) + } + ) + add( + InstallableProperty( + R.string.save_data, + R.string.save_data_description, + R.drawable.ic_save, + { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.import_save_warning, + descriptionId = R.string.import_save_warning_description, + positiveAction = { homeViewModel.setOpenImportSaves(true) } + ).show(parentFragmentManager, MessageDialogFragment.TAG) + }, + if (File(args.game.saveDir).exists()) { + { exportSaves.launch(args.game.saveZipName) } + } else { + null + } + ) + ) + + val saveDirFile = File(args.game.saveDir) + if (saveDirFile.exists()) { + add( + SubmenuProperty( + R.string.delete_save_data, + R.string.delete_save_data_description, + R.drawable.ic_delete, + action = { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.delete_save_data, + descriptionId = R.string.delete_save_data_warning_description, + positiveAction = { + File(args.game.saveDir).deleteRecursively() + Toast.makeText( + YuzuApplication.appContext, + R.string.save_data_deleted_successfully, + Toast.LENGTH_SHORT + ).show() + homeViewModel.reloadPropertiesList(true) + } + ).show(parentFragmentManager, MessageDialogFragment.TAG) + } + ) + ) + } + + val shaderCacheDir = File( + DirectoryInitialization.userDirectory + + "/shader/" + args.game.settingsName.lowercase() + ) + if (shaderCacheDir.exists()) { + add( + SubmenuProperty( + R.string.clear_shader_cache, + R.string.clear_shader_cache_description, + R.drawable.ic_delete, + { + if (shaderCacheDir.exists()) { + val bytes = shaderCacheDir.walkTopDown().filter { it.isFile } + .map { it.length() }.sum() + MemoryUtil.bytesToSizeUnit(bytes.toFloat()) + } else { + MemoryUtil.bytesToSizeUnit(0f) + } + } + ) { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.clear_shader_cache, + descriptionId = R.string.clear_shader_cache_warning_description, + positiveAction = { + shaderCacheDir.deleteRecursively() + Toast.makeText( + YuzuApplication.appContext, + R.string.cleared_shaders_successfully, + Toast.LENGTH_SHORT + ).show() + homeViewModel.reloadPropertiesList(true) + } + ).show(parentFragmentManager, MessageDialogFragment.TAG) + } + ) + } + } + } + binding.listProperties.apply { + layoutManager = + GridLayoutManager(requireContext(), resources.getInteger(R.integer.grid_columns)) + adapter = GamePropertiesAdapter(viewLifecycleOwner, properties) + } + } + + override fun onResume() { + super.onResume() + driverViewModel.updateDriverNameForGame(args.game) + } + + private fun setInsets() = + ViewCompat.setOnApplyWindowInsetsListener( + binding.root + ) { _: View, windowInsets: WindowInsetsCompat -> + val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) + + val leftInsets = barInsets.left + cutoutInsets.left + val rightInsets = barInsets.right + cutoutInsets.right + + val smallLayout = resources.getBoolean(R.bool.small_layout) + if (smallLayout) { + val mlpListAll = + binding.listAll.layoutParams as ViewGroup.MarginLayoutParams + mlpListAll.leftMargin = leftInsets + mlpListAll.rightMargin = rightInsets + binding.listAll.layoutParams = mlpListAll + } else { + if (ViewCompat.getLayoutDirection(binding.root) == + ViewCompat.LAYOUT_DIRECTION_LTR + ) { + val mlpListAll = + binding.listAll.layoutParams as ViewGroup.MarginLayoutParams + mlpListAll.rightMargin = rightInsets + binding.listAll.layoutParams = mlpListAll + + val mlpIconLayout = + binding.iconLayout!!.layoutParams as ViewGroup.MarginLayoutParams + mlpIconLayout.topMargin = barInsets.top + mlpIconLayout.leftMargin = leftInsets + binding.iconLayout!!.layoutParams = mlpIconLayout + } else { + val mlpListAll = + binding.listAll.layoutParams as ViewGroup.MarginLayoutParams + mlpListAll.leftMargin = leftInsets + binding.listAll.layoutParams = mlpListAll + + val mlpIconLayout = + binding.iconLayout!!.layoutParams as ViewGroup.MarginLayoutParams + mlpIconLayout.topMargin = barInsets.top + mlpIconLayout.rightMargin = rightInsets + binding.iconLayout!!.layoutParams = mlpIconLayout + } + } + + val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab) + val mlpFab = + binding.buttonStart.layoutParams as ViewGroup.MarginLayoutParams + mlpFab.leftMargin = leftInsets + fabSpacing + mlpFab.rightMargin = rightInsets + fabSpacing + mlpFab.bottomMargin = barInsets.bottom + fabSpacing + binding.buttonStart.layoutParams = mlpFab + + binding.layoutAll.updatePadding( + top = barInsets.top, + bottom = barInsets.bottom + + resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab) + ) + + windowInsets + } + + private val importSaves = + registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> + if (result == null) { + return@registerForActivityResult + } + + val inputZip = requireContext().contentResolver.openInputStream(result) + val savesFolder = File(args.game.saveDir) + val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/") + cacheSaveDir.mkdir() + + if (inputZip == null) { + Toast.makeText( + YuzuApplication.appContext, + getString(R.string.fatal_error), + Toast.LENGTH_LONG + ).show() + return@registerForActivityResult + } + + IndeterminateProgressDialogFragment.newInstance( + requireActivity(), + R.string.save_files_importing, + false + ) { + try { + FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir) + val files = cacheSaveDir.listFiles() + var savesFolderFile: File? = null + if (files != null) { + val savesFolderName = args.game.programIdHex + for (file in files) { + if (file.isDirectory && file.name == savesFolderName) { + savesFolderFile = file + break + } + } + } + + if (savesFolderFile != null) { + savesFolder.deleteRecursively() + savesFolder.mkdir() + savesFolderFile.copyRecursively(savesFolder) + savesFolderFile.deleteRecursively() + } + + withContext(Dispatchers.Main) { + if (savesFolderFile == null) { + MessageDialogFragment.newInstance( + requireActivity(), + titleId = R.string.save_file_invalid_zip_structure, + descriptionId = R.string.save_file_invalid_zip_structure_description + ).show(parentFragmentManager, MessageDialogFragment.TAG) + return@withContext + } + Toast.makeText( + YuzuApplication.appContext, + getString(R.string.save_file_imported_success), + Toast.LENGTH_LONG + ).show() + homeViewModel.reloadPropertiesList(true) + } + + cacheSaveDir.deleteRecursively() + } catch (e: Exception) { + Toast.makeText( + YuzuApplication.appContext, + getString(R.string.fatal_error), + Toast.LENGTH_LONG + ).show() + } + }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) + } + + /** + * Exports the save file located in the given folder path by creating a zip file and opening a + * file picker to save. + */ + private val exportSaves = registerForActivityResult( + ActivityResultContracts.CreateDocument("application/zip") + ) { result -> + if (result == null) { + return@registerForActivityResult + } + + IndeterminateProgressDialogFragment.newInstance( + requireActivity(), + R.string.save_files_exporting, + false + ) { + val saveLocation = args.game.saveDir + val zipResult = FileUtil.zipFromInternalStorage( + File(saveLocation), + saveLocation.replaceAfterLast("/", ""), + BufferedOutputStream(requireContext().contentResolver.openOutputStream(result)) + ) + return@newInstance when (zipResult) { + TaskState.Completed -> getString(R.string.export_success) + TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed) + } + }.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG) + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index 3addc2e63..6ddd758e6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -68,6 +68,9 @@ class HomeSettingsFragment : Fragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + homeViewModel.setNavigationVisibility(visible = true, animated = true) + homeViewModel.setStatusBarShadeVisibility(visible = true) mainActivity = requireActivity() as MainActivity val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply { @@ -91,13 +94,14 @@ class HomeSettingsFragment : Fragment() { R.string.install_gpu_driver_description, R.drawable.ic_build, { - binding.root.findNavController() - .navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment) + val action = HomeSettingsFragmentDirections + .actionHomeSettingsFragmentToDriverManagerFragment(null) + binding.root.findNavController().navigate(action) }, { GpuDriverHelper.supportsCustomDriverLoading() }, R.string.custom_driver_not_supported, R.string.custom_driver_not_supported_description, - driverViewModel.selectedDriverMetadata + driverViewModel.selectedDriverTitle ) ) add( @@ -212,8 +216,11 @@ class HomeSettingsFragment : Fragment() { override fun onStart() { super.onStart() exitTransition = null - homeViewModel.setNavigationVisibility(visible = true, animated = true) - homeViewModel.setStatusBarShadeVisibility(visible = true) + } + + override fun onResume() { + super.onResume() + driverViewModel.updateDriverNameForGame(null) } override fun onDestroyView() { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt index 7e467814d..8847e5531 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt @@ -122,7 +122,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() { activity: FragmentActivity, titleId: Int, cancellable: Boolean = false, - task: () -> Any + task: suspend () -> Any ): IndeterminateProgressDialogFragment { val dialog = IndeterminateProgressDialogFragment() val args = Bundle() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt index 6940fc757..569727b90 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt @@ -21,8 +21,6 @@ import org.yuzu.yuzu_emu.databinding.FragmentInstallablesBinding import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.Installable import org.yuzu.yuzu_emu.ui.main.MainActivity -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter class InstallableFragment : Fragment() { private var _binding: FragmentInstallablesBinding? = null @@ -75,28 +73,6 @@ class InstallableFragment : Fragment() { R.string.install_firmware_description, install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) } ), - if (mainActivity.savesFolderRoot != "") { - Installable( - R.string.manage_save_data, - R.string.import_export_saves_description, - install = { mainActivity.importSaves.launch(arrayOf("application/zip")) }, - export = { - mainActivity.exportSaves.launch( - "yuzu saves - ${ - LocalDateTime.now().format( - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") - ) - }.zip" - ) - } - ) - } else { - Installable( - R.string.manage_save_data, - R.string.import_export_saves_description, - install = { mainActivity.importSaves.launch(arrayOf("application/zip")) } - ) - }, Installable( R.string.install_prod_keys, R.string.install_prod_keys_description, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LaunchGameDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LaunchGameDialogFragment.kt new file mode 100644 index 000000000..e1ac46c48 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LaunchGameDialogFragment.kt @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.fragments + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import androidx.navigation.fragment.findNavController +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.yuzu.yuzu_emu.HomeNavigationDirections +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.model.Game +import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable + +class LaunchGameDialogFragment : DialogFragment() { + private var selectedItem = 1 + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val game = requireArguments().parcelable<Game>(GAME) + val launchOptions = arrayOf(getString(R.string.global), getString(R.string.custom)) + + if (savedInstanceState != null) { + selectedItem = savedInstanceState.getInt(SELECTED_ITEM) + } + + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.launch_options) + .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> + val action = HomeNavigationDirections + .actionGlobalEmulationActivity(game, selectedItem != 0) + requireParentFragment().findNavController().navigate(action) + } + .setSingleChoiceItems(launchOptions, 1) { _: DialogInterface, i: Int -> + selectedItem = i + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putInt(SELECTED_ITEM, selectedItem) + } + + companion object { + const val TAG = "LaunchGameDialogFragment" + + const val GAME = "Game" + const val SELECTED_ITEM = "SelectedItem" + + fun newInstance(game: Game): LaunchGameDialogFragment { + val args = Bundle() + args.putParcelable(GAME, game) + val fragment = LaunchGameDialogFragment() + fragment.arguments = args + return fragment + } + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt index a6183d19e..32062b6fe 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MessageDialogFragment.kt @@ -27,30 +27,31 @@ class MessageDialogFragment : DialogFragment() { val descriptionString = requireArguments().getString(DESCRIPTION_STRING)!! val helpLinkId = requireArguments().getInt(HELP_LINK) - val dialog = MaterialAlertDialogBuilder(requireContext()) - .setPositiveButton(R.string.close, null) + val builder = MaterialAlertDialogBuilder(requireContext()) - if (titleId != 0) dialog.setTitle(titleId) - if (titleString.isNotEmpty()) dialog.setTitle(titleString) + if (messageDialogViewModel.positiveAction == null) { + builder.setPositiveButton(R.string.close, null) + } else { + builder.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> + messageDialogViewModel.positiveAction?.invoke() + }.setNegativeButton(android.R.string.cancel, null) + } + + if (titleId != 0) builder.setTitle(titleId) + if (titleString.isNotEmpty()) builder.setTitle(titleString) if (descriptionId != 0) { - dialog.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY)) + builder.setMessage(Html.fromHtml(getString(descriptionId), Html.FROM_HTML_MODE_LEGACY)) } - if (descriptionString.isNotEmpty()) dialog.setMessage(descriptionString) + if (descriptionString.isNotEmpty()) builder.setMessage(descriptionString) if (helpLinkId != 0) { - dialog.setNeutralButton(R.string.learn_more) { _, _ -> + builder.setNeutralButton(R.string.learn_more) { _, _ -> openLink(getString(helpLinkId)) } } - return dialog.show() - } - - override fun onDismiss(dialog: DialogInterface) { - super.onDismiss(dialog) - messageDialogViewModel.dismissAction.invoke() - messageDialogViewModel.clear() + return builder.show() } private fun openLink(link: String) { @@ -74,7 +75,7 @@ class MessageDialogFragment : DialogFragment() { descriptionId: Int = 0, descriptionString: String = "", helpLinkId: Int = 0, - dismissAction: () -> Unit = {} + positiveAction: (() -> Unit)? = null ): MessageDialogFragment { val dialog = MessageDialogFragment() val bundle = Bundle() @@ -85,8 +86,10 @@ class MessageDialogFragment : DialogFragment() { putString(DESCRIPTION_STRING, descriptionString) putInt(HELP_LINK, helpLinkId) } - ViewModelProvider(activity)[MessageDialogViewModel::class.java].dismissAction = - dismissAction + ViewModelProvider(activity)[MessageDialogViewModel::class.java].apply { + clear() + this.positiveAction = positiveAction + } dialog.arguments = bundle return dialog } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt index 2dbca76a5..64b295fbd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.repeatOnLifecycle import androidx.preference.PreferenceManager import info.debatty.java.stringsimilarity.Jaccard import info.debatty.java.stringsimilarity.JaroWinkler +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import java.util.Locale import org.yuzu.yuzu_emu.R @@ -60,7 +61,9 @@ class SearchFragment : Fragment() { // This is using the correct scope, lint is just acting up @SuppressLint("UnsafeRepeatOnLifecycleDetector") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - homeViewModel.setNavigationVisibility(visible = true, animated = false) + super.onViewCreated(view, savedInstanceState) + homeViewModel.setNavigationVisibility(visible = true, animated = true) + homeViewModel.setStatusBarShadeVisibility(true) preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) if (savedInstanceState != null) { @@ -99,7 +102,7 @@ class SearchFragment : Fragment() { } launch { repeatOnLifecycle(Lifecycle.State.CREATED) { - gamesViewModel.games.collect { filterAndSearch() } + gamesViewModel.games.collectLatest { filterAndSearch() } } } launch { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt index b88d2c038..60e029f34 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt @@ -70,7 +70,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener sliderBinding = DialogSliderBinding.inflate(layoutInflater) val item = settingsViewModel.clickedItem as SliderSetting - settingsViewModel.setSliderTextValue(item.selectedValue.toFloat(), item.units) + settingsViewModel.setSliderTextValue(item.getSelectedValue().toFloat(), item.units) sliderBinding.slider.apply { valueFrom = item.min.toFloat() valueTo = item.max.toFloat() @@ -136,18 +136,18 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener is SingleChoiceSetting -> { val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting val value = getValueForSingleChoiceSelection(scSetting, which) - scSetting.selectedValue = value + scSetting.setSelectedValue(value) } is StringSingleChoiceSetting -> { val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting val value = scSetting.getValueAt(which) - scSetting.selectedValue = value + scSetting.setSelectedValue(value) } is SliderSetting -> { val sliderSetting = settingsViewModel.clickedItem as SliderSetting - sliderSetting.selectedValue = settingsViewModel.sliderProgress.value + sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value) } } closeDialog() @@ -171,7 +171,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener } private fun getSelectionForSingleChoiceValue(item: SingleChoiceSetting): Int { - val value = item.selectedValue + val value = item.getSelectedValue() val valuesId = item.valuesId if (valuesId > 0) { val valuesArray = requireContext().resources.getIntArray(valuesId) @@ -211,7 +211,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener throw IllegalArgumentException("[SettingsDialogFragment] Incompatible type!") SettingsItem.TYPE_SLIDER -> settingsViewModel.setSliderProgress( - (clickedItem as SliderSetting).selectedValue.toFloat() + (clickedItem as SliderSetting).getSelectedValue().toFloat() ) } settingsViewModel.clickedItem = clickedItem 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/Addon.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Addon.kt new file mode 100644 index 000000000..ed79a8b02 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Addon.kt @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.model + +data class Addon( + var enabled: Boolean, + val title: String, + val version: String +) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt new file mode 100644 index 000000000..075252f5b --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.model + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.utils.NativeConfig +import java.util.concurrent.atomic.AtomicBoolean + +class AddonViewModel : ViewModel() { + private val _addonList = MutableStateFlow(mutableListOf<Addon>()) + val addonList get() = _addonList.asStateFlow() + + private val _showModInstallPicker = MutableStateFlow(false) + val showModInstallPicker get() = _showModInstallPicker.asStateFlow() + + private val _showModNoticeDialog = MutableStateFlow(false) + val showModNoticeDialog get() = _showModNoticeDialog.asStateFlow() + + var game: Game? = null + + private val isRefreshing = AtomicBoolean(false) + + fun onOpenAddons(game: Game) { + this.game = game + refreshAddons() + } + + fun refreshAddons() { + if (isRefreshing.get() || game == null) { + return + } + isRefreshing.set(true) + viewModelScope.launch { + withContext(Dispatchers.IO) { + val addonList = mutableListOf<Addon>() + val disabledAddons = NativeConfig.getDisabledAddons(game!!.programId) + NativeLibrary.getAddonsForFile(game!!.path, game!!.programId)?.forEach { + val name = it.first.replace("[D] ", "") + addonList.add(Addon(!disabledAddons.contains(name), name, it.second)) + } + addonList.sortBy { it.title } + _addonList.value = addonList + isRefreshing.set(false) + } + } + } + + fun onCloseAddons() { + if (_addonList.value.isEmpty()) { + return + } + + NativeConfig.setDisabledAddons( + game!!.programId, + _addonList.value.mapNotNull { + if (it.enabled) { + null + } else { + it.title + } + }.toTypedArray() + ) + NativeConfig.saveGlobalConfig() + _addonList.value.clear() + game = null + } + + fun showModInstallPicker(install: Boolean) { + _showModInstallPicker.value = install + } + + fun showModNoticeDialog(show: Boolean) { + _showModNoticeDialog.value = show + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt index 62945ad65..76accf8f3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt @@ -7,81 +7,83 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.features.settings.model.StringSetting +import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.GpuDriverHelper import org.yuzu.yuzu_emu.utils.GpuDriverMetadata +import org.yuzu.yuzu_emu.utils.NativeConfig import java.io.BufferedOutputStream import java.io.File class DriverViewModel : ViewModel() { private val _areDriversLoading = MutableStateFlow(false) - val areDriversLoading: StateFlow<Boolean> get() = _areDriversLoading - private val _isDriverReady = MutableStateFlow(true) - val isDriverReady: StateFlow<Boolean> get() = _isDriverReady - private val _isDeletingDrivers = MutableStateFlow(false) - val isDeletingDrivers: StateFlow<Boolean> get() = _isDeletingDrivers - private val _driverList = MutableStateFlow(mutableListOf<Pair<String, GpuDriverMetadata>>()) + val isInteractionAllowed: StateFlow<Boolean> = + combine( + _areDriversLoading, + _isDriverReady, + _isDeletingDrivers + ) { loading, ready, deleting -> + !loading && ready && !deleting + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false) + + private val _driverList = MutableStateFlow(GpuDriverHelper.getDrivers()) val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList var previouslySelectedDriver = 0 var selectedDriver = -1 - private val _selectedDriverMetadata = - MutableStateFlow( - GpuDriverHelper.customDriverData.name - ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) - ) - val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata + // Used for showing which driver is currently installed within the driver manager card + private val _selectedDriverTitle = MutableStateFlow("") + val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle private val _newDriverInstalled = MutableStateFlow(false) val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled val driversToDelete = mutableListOf<String>() - val isInteractionAllowed - get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value - init { - _areDriversLoading.value = true - viewModelScope.launch { - withContext(Dispatchers.IO) { - val drivers = GpuDriverHelper.getDrivers() - val currentDriverMetadata = GpuDriverHelper.customDriverData - for (i in drivers.indices) { - if (drivers[i].second == currentDriverMetadata) { - setSelectedDriverIndex(i) - break - } - } - - // If a user had installed a driver before the manager was implemented, this zips - // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can - // be indexed and exported as expected. - if (selectedDriver == -1) { - val driverToSave = - File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip") - driverToSave.createNewFile() - FileUtil.zipFromInternalStorage( - File(GpuDriverHelper.driverInstallationPath!!), - GpuDriverHelper.driverInstallationPath!!, - BufferedOutputStream(driverToSave.outputStream()) - ) - drivers.add(Pair(driverToSave.path, currentDriverMetadata)) - setSelectedDriverIndex(drivers.size - 1) - } + val currentDriverMetadata = GpuDriverHelper.installedCustomDriverData + findSelectedDriver(currentDriverMetadata) + + // If a user had installed a driver before the manager was implemented, this zips + // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can + // be indexed and exported as expected. + if (selectedDriver == -1) { + val driverToSave = + File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip") + driverToSave.createNewFile() + FileUtil.zipFromInternalStorage( + File(GpuDriverHelper.driverInstallationPath!!), + GpuDriverHelper.driverInstallationPath!!, + BufferedOutputStream(driverToSave.outputStream()) + ) + _driverList.value.add(Pair(driverToSave.path, currentDriverMetadata)) + setSelectedDriverIndex(_driverList.value.size - 1) + } - _driverList.value = drivers - _areDriversLoading.value = false - } + // If a user had installed a driver before the config was reworked to be multiplatform, + // we have save the path of the previously selected driver to the new setting. + if (StringSetting.DRIVER_PATH.getString(true).isEmpty() && selectedDriver > 0 && + StringSetting.DRIVER_PATH.global + ) { + StringSetting.DRIVER_PATH.setString(_driverList.value[selectedDriver].first) + NativeConfig.saveGlobalConfig() + } else { + findSelectedDriver(GpuDriverHelper.customDriverSettingData) } + updateDriverNameForGame(null) } fun setSelectedDriverIndex(value: Int) { @@ -98,9 +100,9 @@ class DriverViewModel : ViewModel() { fun addDriver(driverData: Pair<String, GpuDriverMetadata>) { val driverIndex = _driverList.value.indexOfFirst { it == driverData } if (driverIndex == -1) { - setSelectedDriverIndex(_driverList.value.size) _driverList.value.add(driverData) - _selectedDriverMetadata.value = driverData.second.name + setSelectedDriverIndex(_driverList.value.size - 1) + _selectedDriverTitle.value = driverData.second.name ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) } else { setSelectedDriverIndex(driverIndex) @@ -111,8 +113,31 @@ class DriverViewModel : ViewModel() { _driverList.value.remove(driverData) } - fun onCloseDriverManager() { + fun onOpenDriverManager(game: Game?) { + if (game != null) { + SettingsFile.loadCustomConfig(game) + } + + val driverPath = StringSetting.DRIVER_PATH.getString() + if (driverPath.isEmpty()) { + setSelectedDriverIndex(0) + } else { + findSelectedDriver(GpuDriverHelper.getMetadataFromZip(File(driverPath))) + } + } + + fun onCloseDriverManager(game: Game?) { _isDeletingDrivers.value = true + StringSetting.DRIVER_PATH.setString(driverList.value[selectedDriver].first) + updateDriverNameForGame(game) + if (game == null) { + NativeConfig.saveGlobalConfig() + } else { + NativeConfig.savePerGameConfig() + NativeConfig.unloadPerGameConfig() + NativeConfig.reloadGlobalConfig() + } + viewModelScope.launch { withContext(Dispatchers.IO) { driversToDelete.forEach { @@ -125,23 +150,29 @@ class DriverViewModel : ViewModel() { _isDeletingDrivers.value = false } } + } + + // It is the Emulation Fragment's responsibility to load per-game settings so that this function + // knows what driver to load. + fun onLaunchGame() { + _isDriverReady.value = false - if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) { + val selectedDriverFile = File(StringSetting.DRIVER_PATH.getString()) + val selectedDriverMetadata = GpuDriverHelper.customDriverSettingData + if (GpuDriverHelper.installedCustomDriverData == selectedDriverMetadata) { return } - _isDriverReady.value = false viewModelScope.launch { withContext(Dispatchers.IO) { - if (selectedDriver == 0) { + if (selectedDriverMetadata.name == null) { GpuDriverHelper.installDefaultDriver() setDriverReady() return@withContext } - val driverToInstall = File(driverList.value[selectedDriver].first) - if (driverToInstall.exists()) { - GpuDriverHelper.installCustomDriver(driverToInstall) + if (selectedDriverFile.exists()) { + GpuDriverHelper.installCustomDriver(selectedDriverFile) } else { GpuDriverHelper.installDefaultDriver() } @@ -150,9 +181,43 @@ class DriverViewModel : ViewModel() { } } + private fun findSelectedDriver(currentDriverMetadata: GpuDriverMetadata) { + if (driverList.value.size == 1) { + setSelectedDriverIndex(0) + return + } + + driverList.value.forEachIndexed { i: Int, driver: Pair<String, GpuDriverMetadata> -> + if (driver.second == currentDriverMetadata) { + setSelectedDriverIndex(i) + return + } + } + } + + fun updateDriverNameForGame(game: Game?) { + if (!GpuDriverHelper.supportsCustomDriverLoading()) { + return + } + + if (game == null || NativeConfig.isPerGameConfigLoaded()) { + updateName() + } else { + SettingsFile.loadCustomConfig(game) + updateName() + NativeConfig.unloadPerGameConfig() + NativeConfig.reloadGlobalConfig() + } + } + + private fun updateName() { + _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name + ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) + } + private fun setDriverReady() { _isDriverReady.value = true - _selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name + _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index 2fa3ab31b..f1ea1e20f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt @@ -3,10 +3,18 @@ package org.yuzu.yuzu_emu.model +import android.net.Uri import android.os.Parcelable import java.util.HashSet import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.utils.DirectoryInitialization +import org.yuzu.yuzu_emu.utils.FileUtil +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter @Parcelize @Serializable @@ -15,12 +23,44 @@ class Game( val path: String, val programId: String = "", val developer: String = "", - val version: String = "", + var version: String = "", val isHomebrew: Boolean = false ) : Parcelable { val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime" val keyLastPlayedTime get() = "${path}_LastPlayed" + val settingsName: String + get() { + val programIdLong = programId.toLong() + return if (programIdLong == 0L) { + FileUtil.getFilename(Uri.parse(path)) + } else { + "0" + programIdLong.toString(16).uppercase() + } + } + + val programIdHex: String + get() { + val programIdLong = programId.toLong() + return if (programIdLong == 0L) { + "0" + } else { + "0" + programIdLong.toString(16).uppercase() + } + } + + val saveZipName: String + get() = "$title ${YuzuApplication.appContext.getString(R.string.save_data).lowercase()} - ${ + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) + }.zip" + + val saveDir: String + get() = DirectoryInitialization.userDirectory + "/nand" + + NativeLibrary.getSavePath(programId) + + val addonDir: String + get() = DirectoryInitialization.userDirectory + "/load/" + programIdHex + "/" + override fun equals(other: Any?): Boolean { if (other !is Game) { return false @@ -34,6 +74,7 @@ class Game( result = 31 * result + path.hashCode() result = 31 * result + programId.hashCode() result = 31 * result + developer.hashCode() + result = 31 * result + version.hashCode() result = 31 * result + isHomebrew.hashCode() return result } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameProperties.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameProperties.kt new file mode 100644 index 000000000..0135a95be --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameProperties.kt @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.model + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import kotlinx.coroutines.flow.StateFlow + +interface GameProperty { + @get:StringRes + val titleId: Int + + @get:StringRes + val descriptionId: Int + + @get:DrawableRes + val iconId: Int +} + +data class SubmenuProperty( + override val titleId: Int, + override val descriptionId: Int, + override val iconId: Int, + val details: (() -> String)? = null, + val detailsFlow: StateFlow<String>? = null, + val action: () -> Unit +) : GameProperty + +data class InstallableProperty( + override val titleId: Int, + override val descriptionId: Int, + override val iconId: Int, + val install: (() -> Unit)? = null, + val export: (() -> Unit)? = null +) : GameProperty 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..d19f20dc2 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 @@ -20,8 +20,8 @@ import kotlinx.serialization.json.Json import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.utils.GameHelper -import org.yuzu.yuzu_emu.utils.GameMetadata import org.yuzu.yuzu_emu.utils.NativeConfig +import java.util.concurrent.atomic.AtomicBoolean class GamesViewModel : ViewModel() { val games: StateFlow<List<Game>> get() = _games @@ -33,6 +33,8 @@ class GamesViewModel : ViewModel() { val isReloading: StateFlow<Boolean> get() = _isReloading private val _isReloading = MutableStateFlow(false) + private val reloading = AtomicBoolean(false) + val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData private val _shouldSwapData = MutableStateFlow(false) @@ -49,38 +51,8 @@ class GamesViewModel : ViewModel() { // Ensure keys are loaded so that ROM metadata can be decrypted. NativeLibrary.reloadKeys() - // Retrieve list of cached games - val storedGames = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - .getStringSet(GameHelper.KEY_GAMES, emptySet()) - - viewModelScope.launch { - withContext(Dispatchers.IO) { - getGameDirs() - if (storedGames!!.isNotEmpty()) { - val deserializedGames = mutableSetOf<Game>() - storedGames.forEach { - val game: Game - try { - game = Json.decodeFromString(it) - } catch (e: Exception) { - // We don't care about any errors related to parsing the game cache - return@forEach - } - - val gameExists = - DocumentFile.fromSingleUri( - YuzuApplication.appContext, - Uri.parse(game.path) - )?.exists() - if (gameExists == true) { - deserializedGames.add(game) - } - } - setGames(deserializedGames.toList()) - } - reloadGames(false) - } - } + getGameDirs() + reloadGames(directoriesChanged = false, firstStartup = true) } fun setGames(games: List<Game>) { @@ -110,16 +82,46 @@ class GamesViewModel : ViewModel() { _searchFocused.value = searchFocused } - fun reloadGames(directoriesChanged: Boolean) { - if (isReloading.value) { + fun reloadGames(directoriesChanged: Boolean, firstStartup: Boolean = false) { + if (reloading.get()) { return } + reloading.set(true) _isReloading.value = true viewModelScope.launch { withContext(Dispatchers.IO) { - GameMetadata.resetMetadata() + if (firstStartup) { + // Retrieve list of cached games + val storedGames = + PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) + .getStringSet(GameHelper.KEY_GAMES, emptySet()) + if (storedGames!!.isNotEmpty()) { + val deserializedGames = mutableSetOf<Game>() + storedGames.forEach { + val game: Game + try { + game = Json.decodeFromString(it) + } catch (e: Exception) { + // We don't care about any errors related to parsing the game cache + return@forEach + } + + val gameExists = + DocumentFile.fromSingleUri( + YuzuApplication.appContext, + Uri.parse(game.path) + )?.exists() + if (gameExists == true) { + deserializedGames.add(game) + } + } + setGames(deserializedGames.toList()) + } + } + setGames(GameHelper.getGames()) + reloading.set(false) _isReloading.value = false if (directoriesChanged) { @@ -168,6 +170,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/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt index 07e65b028..513ac2fc5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt @@ -3,6 +3,7 @@ package org.yuzu.yuzu_emu.model +import android.net.Uri import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -21,6 +22,15 @@ class HomeViewModel : ViewModel() { private val _gamesDirSelected = MutableStateFlow(false) val gamesDirSelected get() = _gamesDirSelected.asStateFlow() + private val _openImportSaves = MutableStateFlow(false) + val openImportSaves get() = _openImportSaves.asStateFlow() + + private val _contentToInstall = MutableStateFlow<List<Uri>?>(null) + val contentToInstall get() = _contentToInstall.asStateFlow() + + private val _reloadPropertiesList = MutableStateFlow(false) + val reloadPropertiesList get() = _reloadPropertiesList.asStateFlow() + var navigatedToSetup = false fun setNavigationVisibility(visible: Boolean, animated: Boolean) { @@ -44,4 +54,16 @@ class HomeViewModel : ViewModel() { fun setGamesDirSelected(selected: Boolean) { _gamesDirSelected.value = selected } + + fun setOpenImportSaves(import: Boolean) { + _openImportSaves.value = import + } + + fun setContentToInstall(documents: List<Uri>?) { + _contentToInstall.value = documents + } + + fun reloadPropertiesList(reload: Boolean) { + _reloadPropertiesList.value = reload + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt index 36ffd08d2..641c5cb17 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/MessageDialogViewModel.kt @@ -6,9 +6,9 @@ package org.yuzu.yuzu_emu.model import androidx.lifecycle.ViewModel class MessageDialogViewModel : ViewModel() { - var dismissAction: () -> Unit = {} + var positiveAction: (() -> Unit)? = null fun clear() { - dismissAction = {} + positiveAction = null } } 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/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt index 16a794dee..e59c95733 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt @@ -23,7 +23,7 @@ class TaskViewModel : ViewModel() { val cancelled: StateFlow<Boolean> get() = _cancelled private val _cancelled = MutableStateFlow(false) - lateinit var task: () -> Any + lateinit var task: suspend () -> Any fun clear() { _result.value = Any() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index 805b89b31..fc0eeb9ad 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt @@ -19,7 +19,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.color.MaterialColors -import com.google.android.material.transition.MaterialFadeThrough +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.adapters.GameAdapter @@ -35,11 +35,6 @@ class GamesFragment : Fragment() { private val gamesViewModel: GamesViewModel by activityViewModels() private val homeViewModel: HomeViewModel by activityViewModels() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enterTransition = MaterialFadeThrough() - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -52,7 +47,9 @@ class GamesFragment : Fragment() { // This is using the correct scope, lint is just acting up @SuppressLint("UnsafeRepeatOnLifecycleDetector") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - homeViewModel.setNavigationVisibility(visible = true, animated = false) + super.onViewCreated(view, savedInstanceState) + homeViewModel.setNavigationVisibility(visible = true, animated = true) + homeViewModel.setStatusBarShadeVisibility(true) binding.gridGames.apply { layoutManager = AutofitGridLayoutManager( @@ -99,7 +96,7 @@ class GamesFragment : Fragment() { } launch { repeatOnLifecycle(Lifecycle.State.RESUMED) { - gamesViewModel.games.collect { + gamesViewModel.games.collectLatest { (binding.gridGames.adapter as GameAdapter).submitList(it) if (it.isEmpty()) { binding.noticeText.visibility = View.VISIBLE 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 16323a316..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 @@ -43,7 +40,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.MessageDialogFragment -import org.yuzu.yuzu_emu.getPublicFilesDir +import org.yuzu.yuzu_emu.model.AddonViewModel import org.yuzu.yuzu_emu.model.GamesViewModel import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.TaskState @@ -60,15 +57,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider { private val homeViewModel: HomeViewModel by viewModels() private val gamesViewModel: GamesViewModel by viewModels() private val taskViewModel: TaskViewModel by viewModels() + private val addonViewModel: AddonViewModel by viewModels() override var themeId: Int = 0 - private val savesFolder - get() = "${getPublicFilesDir().canonicalPath}/nand/user/save/0000000000000000" - - // Get first subfolder in saves folder (should be the user folder) - val savesFolderRoot get() = File(savesFolder).listFiles()?.firstOrNull()?.canonicalPath ?: "" - override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady } @@ -145,6 +137,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider { homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) } } } + launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + homeViewModel.contentToInstall.collect { + if (it != null) { + installContent(it) + homeViewModel.setContentToInstall(null) + } + } + } + } } // Dismiss previous notifications (should not happen unless a crash occurred) @@ -253,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() @@ -468,110 +463,150 @@ class MainActivity : AppCompatActivity(), ThemeProvider { val installGameUpdate = registerForActivityResult( ActivityResultContracts.OpenMultipleDocuments() ) { documents: List<Uri> -> - if (documents.isNotEmpty()) { - IndeterminateProgressDialogFragment.newInstance( - this@MainActivity, - R.string.installing_game_content - ) { - var installSuccess = 0 - var installOverwrite = 0 - var errorBaseGame = 0 - var errorExtension = 0 - var errorOther = 0 - documents.forEach { - when ( - NativeLibrary.installFileToNand( - it.toString(), - FileUtil.getExtension(it) - ) - ) { - NativeLibrary.InstallFileToNandResult.Success -> { - installSuccess += 1 - } + if (documents.isEmpty()) { + return@registerForActivityResult + } - NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { - installOverwrite += 1 - } + if (addonViewModel.game == null) { + installContent(documents) + return@registerForActivityResult + } - NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { - errorBaseGame += 1 - } + IndeterminateProgressDialogFragment.newInstance( + this@MainActivity, + R.string.verifying_content, + false + ) { + var updatesMatchProgram = true + for (document in documents) { + val valid = NativeLibrary.doesUpdateMatchProgram( + addonViewModel.game!!.programId, + document.toString() + ) + if (!valid) { + updatesMatchProgram = false + break + } + } - NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { - errorExtension += 1 - } + if (updatesMatchProgram) { + homeViewModel.setContentToInstall(documents) + } else { + MessageDialogFragment.newInstance( + this@MainActivity, + titleId = R.string.content_install_notice, + descriptionId = R.string.content_install_notice_description, + positiveAction = { homeViewModel.setContentToInstall(documents) } + ) + } + }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) + } - else -> { - errorOther += 1 - } + private fun installContent(documents: List<Uri>) { + IndeterminateProgressDialogFragment.newInstance( + this@MainActivity, + R.string.installing_game_content + ) { + var installSuccess = 0 + var installOverwrite = 0 + var errorBaseGame = 0 + var errorExtension = 0 + var errorOther = 0 + documents.forEach { + when ( + NativeLibrary.installFileToNand( + it.toString(), + FileUtil.getExtension(it) + ) + ) { + NativeLibrary.InstallFileToNandResult.Success -> { + installSuccess += 1 + } + + NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> { + installOverwrite += 1 + } + + NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> { + errorBaseGame += 1 + } + + NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> { + errorExtension += 1 + } + + else -> { + errorOther += 1 } } + } - val separator = System.getProperty("line.separator") ?: "\n" - val installResult = StringBuilder() - if (installSuccess > 0) { - installResult.append( - getString( - R.string.install_game_content_success_install, - installSuccess - ) + addonViewModel.refreshAddons() + + val separator = System.getProperty("line.separator") ?: "\n" + val installResult = StringBuilder() + if (installSuccess > 0) { + installResult.append( + getString( + R.string.install_game_content_success_install, + installSuccess ) + ) + installResult.append(separator) + } + if (installOverwrite > 0) { + installResult.append( + getString( + R.string.install_game_content_success_overwrite, + installOverwrite + ) + ) + installResult.append(separator) + } + val errorTotal: Int = errorBaseGame + errorExtension + errorOther + if (errorTotal > 0) { + installResult.append(separator) + installResult.append( + getString( + R.string.install_game_content_failed_count, + errorTotal + ) + ) + installResult.append(separator) + if (errorBaseGame > 0) { installResult.append(separator) - } - if (installOverwrite > 0) { installResult.append( - getString( - R.string.install_game_content_success_overwrite, - installOverwrite - ) + getString(R.string.install_game_content_failure_base) ) installResult.append(separator) } - val errorTotal: Int = errorBaseGame + errorExtension + errorOther - if (errorTotal > 0) { + if (errorExtension > 0) { installResult.append(separator) installResult.append( - getString( - R.string.install_game_content_failed_count, - errorTotal - ) + getString(R.string.install_game_content_failure_file_extension) ) installResult.append(separator) - if (errorBaseGame > 0) { - installResult.append(separator) - installResult.append( - getString(R.string.install_game_content_failure_base) - ) - installResult.append(separator) - } - if (errorExtension > 0) { - installResult.append(separator) - installResult.append( - getString(R.string.install_game_content_failure_file_extension) - ) - installResult.append(separator) - } - if (errorOther > 0) { - installResult.append( - getString(R.string.install_game_content_failure_description) - ) - installResult.append(separator) - } - return@newInstance MessageDialogFragment.newInstance( - this, - titleId = R.string.install_game_content_failure, - descriptionString = installResult.toString().trim(), - helpLinkId = R.string.install_game_content_help_link - ) - } else { - return@newInstance MessageDialogFragment.newInstance( - this, - titleId = R.string.install_game_content_success, - descriptionString = installResult.toString().trim() + } + if (errorOther > 0) { + installResult.append( + getString(R.string.install_game_content_failure_description) ) + installResult.append(separator) } - }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) - } + return@newInstance MessageDialogFragment.newInstance( + this, + titleId = R.string.install_game_content_failure, + descriptionString = installResult.toString().trim(), + helpLinkId = R.string.install_game_content_help_link + ) + } else { + return@newInstance MessageDialogFragment.newInstance( + this, + titleId = R.string.install_game_content_success, + descriptionString = installResult.toString().trim() + ) + } + }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) } val exportUserData = registerForActivityResult( @@ -632,7 +667,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } // Clear existing user data - NativeConfig.unloadConfig() + NativeConfig.unloadGlobalConfig() File(DirectoryInitialization.userDirectory!!).deleteRecursively() // Copy archive to internal storage @@ -651,108 +686,10 @@ 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) }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) } - - /** - * Exports the save file located in the given folder path by creating a zip file and sharing it via intent. - */ - val exportSaves = registerForActivityResult( - ActivityResultContracts.CreateDocument("application/zip") - ) { result -> - if (result == null) { - return@registerForActivityResult - } - - IndeterminateProgressDialogFragment.newInstance( - this, - R.string.save_files_exporting, - false - ) { - val zipResult = FileUtil.zipFromInternalStorage( - File(savesFolderRoot), - savesFolderRoot, - BufferedOutputStream(contentResolver.openOutputStream(result)) - ) - return@newInstance when (zipResult) { - TaskState.Completed -> getString(R.string.export_success) - TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed) - } - }.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG) - } - - private val startForResultExportSave = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ -> - File(getPublicFilesDir().canonicalPath, "temp").deleteRecursively() - } - - val importSaves = - registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> - if (result == null) { - return@registerForActivityResult - } - - NativeLibrary.initializeEmptyUserDirectory() - - val inputZip = contentResolver.openInputStream(result) - // A zip needs to have at least one subfolder named after a TitleId in order to be considered valid. - var validZip = false - val savesFolder = File(savesFolderRoot) - val cacheSaveDir = File("${applicationContext.cacheDir.path}/saves/") - cacheSaveDir.mkdir() - - if (inputZip == null) { - Toast.makeText( - applicationContext, - getString(R.string.fatal_error), - Toast.LENGTH_LONG - ).show() - return@registerForActivityResult - } - - val filterTitleId = - FilenameFilter { _, dirName -> dirName.matches(Regex("^0100[\\dA-Fa-f]{12}$")) } - - try { - CoroutineScope(Dispatchers.IO).launch { - FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir) - cacheSaveDir.list(filterTitleId)?.forEach { savePath -> - File(savesFolder, savePath).deleteRecursively() - File(cacheSaveDir, savePath).copyRecursively( - File(savesFolder, savePath), - true - ) - validZip = true - } - - withContext(Dispatchers.Main) { - if (!validZip) { - MessageDialogFragment.newInstance( - this@MainActivity, - titleId = R.string.save_file_invalid_zip_structure, - descriptionId = R.string.save_file_invalid_zip_structure_description - ).show(supportFragmentManager, MessageDialogFragment.TAG) - return@withContext - } - Toast.makeText( - applicationContext, - getString(R.string.save_file_imported_success), - Toast.LENGTH_LONG - ).show() - } - - cacheSaveDir.deleteRecursively() - } - } catch (e: Exception) { - Toast.makeText( - applicationContext, - getString(R.string.fatal_error), - Toast.LENGTH_LONG - ).show() - } - } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt new file mode 100644 index 000000000..8cc5ea71f --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/AddonUtil.kt @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.utils + +object AddonUtil { + val validAddonDirectories = listOf("cheats", "exefs", "romfs") +} 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/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt index bbe7bfa92..00c6bf90e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt @@ -22,6 +22,7 @@ import java.io.BufferedOutputStream import java.lang.NullPointerException import java.nio.charset.StandardCharsets import java.util.zip.ZipOutputStream +import kotlin.IllegalStateException object FileUtil { const val PATH_TREE = "tree" @@ -342,6 +343,37 @@ object FileUtil { return TaskState.Completed } + /** + * Helper function that copies the contents of a DocumentFile folder into a [File] + * @param file [File] representation of the folder to copy into + * @throws IllegalStateException Fails when trying to copy a folder into a file and vice versa + */ + fun DocumentFile.copyFilesTo(file: File) { + file.mkdirs() + if (!this.isDirectory || !file.isDirectory) { + throw IllegalStateException( + "[FileUtil] Tried to copy a folder into a file or vice versa" + ) + } + + this.listFiles().forEach { + val newFile = File(file, it.name!!) + if (it.isDirectory) { + newFile.mkdirs() + DocumentFile.fromTreeUri(YuzuApplication.appContext, it.uri)?.copyFilesTo(newFile) + } else { + val inputStream = + YuzuApplication.appContext.contentResolver.openInputStream(it.uri) + BufferedInputStream(inputStream).use { bos -> + if (!newFile.exists()) { + newFile.createNewFile() + } + newFile.outputStream().use { os -> bos.copyTo(os) } + } + } + } + } + fun isRootTreeUri(uri: Uri): Boolean { val paths = uri.pathSegments return paths.size == 2 && PATH_TREE == paths[0] diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 55010dc59..579b600f1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt @@ -36,6 +36,12 @@ object GameHelper { // Ensure keys are loaded so that ROM metadata can be decrypted. NativeLibrary.reloadKeys() + // Reset metadata so we don't use stale information + GameMetadata.resetMetadata() + + // Remove previous filesystem provider information so we can get up to date version info + NativeLibrary.clearFilesystemProvider() + val badDirs = mutableListOf<Int>() gameDirs.forEachIndexed { index: Int, gameDir: GameDir -> val gameDirUri = Uri.parse(gameDir.uriString) @@ -92,14 +98,24 @@ object GameHelper { ) } else { if (Game.extensions.contains(FileUtil.getExtension(it.uri))) { - games.add(getGame(it.uri, true)) + val game = getGame(it.uri, true) + if (game != null) { + games.add(game) + } } } } } - fun getGame(uri: Uri, addedToLibrary: Boolean): Game { + fun getGame(uri: Uri, addedToLibrary: Boolean): Game? { val filePath = uri.toString() + if (!GameMetadata.getIsValid(filePath)) { + return null + } + + // Needed to update installed content information + NativeLibrary.addFileToFilesystemProvider(filePath) + var name = GameMetadata.getTitle(filePath) // If the game's title field is empty, use the filename. @@ -118,7 +134,7 @@ object GameHelper { filePath, programId, GameMetadata.getDeveloper(filePath), - GameMetadata.getVersion(filePath), + GameMetadata.getVersion(filePath, false), GameMetadata.getIsHomebrew(filePath) ) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt index 0f3542ac6..8e412482a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt @@ -4,13 +4,15 @@ package org.yuzu.yuzu_emu.utils object GameMetadata { + external fun getIsValid(path: String): Boolean + external fun getTitle(path: String): String external fun getProgramId(path: String): String external fun getDeveloper(path: String): String - external fun getVersion(path: String): String + external fun getVersion(path: String, reload: Boolean): String external fun getIcon(path: String): ByteArray diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt index f6882ce6c..685272288 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt @@ -10,6 +10,8 @@ import java.io.File import java.io.IOException import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.YuzuApplication +import org.yuzu.yuzu_emu.features.settings.model.StringSetting +import java.io.FileNotFoundException import java.util.zip.ZipException import java.util.zip.ZipFile @@ -44,7 +46,7 @@ object GpuDriverHelper { NativeLibrary.initializeGpuDriver( hookLibPath, driverInstallationPath, - customDriverData.libraryName, + installedCustomDriverData.libraryName, fileRedirectionPath ) } @@ -190,6 +192,7 @@ object GpuDriverHelper { } } } catch (_: ZipException) { + } catch (_: FileNotFoundException) { } return GpuDriverMetadata() } @@ -197,9 +200,12 @@ object GpuDriverHelper { external fun supportsCustomDriverLoading(): Boolean // Parse the custom driver metadata to retrieve the name. - val customDriverData: GpuDriverMetadata + val installedCustomDriverData: GpuDriverMetadata get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME)) + val customDriverSettingData: GpuDriverMetadata + get() = getMetadataFromZip(File(StringSetting.DRIVER_PATH.getString())) + fun initializeDirectories() { // Ensure the file redirection directory exists. val fileRedirectionDir = File(fileRedirectionPath!!) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt index 9076a86c4..0b94c73e5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt @@ -27,13 +27,13 @@ object MemoryUtil { const val Pb = Tb * 1024 const val Eb = Pb * 1024 - private fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String = + fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String = when { size < Kb -> { context.getString( R.string.memory_formatted, size.hundredths, - context.getString(R.string.memory_byte) + context.getString(R.string.memory_byte_shorthand) ) } size < Mb -> { 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 f4e1bb13f..7512d5eed 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,56 +7,113 @@ 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 + + @Synchronized external fun setBoolean(key: String, value: Boolean) - external fun getByte(key: String, getDefault: Boolean): Byte + @Synchronized + external fun getByte(key: String, needsGlobal: Boolean): Byte + + @Synchronized external fun setByte(key: String, value: Byte) - external fun getShort(key: String, getDefault: Boolean): Short + @Synchronized + external fun getShort(key: String, needsGlobal: Boolean): Short + + @Synchronized external fun setShort(key: String, value: Short) - external fun getInt(key: String, getDefault: Boolean): Int + @Synchronized + external fun getInt(key: String, needsGlobal: Boolean): Int + + @Synchronized external fun setInt(key: String, value: Int) - external fun getFloat(key: String, getDefault: Boolean): Float + @Synchronized + external fun getFloat(key: String, needsGlobal: Boolean): Float + + @Synchronized external fun setFloat(key: String, value: Float) - external fun getLong(key: String, getDefault: Boolean): Long + @Synchronized + external fun getLong(key: String, needsGlobal: Boolean): Long + + @Synchronized external fun setLong(key: String, value: Long) - external fun getString(key: String, getDefault: Boolean): String + @Synchronized + external fun getString(key: String, needsGlobal: Boolean): String + + @Synchronized external fun setString(key: String, value: String) external fun getIsRuntimeModifiable(key: String): Boolean - external fun getConfigHeader(category: Int): String - external fun getPairedSettingKey(key: String): String + external fun getIsSwitchable(key: String): Boolean + + @Synchronized + external fun usingGlobal(key: String): Boolean + + @Synchronized + external fun setGlobal(key: String, global: Boolean) + + external fun getIsSaveable(key: String): Boolean + + external fun getDefaultToString(key: String): String + /** * Gets every [GameDir] in AndroidSettings::values.game_dirs */ @@ -74,4 +131,23 @@ object NativeConfig { */ @Synchronized external fun addGameDir(dir: GameDir) + + /** + * Gets an array of the addons that are disabled for a given game + * + * @param programId String representation of a game's program ID + * @return An array of disabled addons + */ + @Synchronized + external fun getDisabledAddons(programId: String): Array<String> + + /** + * Clears the disabled addons array corresponding to [programId] and replaces them + * with [disabledAddons] + * + * @param programId String representation of a game's program ID + * @param disabledAddons Replacement array of disabled addons + */ + @Synchronized + external fun setDisabledAddons(programId: String, disabledAddons: Array<String>) } diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp index 767d8ea83..9c3a5a9b2 100644 --- a/src/android/app/src/main/jni/android_config.cpp +++ b/src/android/app/src/main/jni/android_config.cpp @@ -36,6 +36,7 @@ void AndroidConfig::ReadAndroidValues() { ReadAndroidUIValues(); ReadUIValues(); } + ReadDriverValues(); } void AndroidConfig::ReadAndroidUIValues() { @@ -57,6 +58,7 @@ void AndroidConfig::ReadUIValues() { void AndroidConfig::ReadPathValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); + AndroidSettings::values.game_dirs.clear(); const int gamedirs_size = BeginArray(std::string("gamedirs")); for (int i = 0; i < gamedirs_size; ++i) { SetArrayIndex(i); @@ -71,11 +73,20 @@ void AndroidConfig::ReadPathValues() { EndGroup(); } +void AndroidConfig::ReadDriverValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::GpuDriver)); + + ReadCategory(Settings::Category::GpuDriver); + + EndGroup(); +} + void AndroidConfig::SaveAndroidValues() { if (global) { SaveAndroidUIValues(); SaveUIValues(); } + SaveDriverValues(); WriteToIni(); } @@ -111,6 +122,14 @@ void AndroidConfig::SavePathValues() { EndGroup(); } +void AndroidConfig::SaveDriverValues() { + BeginGroup(Settings::TranslateCategory(Settings::Category::GpuDriver)); + + WriteCategory(Settings::Category::GpuDriver); + + EndGroup(); +} + std::vector<Settings::BasicSetting*>& AndroidConfig::FindRelevantList(Settings::Category category) { auto& map = Settings::values.linkage.by_category; if (map.contains(category)) { diff --git a/src/android/app/src/main/jni/android_config.h b/src/android/app/src/main/jni/android_config.h index f490be016..2c12874e1 100644 --- a/src/android/app/src/main/jni/android_config.h +++ b/src/android/app/src/main/jni/android_config.h @@ -17,6 +17,7 @@ public: protected: void ReadAndroidValues(); void ReadAndroidUIValues(); + void ReadDriverValues(); void ReadHidbusValues() override {} void ReadDebugControlValues() override {} void ReadPathValues() override; @@ -28,6 +29,7 @@ protected: void SaveAndroidValues(); void SaveAndroidUIValues(); + void SaveDriverValues(); void SaveHidbusValues() override {} void SaveDebugControlValues() override {} void SavePathValues() override; diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index fc0523206..3733f5a3c 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -30,6 +30,9 @@ struct Values { Settings::Specialization::Default, true, true}; + + Settings::SwitchableSetting<std::string, false> driver_path{linkage, "", "driver_path", + Settings::Category::GpuDriver}; }; extern Values values; diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp index 24d9df702..78f604c70 100644 --- a/src/android/app/src/main/jni/game_metadata.cpp +++ b/src/android/app/src/main/jni/game_metadata.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <core/core.h> +#include <core/file_sys/mode.h> #include <core/file_sys/patch_manager.h> #include <core/loader/nro.h> #include <jni.h> @@ -61,7 +62,11 @@ RomMetadata CacheRomMetadata(const std::string& path) { return entry; } -RomMetadata GetRomMetadata(const std::string& path) { +RomMetadata GetRomMetadata(const std::string& path, bool reload = false) { + if (reload) { + return CacheRomMetadata(path); + } + if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { return search->second; } @@ -71,6 +76,32 @@ RomMetadata GetRomMetadata(const std::string& path) { extern "C" { +jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsValid(JNIEnv* env, jobject obj, + jstring jpath) { + const auto file = EmulationSession::GetInstance().System().GetFilesystem()->OpenFile( + GetJString(env, jpath), FileSys::Mode::Read); + if (!file) { + return false; + } + + auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file); + if (!loader) { + return false; + } + + const auto file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { + return false; + } + + u64 program_id = 0; + Loader::ResultStatus res = loader->ReadProgramId(program_id); + if (res != Loader::ResultStatus::Success) { + return false; + } + return true; +} + jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj, jstring jpath) { return ToJString(env, GetRomMetadata(GetJString(env, jpath)).title); @@ -87,8 +118,8 @@ jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, job } jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj, - jstring jpath) { - return ToJString(env, GetRomMetadata(GetJString(env, jpath)).version); + jstring jpath, jboolean jreload) { + return ToJString(env, GetRomMetadata(GetJString(env, jpath), jreload).version); } jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj, @@ -106,7 +137,7 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, j } void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) { - return m_rom_metadata_cache.clear(); + m_rom_metadata_cache.clear(); } } // extern "C" diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index a56ed5662..e7a86d3fd 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -20,6 +20,21 @@ static jmethodID s_disk_cache_load_progress; static jmethodID s_on_emulation_started; static jmethodID s_on_emulation_stopped; +static jclass s_game_class; +static jmethodID s_game_constructor; +static jfieldID s_game_title_field; +static jfieldID s_game_path_field; +static jfieldID s_game_program_id_field; +static jfieldID s_game_developer_field; +static jfieldID s_game_version_field; +static jfieldID s_game_is_homebrew_field; + +static jclass s_string_class; +static jclass s_pair_class; +static jmethodID s_pair_constructor; +static jfieldID s_pair_first_field; +static jfieldID s_pair_second_field; + static constexpr jint JNI_VERSION = JNI_VERSION_1_6; namespace IDCache { @@ -79,6 +94,58 @@ jmethodID GetOnEmulationStopped() { return s_on_emulation_stopped; } +jclass GetGameClass() { + return s_game_class; +} + +jmethodID GetGameConstructor() { + return s_game_constructor; +} + +jfieldID GetGameTitleField() { + return s_game_title_field; +} + +jfieldID GetGamePathField() { + return s_game_path_field; +} + +jfieldID GetGameProgramIdField() { + return s_game_program_id_field; +} + +jfieldID GetGameDeveloperField() { + return s_game_developer_field; +} + +jfieldID GetGameVersionField() { + return s_game_version_field; +} + +jfieldID GetGameIsHomebrewField() { + return s_game_is_homebrew_field; +} + +jclass GetStringClass() { + return s_string_class; +} + +jclass GetPairClass() { + return s_pair_class; +} + +jmethodID GetPairConstructor() { + return s_pair_constructor; +} + +jfieldID GetPairFirstField() { + return s_pair_first_field; +} + +jfieldID GetPairSecondField() { + return s_pair_second_field; +} + } // namespace IDCache #ifdef __cplusplus @@ -115,6 +182,31 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { s_on_emulation_stopped = env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V"); + const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game"); + s_game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class)); + s_game_constructor = env->GetMethodID(game_class, "<init>", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;Ljava/lang/String;Z)V"); + s_game_title_field = env->GetFieldID(game_class, "title", "Ljava/lang/String;"); + s_game_path_field = env->GetFieldID(game_class, "path", "Ljava/lang/String;"); + s_game_program_id_field = env->GetFieldID(game_class, "programId", "Ljava/lang/String;"); + s_game_developer_field = env->GetFieldID(game_class, "developer", "Ljava/lang/String;"); + s_game_version_field = env->GetFieldID(game_class, "version", "Ljava/lang/String;"); + s_game_is_homebrew_field = env->GetFieldID(game_class, "isHomebrew", "Z"); + env->DeleteLocalRef(game_class); + + const jclass string_class = env->FindClass("java/lang/String"); + s_string_class = reinterpret_cast<jclass>(env->NewGlobalRef(string_class)); + env->DeleteLocalRef(string_class); + + const jclass pair_class = env->FindClass("kotlin/Pair"); + s_pair_class = reinterpret_cast<jclass>(env->NewGlobalRef(pair_class)); + s_pair_constructor = + env->GetMethodID(pair_class, "<init>", "(Ljava/lang/Object;Ljava/lang/Object;)V"); + s_pair_first_field = env->GetFieldID(pair_class, "first", "Ljava/lang/Object;"); + s_pair_second_field = env->GetFieldID(pair_class, "second", "Ljava/lang/Object;"); + env->DeleteLocalRef(pair_class); + // Initialize Android Storage Common::FS::Android::RegisterCallbacks(env, s_native_library_class); @@ -136,6 +228,9 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { env->DeleteGlobalRef(s_disk_cache_progress_class); env->DeleteGlobalRef(s_load_callback_stage_class); env->DeleteGlobalRef(s_game_dir_class); + env->DeleteGlobalRef(s_game_class); + env->DeleteGlobalRef(s_string_class); + env->DeleteGlobalRef(s_pair_class); // UnInitialize applets SoftwareKeyboard::CleanupJNI(env); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index 855649efa..24030be42 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -20,4 +20,19 @@ jmethodID GetDiskCacheLoadProgress(); jmethodID GetOnEmulationStarted(); jmethodID GetOnEmulationStopped(); +jclass GetGameClass(); +jmethodID GetGameConstructor(); +jfieldID GetGameTitleField(); +jfieldID GetGamePathField(); +jfieldID GetGameProgramIdField(); +jfieldID GetGameDeveloperField(); +jfieldID GetGameVersionField(); +jfieldID GetGameIsHomebrewField(); + +jclass GetStringClass(); +jclass GetPairClass(); +jmethodID GetPairConstructor(); +jfieldID GetPairFirstField(); +jfieldID GetPairSecondField(); + } // namespace IDCache diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index e5d3158c8..0c1db7d46 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -14,6 +14,7 @@ #include <android/api-level.h> #include <android/native_window_jni.h> #include <common/fs/fs.h> +#include <core/file_sys/patch_manager.h> #include <core/file_sys/savedata_factory.h> #include <core/loader/nro.h> #include <jni.h> @@ -79,6 +80,10 @@ Core::System& EmulationSession::System() { return m_system; } +FileSys::ManualContentProvider* EmulationSession::GetContentProvider() { + return m_manual_provider.get(); +} + const EmuWindow_Android& EmulationSession::Window() const { return *m_window; } @@ -455,6 +460,15 @@ void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) { static_cast<jint>(result)); } +u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) { + auto program_id_string = GetJString(env, jprogramId); + try { + return std::stoull(program_id_string); + } catch (...) { + return 0; + } +} + static Core::SystemResultStatus RunEmulation(const std::string& filepath) { MicroProfileOnThreadCreate("EmuThread"); SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -504,6 +518,27 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject GetJString(env, j_file_extension)); } +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, + jstring jprogramId, + jstring jupdatePath) { + u64 program_id = EmulationSession::GetProgramId(env, jprogramId); + std::string updatePath = GetJString(env, jupdatePath); + std::shared_ptr<FileSys::NSP> nsp = std::make_shared<FileSys::NSP>( + EmulationSession::GetInstance().System().GetFilesystem()->OpenFile(updatePath, + FileSys::Mode::Read)); + for (const auto& item : nsp->GetNCAs()) { + for (const auto& nca_details : item.second) { + if (nca_details.second->GetName().ends_with(".cnmt.nca")) { + auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL; + if (update_id == program_id) { + return true; + } + } + } + } + return false; +} + void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(JNIEnv* env, jclass clazz, jstring hook_lib_dir, jstring custom_driver_dir, @@ -665,13 +700,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass EmulationSession::GetInstance().InitializeSystem(reload); } -jint Java_org_yuzu_yuzu_1emu_NativeLibrary_defaultCPUCore(JNIEnv* env, jclass clazz) { - return {}; -} - -void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2Ljava_lang_String_2Z( - JNIEnv* env, jclass clazz, jstring j_file, jstring j_savestate, jboolean j_delete_savestate) {} - jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jclass clazz) { jdoubleArray j_stats = env->NewDoubleArray(4); @@ -696,9 +724,13 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jclass return ToJString(env, "JIT"); } -void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env, - jclass clazz, - jstring j_path) {} +void Java_org_yuzu_yuzu_1emu_NativeLibrary_applySettings(JNIEnv* env, jobject jobj) { + EmulationSession::GetInstance().System().ApplySettings(); +} + +void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj) { + Settings::LogSettings(); +} void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz, jstring j_path) { @@ -792,4 +824,69 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isFirmwareAvailable(JNIEnv* env, return true; } +jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getAddonsForFile(JNIEnv* env, jobject jobj, + jstring jpath, + jstring jprogramId) { + const auto path = GetJString(env, jpath); + const auto vFile = + Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path); + if (vFile == nullptr) { + return nullptr; + } + + auto& system = EmulationSession::GetInstance().System(); + auto program_id = EmulationSession::GetProgramId(env, jprogramId); + const FileSys::PatchManager pm{program_id, system.GetFileSystemController(), + system.GetContentProvider()}; + const auto loader = Loader::GetLoader(system, vFile); + + FileSys::VirtualFile update_raw; + loader->ReadUpdateRaw(update_raw); + + auto addons = pm.GetPatchVersionNames(update_raw); + auto jemptyString = ToJString(env, ""); + auto jemptyStringPair = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(), + jemptyString, jemptyString); + jobjectArray jaddonsArray = + env->NewObjectArray(addons.size(), IDCache::GetPairClass(), jemptyStringPair); + int i = 0; + for (const auto& addon : addons) { + jobject jaddon = env->NewObject(IDCache::GetPairClass(), IDCache::GetPairConstructor(), + ToJString(env, addon.first), ToJString(env, addon.second)); + env->SetObjectArrayElement(jaddonsArray, i, jaddon); + ++i; + } + return jaddonsArray; +} + +jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, + jstring jprogramId) { + auto program_id = EmulationSession::GetProgramId(env, jprogramId); + + auto& system = EmulationSession::GetInstance().System(); + + Service::Account::ProfileManager manager; + // TODO: Pass in a selected user once we get the relevant UI working + const auto user_id = manager.GetUser(static_cast<std::size_t>(0)); + ASSERT(user_id); + + const auto nandDir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); + auto vfsNandDir = system.GetFilesystem()->OpenDirectory(Common::FS::PathToUTF8String(nandDir), + FileSys::Mode::Read); + + const auto user_save_data_path = FileSys::SaveDataFactory::GetFullPath( + system, vfsNandDir, FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, + program_id, user_id->AsU128(), 0); + return ToJString(env, user_save_data_path); +} + +void Java_org_yuzu_yuzu_1emu_NativeLibrary_addFileToFilesystemProvider(JNIEnv* env, jobject jobj, + jstring jpath) { + EmulationSession::GetInstance().ConfigureFilesystemProvider(GetJString(env, jpath)); +} + +void Java_org_yuzu_yuzu_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* env, jobject jobj) { + EmulationSession::GetInstance().GetContentProvider()->ClearAllEntries(); +} + } // extern "C" diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index f1457bd1f..4a8049578 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -21,6 +21,7 @@ public: static EmulationSession& GetInstance(); const Core::System& System() const; Core::System& System(); + FileSys::ManualContentProvider* GetContentProvider(); const EmuWindow_Android& Window() const; EmuWindow_Android& Window(); @@ -54,6 +55,8 @@ public: static void OnEmulationStarted(); + static u64 GetProgramId(JNIEnv* env, jstring jprogramId); + private: static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max); static void OnEmulationStopped(Core::SystemResultStatus result); diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp index 763b2164c..324d9e9cd 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,19 +13,21 @@ #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 basic_setting = Settings::values.linkage.by_key[key]; + if (basic_setting != 0) { + return static_cast<Settings::Setting<T>*>(basic_setting); } - if (basicAndroidSetting != 0) { - return static_cast<Settings::Setting<T>*>(basicAndroidSetting); + auto basic_android_setting = AndroidSettings::values.linkage.by_key[key]; + if (basic_android_setting != 0) { + return static_cast<Settings::Setting<T>*>(basic_android_setting); } LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key); return nullptr; @@ -32,35 +35,52 @@ 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_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<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, - jstring jkey, jboolean getDefault) { + jstring jkey, jboolean needGlobal) { auto setting = getSetting<bool>(env, jkey); if (setting == nullptr) { return false; } - setting->SetGlobal(true); - - if (static_cast<bool>(getDefault)) { - return setting->GetDefault(); - } - - return setting->GetValue(); + return setting->GetValue(static_cast<bool>(needGlobal)); } void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject obj, jstring jkey, @@ -69,23 +89,16 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setBoolean(JNIEnv* env, jobject if (setting == nullptr) { return; } - setting->SetGlobal(true); setting->SetValue(static_cast<bool>(value)); } jbyte Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getByte(JNIEnv* env, jobject obj, jstring jkey, - jboolean getDefault) { + jboolean needGlobal) { auto setting = getSetting<u8>(env, jkey); if (setting == nullptr) { return -1; } - setting->SetGlobal(true); - - if (static_cast<bool>(getDefault)) { - return setting->GetDefault(); - } - - return setting->GetValue(); + return setting->GetValue(static_cast<bool>(needGlobal)); } void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj, jstring jkey, @@ -94,23 +107,16 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setByte(JNIEnv* env, jobject obj if (setting == nullptr) { return; } - setting->SetGlobal(true); setting->SetValue(value); } jshort Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getShort(JNIEnv* env, jobject obj, jstring jkey, - jboolean getDefault) { + jboolean needGlobal) { auto setting = getSetting<u16>(env, jkey); if (setting == nullptr) { return -1; } - setting->SetGlobal(true); - - if (static_cast<bool>(getDefault)) { - return setting->GetDefault(); - } - - return setting->GetValue(); + return setting->GetValue(static_cast<bool>(needGlobal)); } void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject obj, jstring jkey, @@ -119,23 +125,16 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject ob if (setting == nullptr) { return; } - setting->SetGlobal(true); setting->SetValue(value); } jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey, - jboolean getDefault) { + jboolean needGlobal) { auto setting = getSetting<int>(env, jkey); if (setting == nullptr) { return -1; } - setting->SetGlobal(true); - - if (static_cast<bool>(getDefault)) { - return setting->GetDefault(); - } - - return setting->GetValue(); + return setting->GetValue(needGlobal); } void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, jstring jkey, @@ -144,23 +143,16 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setInt(JNIEnv* env, jobject obj, if (setting == nullptr) { return; } - setting->SetGlobal(true); setting->SetValue(value); } jfloat Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getFloat(JNIEnv* env, jobject obj, jstring jkey, - jboolean getDefault) { + jboolean needGlobal) { auto setting = getSetting<float>(env, jkey); if (setting == nullptr) { return -1; } - setting->SetGlobal(true); - - if (static_cast<bool>(getDefault)) { - return setting->GetDefault(); - } - - return setting->GetValue(); + return setting->GetValue(static_cast<bool>(needGlobal)); } void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject obj, jstring jkey, @@ -169,23 +161,16 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setFloat(JNIEnv* env, jobject ob if (setting == nullptr) { return; } - setting->SetGlobal(true); setting->SetValue(value); } jlong Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getLong(JNIEnv* env, jobject obj, jstring jkey, - jboolean getDefault) { - auto setting = getSetting<long>(env, jkey); + jboolean needGlobal) { + auto setting = getSetting<s64>(env, jkey); if (setting == nullptr) { return -1; } - setting->SetGlobal(true); - - if (static_cast<bool>(getDefault)) { - return setting->GetDefault(); - } - - return setting->GetValue(); + return setting->GetValue(static_cast<bool>(needGlobal)); } void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj, jstring jkey, @@ -194,23 +179,16 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setLong(JNIEnv* env, jobject obj if (setting == nullptr) { return; } - setting->SetGlobal(true); setting->SetValue(value); } jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getString(JNIEnv* env, jobject obj, jstring jkey, - jboolean getDefault) { + jboolean needGlobal) { auto setting = getSetting<std::string>(env, jkey); if (setting == nullptr) { return ToJString(env, ""); } - setting->SetGlobal(true); - - if (static_cast<bool>(getDefault)) { - return ToJString(env, setting->GetDefault()); - } - - return ToJString(env, setting->GetValue()); + return ToJString(env, setting->GetValue(static_cast<bool>(needGlobal))); } void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey, @@ -220,27 +198,18 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject o return; } - setting->SetGlobal(true); setting->SetValue(GetJString(env, value)); } jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj, jstring jkey) { - auto key = GetJString(env, jkey); - auto setting = Settings::values.linkage.by_key[key]; - if (setting != 0) { + auto setting = getSetting<std::string>(env, jkey); + if (setting != nullptr) { return setting->RuntimeModfiable(); } - LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key); return true; } -jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getConfigHeader(JNIEnv* env, jobject obj, - jint jcategory) { - auto category = static_cast<Settings::Category>(jcategory); - return ToJString(env, Settings::TranslateCategory(category)); -} - jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* env, jobject obj, jstring jkey) { auto setting = getSetting<std::string>(env, jkey); @@ -254,6 +223,50 @@ jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* e return ToJString(env, setting->PairedSetting()->GetLabel()); } +jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsSwitchable(JNIEnv* env, jobject obj, + jstring jkey) { + auto setting = getSetting<std::string>(env, jkey); + if (setting != nullptr) { + return setting->Switchable(); + } + return false; +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_usingGlobal(JNIEnv* env, jobject obj, + jstring jkey) { + auto setting = getSetting<std::string>(env, jkey); + if (setting != nullptr) { + return setting->UsingGlobal(); + } + return true; +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setGlobal(JNIEnv* env, jobject obj, jstring jkey, + jboolean global) { + auto setting = getSetting<std::string>(env, jkey); + if (setting != nullptr) { + setting->SetGlobal(static_cast<bool>(global)); + } +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getIsSaveable(JNIEnv* env, jobject obj, + jstring jkey) { + auto setting = getSetting<std::string>(env, jkey); + if (setting != nullptr) { + return setting->Save(); + } + return false; +} + +jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDefaultToString(JNIEnv* env, jobject obj, + jstring jkey) { + auto setting = getSetting<std::string>(env, jkey); + if (setting != nullptr) { + return ToJString(env, setting->DefaultToString()); + } + return ToJString(env, ""); +} + jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getGameDirs(JNIEnv* env, jobject obj) { jclass gameDirClass = IDCache::GetGameDirClass(); jmethodID gameDirConstructor = IDCache::GetGameDirConstructor(); @@ -305,4 +318,30 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject AndroidSettings::GameDir{uriString, static_cast<bool>(jdeepScanBoolean)}); } +jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj, + jstring jprogramId) { + auto program_id = EmulationSession::GetProgramId(env, jprogramId); + auto& disabledAddons = Settings::values.disabled_addons[program_id]; + jobjectArray jdisabledAddonsArray = + env->NewObjectArray(disabledAddons.size(), IDCache::GetStringClass(), ToJString(env, "")); + for (size_t i = 0; i < disabledAddons.size(); ++i) { + env->SetObjectArrayElement(jdisabledAddonsArray, i, ToJString(env, disabledAddons[i])); + } + return jdisabledAddonsArray; +} + +void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj, + jstring jprogramId, + jobjectArray jdisabledAddons) { + auto program_id = EmulationSession::GetProgramId(env, jprogramId); + Settings::values.disabled_addons[program_id].clear(); + std::vector<std::string> disabled_addons; + const int size = env->GetArrayLength(jdisabledAddons); + for (int i = 0; i < size; ++i) { + auto jaddon = static_cast<jstring>(env->GetObjectArrayElement(jdisabledAddons, i)); + disabled_addons.push_back(GetJString(env, jaddon)); + } + Settings::values.disabled_addons[program_id] = disabled_addons; +} + } // extern "C" diff --git a/src/android/app/src/main/res/drawable/ic_save.xml b/src/android/app/src/main/res/drawable/ic_save.xml index a9af3d9cf..5acc2bbab 100644 --- a/src/android/app/src/main/res/drawable/ic_save.xml +++ b/src/android/app/src/main/res/drawable/ic_save.xml @@ -1,10 +1,9 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" - android:viewportWidth="960" - android:viewportHeight="960" - android:tint="?attr/colorControlNormal"> + android:viewportWidth="24" + android:viewportHeight="24"> <path - android:fillColor="@android:color/white" - android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L647,120Q663,120 677.5,126Q692,132 703,143L817,257Q828,268 834,282.5Q840,297 840,313L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM760,314L646,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,314ZM480,720Q530,720 565,685Q600,650 600,600Q600,550 565,515Q530,480 480,480Q430,480 395,515Q360,550 360,600Q360,650 395,685Q430,720 480,720ZM280,400L560,400Q577,400 588.5,388.5Q600,377 600,360L600,280Q600,263 588.5,251.5Q577,240 560,240L280,240Q263,240 251.5,251.5Q240,263 240,280L240,360Q240,377 251.5,388.5Q263,400 280,400ZM200,314L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200L200,200L200,314Z"/> + android:fillColor="?attr/colorControlNormal" + android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" /> </vector> diff --git a/src/android/app/src/main/res/layout-w1000dp/card_installable_icon.xml b/src/android/app/src/main/res/layout-w1000dp/card_installable_icon.xml new file mode 100644 index 000000000..59ee1aad3 --- /dev/null +++ b/src/android/app/src/main/res/layout-w1000dp/card_installable_icon.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + style="?attr/materialCardViewOutlinedStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:layout_marginVertical="12dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="horizontal" + android:gravity="center_vertical" + android:paddingHorizontal="24dp" + android:paddingVertical="16dp"> + + <ImageView + android:id="@+id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="20dp" + android:layout_gravity="center_vertical" + app:tint="?attr/colorOnSurface" + tools:src="@drawable/ic_settings" /> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:layout_weight="1" + android:orientation="vertical"> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/title" + style="@style/TextAppearance.Material3.TitleMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/user_data" + android:textAlignment="viewStart" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/description" + style="@style/TextAppearance.Material3.BodyMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + android:text="@string/user_data_description" + android:textAlignment="viewStart" /> + + </LinearLayout> + + <Button + android:id="@+id/button_export" + style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:contentDescription="@string/export" + android:tooltipText="@string/export" + android:visibility="gone" + app:icon="@drawable/ic_export" + tools:visibility="visible" /> + + <Button + android:id="@+id/button_install" + style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="12dp" + android:contentDescription="@string/string_import" + android:tooltipText="@string/string_import" + android:visibility="gone" + app:icon="@drawable/ic_import" + tools:visibility="visible" /> + + </LinearLayout> + +</com.google.android.material.card.MaterialCardView> diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml b/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml new file mode 100644 index 000000000..0b9633855 --- /dev/null +++ b/src/android/app/src/main/res/layout-w600dp/fragment_game_properties.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/colorSurface"> + + <androidx.core.widget.NestedScrollView + android:id="@+id/list_all" + android:layout_width="0dp" + android:layout_height="match_parent" + android:clipToPadding="false" + android:fadeScrollbars="false" + android:scrollbars="vertical" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/icon_layout" + app:layout_constraintTop_toTopOf="parent"> + + <LinearLayout + android:id="@+id/layout_all" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="horizontal"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list_properties" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/card_simple_outlined" /> + + </LinearLayout> + + </androidx.core.widget.NestedScrollView> + + <LinearLayout + android:id="@+id/icon_layout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <Button + android:id="@+id/button_back" + style="?attr/materialIconButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:layout_margin="8dp" + app:icon="@drawable/ic_back" + app:iconSize="24dp" + app:iconTint="?attr/colorOnSurface" /> + + <com.google.android.material.card.MaterialCardView + style="?attr/materialCardViewElevatedStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:layout_marginTop="8dp" + app:cardCornerRadius="4dp" + app:cardElevation="4dp"> + + <ImageView + android:id="@+id/image_game_screen" + android:layout_width="175dp" + android:layout_height="175dp" + tools:src="@drawable/default_icon" /> + + </com.google.android.material.card.MaterialCardView> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/title" + style="@style/TextAppearance.Material3.TitleMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:layout_marginTop="12dp" + android:ellipsize="none" + android:marqueeRepeatLimit="marquee_forever" + android:requiresFadingEdge="horizontal" + android:singleLine="true" + android:textAlignment="center" + tools:text="deko_basic" /> + + </LinearLayout> + + <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton + android:id="@+id/button_start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/start" + app:icon="@drawable/ic_play" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/src/android/app/src/main/res/layout/card_installable.xml b/src/android/app/src/main/res/layout/card_installable.xml index f5b0e3741..ce2402d7a 100644 --- a/src/android/app/src/main/res/layout/card_installable.xml +++ b/src/android/app/src/main/res/layout/card_installable.xml @@ -11,7 +11,8 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="16dp" + android:paddingVertical="16dp" + android:paddingHorizontal="24dp" android:orientation="horizontal" android:layout_gravity="center"> diff --git a/src/android/app/src/main/res/layout/card_installable_icon.xml b/src/android/app/src/main/res/layout/card_installable_icon.xml new file mode 100644 index 000000000..4ae5423b1 --- /dev/null +++ b/src/android/app/src/main/res/layout/card_installable_icon.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + style="?attr/materialCardViewOutlinedStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="16dp" + android:layout_marginVertical="12dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="horizontal" + android:gravity="center_vertical" + android:paddingHorizontal="24dp" + android:paddingVertical="16dp"> + + <ImageView + android:id="@+id/icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="20dp" + android:layout_gravity="center_vertical" + app:tint="?attr/colorOnSurface" + tools:src="@drawable/ic_settings" /> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:layout_weight="1" + android:orientation="vertical"> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/title" + style="@style/TextAppearance.Material3.TitleMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/user_data" + android:textAlignment="viewStart" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/description" + style="@style/TextAppearance.Material3.BodyMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + android:text="@string/user_data_description" + android:textAlignment="viewStart" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <Button + android:id="@+id/button_install" + style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:contentDescription="@string/string_import" + android:tooltipText="@string/string_import" + android:visibility="gone" + app:icon="@drawable/ic_import" + tools:visibility="visible" /> + + <Button + android:id="@+id/button_export" + style="@style/Widget.Material3.Button.IconButton.Filled.Tonal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginTop="8dp" + android:contentDescription="@string/export" + android:tooltipText="@string/export" + android:visibility="gone" + app:icon="@drawable/ic_export" + tools:visibility="visible" /> + + </LinearLayout> + + </LinearLayout> + +</com.google.android.material.card.MaterialCardView> diff --git a/src/android/app/src/main/res/layout/card_applet_option.xml b/src/android/app/src/main/res/layout/card_simple_outlined.xml index 19fbec9f1..b73930e7e 100644 --- a/src/android/app/src/main/res/layout/card_applet_option.xml +++ b/src/android/app/src/main/res/layout/card_simple_outlined.xml @@ -16,7 +16,8 @@ android:layout_height="wrap_content" android:orientation="horizontal" android:layout_gravity="center" - android:padding="24dp"> + android:paddingVertical="16dp" + android:paddingHorizontal="24dp"> <ImageView android:id="@+id/icon" @@ -50,6 +51,23 @@ android:textAlignment="viewStart" tools:text="@string/applets_description" /> + <com.google.android.material.textview.MaterialTextView + style="@style/TextAppearance.Material3.LabelMedium" + android:id="@+id/details" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="viewStart" + android:textSize="14sp" + android:textStyle="bold" + android:singleLine="true" + android:marqueeRepeatLimit="marquee_forever" + android:ellipsize="none" + android:requiresFadingEdge="horizontal" + android:layout_marginTop="6dp" + android:visibility="gone" + tools:visibility="visible" + tools:text="/tree/primary:Games" /> + </LinearLayout> </LinearLayout> diff --git a/src/android/app/src/main/res/layout/fragment_addons.xml b/src/android/app/src/main/res/layout/fragment_addons.xml new file mode 100644 index 000000000..a25e82766 --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_addons.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/coordinator_about" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/colorSurface"> + + <com.google.android.material.appbar.AppBarLayout + android:id="@+id/appbar_addons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fitsSystemWindows="true" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/toolbar_addons" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + app:navigationIcon="@drawable/ic_back" /> + + </com.google.android.material.appbar.AppBarLayout> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list_addons" + android:layout_width="match_parent" + android:layout_height="0dp" + android:clipToPadding="false" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/appbar_addons" /> + + <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton + android:id="@+id/button_install" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|end" + android:text="@string/install" + app:icon="@drawable/ic_add" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/src/android/app/src/main/res/layout/fragment_game_info.xml b/src/android/app/src/main/res/layout/fragment_game_info.xml new file mode 100644 index 000000000..80ede8a8c --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_game_info.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/coordinator_about" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/colorSurface"> + + <com.google.android.material.appbar.AppBarLayout + android:id="@+id/appbar_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fitsSystemWindows="true"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/toolbar_info" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + app:navigationIcon="@drawable/ic_back" /> + + </com.google.android.material.appbar.AppBarLayout> + + <androidx.core.widget.NestedScrollView + android:id="@+id/scroll_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> + + <LinearLayout + android:id="@+id/content_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingHorizontal="16dp"> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/path" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dp"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/path_field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:editable="false" + android:importantForAutofill="no" + android:inputType="none" + android:minHeight="48dp" + android:textAlignment="viewStart" + tools:text="1.0.0" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/program_id" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dp"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/program_id_field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:editable="false" + android:importantForAutofill="no" + android:inputType="none" + android:minHeight="48dp" + android:textAlignment="viewStart" + tools:text="1.0.0" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/developer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dp"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/developer_field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:editable="false" + android:importantForAutofill="no" + android:inputType="none" + android:minHeight="48dp" + android:textAlignment="viewStart" + tools:text="1.0.0" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/version" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dp"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/version_field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:editable="false" + android:importantForAutofill="no" + android:inputType="none" + android:minHeight="48dp" + android:textAlignment="viewStart" + tools:text="1.0.0" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.button.MaterialButton + android:id="@+id/button_copy" + style="@style/Widget.Material3.Button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="@string/copy_details" /> + + </LinearLayout> + + </androidx.core.widget.NestedScrollView> + +</androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/src/android/app/src/main/res/layout/fragment_game_properties.xml b/src/android/app/src/main/res/layout/fragment_game_properties.xml new file mode 100644 index 000000000..72ecbde30 --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_game_properties.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/colorSurface"> + + <androidx.core.widget.NestedScrollView + android:id="@+id/list_all" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="vertical" + android:fadeScrollbars="false" + android:clipToPadding="false"> + + <LinearLayout + android:id="@+id/layout_all" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal"> + + <Button + android:id="@+id/button_back" + style="?attr/materialIconButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:layout_gravity="start" + app:icon="@drawable/ic_back" + app:iconSize="24dp" + app:iconTint="?attr/colorOnSurface" /> + + <com.google.android.material.card.MaterialCardView + style="?attr/materialCardViewElevatedStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + app:cardCornerRadius="4dp" + app:cardElevation="4dp"> + + <ImageView + android:id="@+id/image_game_screen" + android:layout_width="175dp" + android:layout_height="175dp" + tools:src="@drawable/default_icon"/> + + </com.google.android.material.card.MaterialCardView> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/title" + style="@style/TextAppearance.Material3.TitleMedium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="12dp" + android:layout_marginBottom="12dp" + android:layout_marginHorizontal="16dp" + android:ellipsize="none" + android:marqueeRepeatLimit="marquee_forever" + android:requiresFadingEdge="horizontal" + android:singleLine="true" + android:textAlignment="center" + tools:text="deko_basic" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/list_properties" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/card_simple_outlined" /> + + </LinearLayout> + + </androidx.core.widget.NestedScrollView> + + <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton + android:id="@+id/button_start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/start" + app:icon="@drawable/ic_play" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/src/android/app/src/main/res/layout/list_item_addon.xml b/src/android/app/src/main/res/layout/list_item_addon.xml new file mode 100644 index 000000000..74ca04ef1 --- /dev/null +++ b/src/android/app/src/main/res/layout/list_item_addon.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/addon_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:focusable="true" + android:paddingHorizontal="20dp" + android:paddingVertical="16dp"> + + <LinearLayout + android:id="@+id/text_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:orientation="vertical" + app:layout_constraintBottom_toBottomOf="@+id/addon_switch" + app:layout_constraintEnd_toStartOf="@+id/addon_switch" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/addon_switch"> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/title" + 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="1440p Resolution" /> + + <com.google.android.material.textview.MaterialTextView + android:id="@+id/version" + 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="1.0.0" /> + + </LinearLayout> + + <com.google.android.material.materialswitch.MaterialSwitch + android:id="@+id/addon_switch" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:focusable="true" + android:gravity="center" + android:nextFocusLeft="@id/addon_container" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/text_container" + app:layout_constraintTop_toTopOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> 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 cf70b4bc4..37a03a8d1 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 @@ -107,7 +111,13 @@ <fragment android:id="@+id/driverManagerFragment" android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment" - android:label="DriverManagerFragment" /> + android:label="DriverManagerFragment" > + <argument + android:name="game" + app:argType="org.yuzu.yuzu_emu.model.Game" + app:nullable="true" + android:defaultValue="@null" /> + </fragment> <fragment android:id="@+id/appletLauncherFragment" android:name="org.yuzu.yuzu_emu.fragments.AppletLauncherFragment" @@ -124,5 +134,41 @@ android:id="@+id/gameFoldersFragment" android:name="org.yuzu.yuzu_emu.fragments.GameFoldersFragment" android:label="GameFoldersFragment" /> + <fragment + android:id="@+id/perGamePropertiesFragment" + android:name="org.yuzu.yuzu_emu.fragments.GamePropertiesFragment" + android:label="PerGamePropertiesFragment" > + <argument + android:name="game" + app:argType="org.yuzu.yuzu_emu.model.Game" /> + <action + android:id="@+id/action_perGamePropertiesFragment_to_gameInfoFragment" + app:destination="@id/gameInfoFragment" /> + <action + android:id="@+id/action_perGamePropertiesFragment_to_addonsFragment" + app:destination="@id/addonsFragment" /> + <action + android:id="@+id/action_perGamePropertiesFragment_to_driverManagerFragment" + app:destination="@id/driverManagerFragment" /> + </fragment> + <action + android:id="@+id/action_global_perGamePropertiesFragment" + app:destination="@id/perGamePropertiesFragment" /> + <fragment + android:id="@+id/gameInfoFragment" + android:name="org.yuzu.yuzu_emu.fragments.GameInfoFragment" + android:label="GameInfoFragment" > + <argument + android:name="game" + app:argType="org.yuzu.yuzu_emu.model.Game" /> + </fragment> + <fragment + android:id="@+id/addonsFragment" + android:name="org.yuzu.yuzu_emu.fragments.AddonsFragment" + android:label="AddonsFragment" > + <argument + android:name="game" + app:argType="org.yuzu.yuzu_emu.model.Game" /> + </fragment> </navigation> diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml index 380d14213..992b5ae44 100644 --- a/src/android/app/src/main/res/values/dimens.xml +++ b/src/android/app/src/main/res/values/dimens.xml @@ -13,7 +13,7 @@ <dimen name="menu_width">256dp</dimen> <dimen name="card_width">165dp</dimen> <dimen name="icon_inset">24dp</dimen> - <dimen name="spacing_bottom_list_fab">76dp</dimen> + <dimen name="spacing_bottom_list_fab">96dp</dimen> <dimen name="spacing_fab">24dp</dimen> <dimen name="dialog_margin">20dp</dimen> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index a6ccef8a1..c86c43df2 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -91,7 +91,10 @@ <string name="notification_no_directory_link_description">Please locate the user folder with the file manager\'s side panel manually.</string> <string name="manage_save_data">Manage save data</string> <string name="manage_save_data_description">Save data found. Please select an option below.</string> + <string name="import_save_warning">Import save data</string> + <string name="import_save_warning_description">This will overwrite all existing save data with the provided file. Are you sure that you want to continue?</string> <string name="import_export_saves_description">Import or export save files</string> + <string name="save_files_importing">Importing save files…</string> <string name="save_files_exporting">Exporting save files…</string> <string name="save_file_imported_success">Imported successfully</string> <string name="save_file_invalid_zip_structure">Invalid save directory structure</string> @@ -266,6 +269,11 @@ <string name="delete">Delete</string> <string name="edit">Edit</string> <string name="export_success">Exported successfully</string> + <string name="start">Start</string> + <string name="clear">Clear</string> + <string name="global">Global</string> + <string name="custom">Custom</string> + <string name="notice">Notice</string> <!-- GPU driver installation --> <string name="select_gpu_driver">Select GPU driver</string> @@ -291,6 +299,44 @@ <string name="preferences_debug">Debug</string> <string name="preferences_debug_description">CPU/GPU debugging, graphics API, fastmem</string> + <!-- Game properties --> + <string name="info">Info</string> + <string name="info_description">Program ID, developer, version</string> + <string name="per_game_settings">Per-game settings</string> + <string name="per_game_settings_description">Edit settings specific to this game</string> + <string name="launch_options">Launch config</string> + <string name="path">Path</string> + <string name="program_id">Program ID</string> + <string name="developer">Developer</string> + <string name="version">Version</string> + <string name="copy_details">Copy details</string> + <string name="add_ons">Add-ons</string> + <string name="add_ons_description">Toggle mods, updates and DLC</string> + <string name="clear_shader_cache">Clear shader cache</string> + <string name="clear_shader_cache_description">Removes all shaders built while playing this game</string> + <string name="clear_shader_cache_warning_description">You will experience more stuttering as the shader cache regenerates</string> + <string name="cleared_shaders_successfully">Cleared shaders successfully</string> + <string name="addons_game">Addons: %1$s</string> + <string name="save_data">Save data</string> + <string name="save_data_description">Manage save data specific to this game</string> + <string name="delete_save_data">Delete save data</string> + <string name="delete_save_data_description">Removes all save data specific to this game</string> + <string name="delete_save_data_warning_description">This irrecoverably removes all of this game\'s save data. Are you sure you want to continue?</string> + <string name="save_data_deleted_successfully">Save data deleted successfully</string> + <string name="select_content_type">Content type</string> + <string name="updates_and_dlc">Updates and DLC</string> + <string name="mods_and_cheats">Mods and cheats</string> + <string name="addon_notice">Important addon notice</string> + <!-- "cheats/" "romfs/" and "exefs/ should not be translated --> + <string name="addon_notice_description">In order to install mods and cheats, you must select a folder that contains a cheats/, romfs/, or exefs/ directory. We can\'t verify if these will be compatible with your game so be careful!</string> + <string name="invalid_directory">Invalid directory</string> + <!-- "cheats/" "romfs/" and "exefs/ should not be translated --> + <string name="invalid_directory_description">Please make sure that the directory you selected contains a cheats/, romfs/, or exefs/ folder and try again.</string> + <string name="addon_installed_successfully">Addon installed successfully</string> + <string name="verifying_content">Verifying content…</string> + <string name="content_install_notice">Content install notice</string> + <string name="content_install_notice_description">The content that you selected does not match this game.\nInstall anyway?</string> + <!-- ROM loading errors --> <string name="loader_error_encrypted">Your ROM is encrypted</string> <string name="loader_error_encrypted_roms_description"><![CDATA[Please follow the guides to redump your <a href="https://yuzu-emu.org/help/quickstart/#dumping-physical-titles-game-cards">game cartidges</a> or <a href="https://yuzu-emu.org/help/quickstart/#dumping-digital-titles-eshop">installed titles</a>.]]></string> @@ -369,6 +415,7 @@ <!-- Memory Sizes --> <string name="memory_byte">Byte</string> + <string name="memory_byte_shorthand">B</string> <string name="memory_kilobyte">KB</string> <string name="memory_megabyte">MB</string> <string name="memory_gigabyte">GB</string> diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index 51a23fe15..d97ca2a40 100644 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp @@ -253,8 +253,9 @@ CubebSink::~CubebSink() { #endif } -SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels, +SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels_, const std::string& name, StreamType type) { + system_channels = system_channels_; SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( ctx, device_channels, system_channels, output_device, input_device, name, type, system)); diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index 96e0efce2..7dd155ff0 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp @@ -168,8 +168,9 @@ SDLSink::SDLSink(std::string_view target_device_name) { SDLSink::~SDLSink() = default; -SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels, +SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels_, const std::string&, StreamType type) { + system_channels = system_channels_; SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( device_channels, system_channels, output_device, input_device, type, system)); return stream.get(); diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h index f28c6d126..e22e8c3e5 100644 --- a/src/audio_core/sink/sink.h +++ b/src/audio_core/sink/sink.h @@ -85,9 +85,21 @@ public: */ virtual void SetSystemVolume(f32 volume) = 0; + /** + * Get the number of channels the game has set, can be different to the host hardware's support. + * Either 2 or 6. + * + * @return Number of device channels. + */ + u32 GetSystemChannels() const { + return system_channels; + } + protected: /// Number of device channels supported by the hardware u32 device_channels{2}; + /// Number of channels the game is sending + u32 system_channels{2}; }; using SinkPtr = std::unique_ptr<Sink>; diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 2a09db599..c047b0668 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp @@ -40,29 +40,36 @@ void SinkStream::AppendBuffer(SinkBuffer& buffer, std::span<s16> samples) { if (system_channels == 6 && device_channels == 2) { // We're given 6 channels, but our device only outputs 2, so downmix. - static constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; + // Front = 1.0 + // Center = 0.596 + // LFE = 0.354 + // Back = 0.707 + static constexpr std::array<f32, 4> down_mix_coeff{1.0, 0.596f, 0.354f, 0.707f}; for (u32 read_index = 0, write_index = 0; read_index < samples.size(); read_index += system_channels, write_index += device_channels) { + const auto fl = + static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]); + const auto fr = + static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]); + const auto c = + static_cast<f32>(samples[read_index + static_cast<u32>(Channels::Center)]); + const auto lfe = + static_cast<f32>(samples[read_index + static_cast<u32>(Channels::LFE)]); + const auto bl = + static_cast<f32>(samples[read_index + static_cast<u32>(Channels::BackLeft)]); + const auto br = + static_cast<f32>(samples[read_index + static_cast<u32>(Channels::BackRight)]); + const auto left_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * - down_mix_coeff[0] + - samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] + - samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] + - samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) * - volume) - .to_int()}; + static_cast<s32>((fl * down_mix_coeff[0] + c * down_mix_coeff[1] + + lfe * down_mix_coeff[2] + bl * down_mix_coeff[3]) * + volume)}; const auto right_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast<u32>(Channels::FrontRight)]) * - down_mix_coeff[0] + - samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] + - samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] + - samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) * - volume) - .to_int()}; + static_cast<s32>((fr * down_mix_coeff[0] + c * down_mix_coeff[1] + + lfe * down_mix_coeff[2] + br * down_mix_coeff[3]) * + volume)}; samples[write_index + static_cast<u32>(Channels::FrontLeft)] = static_cast<s16>(std::clamp(left_sample, min, max)); diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 47d028d48..ba3081efb 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -123,6 +123,12 @@ namespace Common { return u32(a) | u32(b) << 8 | u32(c) << 16 | u32(d) << 24; } +[[nodiscard]] constexpr u64 MakeMagic(char a, char b, char c, char d, char e, char f, char g, + char h) { + return u64(a) << 0 | u64(b) << 8 | u64(c) << 16 | u64(d) << 24 | u64(e) << 32 | u64(f) << 40 | + u64(g) << 48 | u64(h) << 56; +} + // std::size() does not support zero-size C arrays. We're fixing that. template <class C> constexpr auto Size(const C& c) -> decltype(c.size()) { diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index c3a81f9a9..4f69db6f5 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -354,18 +354,36 @@ std::string_view RemoveTrailingSlash(std::string_view path) { return path; } -std::vector<std::string> SplitPathComponents(std::string_view filename) { - std::string copy(filename); - std::replace(copy.begin(), copy.end(), '\\', '/'); - std::vector<std::string> out; - - std::stringstream stream(copy); - std::string item; - while (std::getline(stream, item, '/')) { - out.push_back(std::move(item)); +template <typename F> +static void ForEachPathComponent(std::string_view filename, F&& cb) { + const char* component_begin = filename.data(); + const char* const end = component_begin + filename.size(); + for (const char* it = component_begin; it != end; ++it) { + const char c = *it; + if (c == '\\' || c == '/') { + if (component_begin != it) { + cb(std::string_view{component_begin, it}); + } + component_begin = it + 1; + } + } + if (component_begin != end) { + cb(std::string_view{component_begin, end}); } +} + +std::vector<std::string_view> SplitPathComponents(std::string_view filename) { + std::vector<std::string_view> components; + ForEachPathComponent(filename, [&](auto component) { components.emplace_back(component); }); - return out; + return components; +} + +std::vector<std::string> SplitPathComponentsCopy(std::string_view filename) { + std::vector<std::string> components; + ForEachPathComponent(filename, [&](auto component) { components.emplace_back(component); }); + + return components; } std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { @@ -400,9 +418,9 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se return std::string(RemoveTrailingSlash(path)); } -std::string_view GetParentPath(std::string_view path) { +std::string GetParentPath(std::string_view path) { if (path.empty()) { - return path; + return std::string(path); } #ifdef ANDROID @@ -421,7 +439,7 @@ std::string_view GetParentPath(std::string_view path) { name_index = std::max(name_bck_index, name_fwd_index); } - return path.substr(0, name_index); + return std::string(path.substr(0, name_index)); } std::string_view GetPathWithoutTop(std::string_view path) { diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 2874ea738..59301e7ed 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -289,7 +289,11 @@ enum class DirectorySeparator { // Splits the path on '/' or '\' and put the components into a vector // i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } -[[nodiscard]] std::vector<std::string> SplitPathComponents(std::string_view filename); +[[nodiscard]] std::vector<std::string_view> SplitPathComponents(std::string_view filename); + +// Splits the path on '/' or '\' and put the components into a vector +// i.e. "C:\Users\Yuzu\Documents\save.bin" becomes {"C:", "Users", "Yuzu", "Documents", "save.bin" } +[[nodiscard]] std::vector<std::string> SplitPathComponentsCopy(std::string_view filename); // Removes trailing slash, makes all '\\' into '/', and removes duplicate '/'. Makes '/' into '\\' // depending if directory_separator is BackwardSlash or PlatformDefault and running on windows @@ -298,7 +302,7 @@ enum class DirectorySeparator { DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash); // Gets all of the text up to the last '/' or '\' in the path. -[[nodiscard]] std::string_view GetParentPath(std::string_view path); +[[nodiscard]] std::string GetParentPath(std::string_view path); // Gets all of the text after the first '/' or '\' in the path. [[nodiscard]] std::string_view GetPathWithoutTop(std::string_view path); diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 4bfc64f2d..e540375b8 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -11,10 +11,6 @@ #elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv -#ifdef ANDROID -#include <android/sharedmem.h> -#endif - #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif @@ -193,6 +189,11 @@ public: } } + bool ClearBackingRegion(size_t physical_offset, size_t length) { + // TODO: This does not seem to be possible on Windows. + return false; + } + void EnableDirectMappedAddress() { // TODO UNREACHABLE(); @@ -442,9 +443,7 @@ public: } // Backing memory initialization -#ifdef ANDROID - fd = ASharedMemory_create("HostMemory", backing_size); -#elif defined(__FreeBSD__) && __FreeBSD__ < 13 +#if defined(__FreeBSD__) && __FreeBSD__ < 13 // XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30 fd = shm_open(SHM_ANON, O_RDWR, 0600); #else @@ -455,7 +454,6 @@ public: throw std::bad_alloc{}; } -#ifndef ANDROID // Defined to extend the file with zeros int ret = ftruncate(fd, backing_size); if (ret != 0) { @@ -463,7 +461,6 @@ public: strerror(errno)); throw std::bad_alloc{}; } -#endif backing_base = static_cast<u8*>( mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); @@ -552,6 +549,19 @@ public: ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno)); } + bool ClearBackingRegion(size_t physical_offset, size_t length) { +#ifdef __linux__ + // Set MADV_REMOVE on backing map to destroy it instantly. + // This also deletes the area from the backing file. + int ret = madvise(backing_base + physical_offset, length, MADV_REMOVE); + ASSERT_MSG(ret == 0, "madvise failed: {}", strerror(errno)); + + return true; +#else + return false; +#endif + } + void EnableDirectMappedAddress() { virtual_base = nullptr; } @@ -623,6 +633,10 @@ public: void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {} + bool ClearBackingRegion(size_t physical_offset, size_t length) { + return false; + } + void EnableDirectMappedAddress() {} u8* backing_base{nullptr}; @@ -698,6 +712,12 @@ void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool w impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute); } +void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value) { + if (!impl || fill_value != 0 || !impl->ClearBackingRegion(physical_offset, length)) { + std::memset(backing_base + physical_offset, fill_value, length); + } +} + void HostMemory::EnableDirectMappedAddress() { if (impl) { impl->EnableDirectMappedAddress(); diff --git a/src/common/host_memory.h b/src/common/host_memory.h index cebfacab2..747c5850c 100644 --- a/src/common/host_memory.h +++ b/src/common/host_memory.h @@ -48,6 +48,8 @@ public: void EnableDirectMappedAddress(); + void ClearBackingRegion(size_t physical_offset, size_t length, u32 fill_value); + [[nodiscard]] u8* BackingBasePointer() noexcept { return backing_base; } diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 88f509ba7..ea52bbfa6 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -211,6 +211,8 @@ const char* TranslateCategory(Category category) { case Category::Debugging: case Category::DebuggingGraphics: return "Debugging"; + case Category::GpuDriver: + return "GpuDriver"; case Category::Miscellaneous: return "Miscellaneous"; case Category::Network: diff --git a/src/common/settings.h b/src/common/settings.h index 7dc18fffe..07dba53ab 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -197,7 +197,7 @@ struct Values { SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto, CpuAccuracy::Auto, CpuAccuracy::Paranoid, "cpu_accuracy", Category::Cpu}; - Setting<bool> cpu_debug_mode{linkage, false, "cpu_debug_mode", Category::CpuDebug}; + SwitchableSetting<bool> cpu_debug_mode{linkage, false, "cpu_debug_mode", Category::CpuDebug}; Setting<bool> cpuopt_page_tables{linkage, true, "cpuopt_page_tables", Category::CpuDebug}; Setting<bool> cpuopt_block_linking{linkage, true, "cpuopt_block_linking", Category::CpuDebug}; @@ -211,9 +211,9 @@ struct Values { Setting<bool> cpuopt_misc_ir{linkage, true, "cpuopt_misc_ir", Category::CpuDebug}; Setting<bool> cpuopt_reduce_misalign_checks{linkage, true, "cpuopt_reduce_misalign_checks", Category::CpuDebug}; - Setting<bool> cpuopt_fastmem{linkage, true, "cpuopt_fastmem", Category::CpuDebug}; - Setting<bool> cpuopt_fastmem_exclusives{linkage, true, "cpuopt_fastmem_exclusives", - Category::CpuDebug}; + SwitchableSetting<bool> cpuopt_fastmem{linkage, true, "cpuopt_fastmem", Category::CpuDebug}; + SwitchableSetting<bool> cpuopt_fastmem_exclusives{linkage, true, "cpuopt_fastmem_exclusives", + Category::CpuDebug}; Setting<bool> cpuopt_recompile_exclusives{linkage, true, "cpuopt_recompile_exclusives", Category::CpuDebug}; Setting<bool> cpuopt_ignore_memory_aborts{linkage, true, "cpuopt_ignore_memory_aborts", @@ -256,7 +256,7 @@ struct Values { AstcDecodeMode::CpuAsynchronous, "accelerate_astc", Category::Renderer}; - Setting<VSyncMode, true> vsync_mode{ + SwitchableSetting<VSyncMode, true> vsync_mode{ linkage, VSyncMode::Fifo, VSyncMode::Immediate, VSyncMode::FifoRelaxed, "use_vsync", Category::Renderer, Specialization::RuntimeList, true, true}; diff --git a/src/common/settings_common.h b/src/common/settings_common.h index 344c04439..c82e17495 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h @@ -26,6 +26,7 @@ enum class Category : u32 { DataStorage, Debugging, DebuggingGraphics, + GpuDriver, Miscellaneous, Network, WebService, diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h index 3175ab07d..0b18ca5ec 100644 --- a/src/common/settings_setting.h +++ b/src/common/settings_setting.h @@ -81,6 +81,9 @@ public: [[nodiscard]] virtual const Type& GetValue() const { return value; } + [[nodiscard]] virtual const Type& GetValue(bool need_global) const { + return value; + } /** * Sets the setting to the given value. @@ -353,7 +356,7 @@ public: } return custom; } - [[nodiscard]] const Type& GetValue(bool need_global) const { + [[nodiscard]] const Type& GetValue(bool need_global) const override final { if (use_global || need_global) { return this->value; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7b9ed856f..96ab39cb8 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -549,6 +549,11 @@ add_library(core STATIC hle/service/hid/xcd.cpp hle/service/hid/xcd.h hle/service/hid/errors.h + hle/service/hid/controllers/types/debug_pad_types.h + hle/service/hid/controllers/types/keyboard_types.h + hle/service/hid/controllers/types/mouse_types.h + hle/service/hid/controllers/types/npad_types.h + hle/service/hid/controllers/types/touch_types.h hle/service/hid/controllers/applet_resource.cpp hle/service/hid/controllers/applet_resource.h hle/service/hid/controllers/console_six_axis.cpp @@ -569,14 +574,15 @@ add_library(core STATIC hle/service/hid/controllers/palma.h hle/service/hid/controllers/seven_six_axis.cpp hle/service/hid/controllers/seven_six_axis.h + hle/service/hid/controllers/shared_memory_format.h + hle/service/hid/controllers/shared_memory_holder.cpp + hle/service/hid/controllers/shared_memory_holder.h hle/service/hid/controllers/six_axis.cpp hle/service/hid/controllers/six_axis.h hle/service/hid/controllers/stubbed.cpp hle/service/hid/controllers/stubbed.h hle/service/hid/controllers/touchscreen.cpp hle/service/hid/controllers/touchscreen.h - hle/service/hid/controllers/xpad.cpp - hle/service/hid/controllers/xpad.h hle/service/hid/hidbus/hidbus_base.cpp hle/service/hid/hidbus/hidbus_base.h hle/service/hid/hidbus/ringcon.cpp @@ -784,6 +790,12 @@ add_library(core STATIC hle/service/service.h hle/service/set/set.cpp hle/service/set/set.h + hle/service/set/appln_settings.cpp + hle/service/set/appln_settings.h + hle/service/set/device_settings.cpp + hle/service/set/device_settings.h + hle/service/set/private_settings.cpp + hle/service/set/private_settings.h hle/service/set/set_cal.cpp hle/service/set/set_cal.h hle/service/set/set_fd.cpp @@ -792,6 +804,8 @@ add_library(core STATIC hle/service/set/set_sys.h hle/service/set/settings.cpp hle/service/set/settings.h + hle/service/set/system_settings.cpp + hle/service/set/system_settings.h hle/service/sm/sm.cpp hle/service/sm/sm.h hle/service/sm/sm_controller.cpp @@ -947,15 +961,19 @@ if (HAS_NCE) set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp") target_sources(core PRIVATE + arm/nce/arm_nce_asm_definitions.h arm/nce/arm_nce.cpp arm/nce/arm_nce.h arm/nce/arm_nce.s arm/nce/guest_context.h + arm/nce/instructions.h + arm/nce/interpreter_visitor.cpp + arm/nce/interpreter_visitor.h arm/nce/patcher.cpp arm/nce/patcher.h - arm/nce/instructions.h + arm/nce/visitor_base.h ) - target_link_libraries(core PRIVATE merry::oaknut) + target_link_libraries(core PRIVATE merry::mcl merry::oaknut) endif() if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index b42a32a0b..1311e66a9 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -6,7 +6,7 @@ #include "common/signal_chain.h" #include "core/arm/nce/arm_nce.h" -#include "core/arm/nce/guest_context.h" +#include "core/arm/nce/interpreter_visitor.h" #include "core/arm/nce/patcher.h" #include "core/core.h" #include "core/memory.h" @@ -21,7 +21,8 @@ namespace Core { namespace { -struct sigaction g_orig_action; +struct sigaction g_orig_bus_action; +struct sigaction g_orig_segv_action; // Verify assembly offsets. using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters; @@ -37,6 +38,9 @@ fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) { return reinterpret_cast<fpsimd_context*>(header); } +using namespace Common::Literals; +constexpr u32 StackSize = 32_KiB; + } // namespace void* ArmNce::RestoreGuestContext(void* raw_context) { @@ -104,19 +108,10 @@ void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) { host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0); } -bool ArmNce::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { +bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext; auto* info = static_cast<siginfo_t*>(raw_info); - // Try to handle an invalid access. - // TODO: handle accesses which split a page? - const Common::ProcessAddress addr = - (reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK); - if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) { - // We handled the access successfully and are returning to guest code. - return true; - } - // We can't handle the access, so determine why we crashed. const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr); @@ -143,8 +138,44 @@ bool ArmNce::HandleGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw return false; } -void ArmNce::HandleHostFault(int sig, void* raw_info, void* raw_context) { - return g_orig_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context); +bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { + auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext; + auto* fpctx = GetFloatingPointState(host_ctx); + auto& memory = guest_ctx->system->ApplicationMemory(); + + // Match and execute an instruction. + auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx); + if (next_pc) { + host_ctx.pc = *next_pc; + return true; + } + + // We couldn't handle the access. + return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); +} + +bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) { + auto* info = static_cast<siginfo_t*>(raw_info); + + // Try to handle an invalid access. + // TODO: handle accesses which split a page? + const Common::ProcessAddress addr = + (reinterpret_cast<u64>(info->si_addr) & ~Memory::YUZU_PAGEMASK); + if (guest_ctx->system->ApplicationMemory().InvalidateNCE(addr, Memory::YUZU_PAGESIZE)) { + // We handled the access successfully and are returning to guest code. + return true; + } + + // We couldn't handle the access. + return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); +} + +void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) { + return g_orig_bus_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context); +} + +void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) { + return g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context); } void ArmNce::LockThread(Kernel::KThread* thread) { @@ -225,18 +256,31 @@ ArmNce::ArmNce(System& system, bool uses_wall_clock, std::size_t core_index) ArmNce::~ArmNce() = default; void ArmNce::Initialize() { - m_thread_id = gettid(); + if (m_thread_id == -1) { + m_thread_id = gettid(); + } + + // Configure signal stack. + if (!m_stack) { + m_stack = std::make_unique<u8[]>(StackSize); + + stack_t ss{}; + ss.ss_sp = m_stack.get(); + ss.ss_size = StackSize; + sigaltstack(&ss, nullptr); + } - // Setup our signals - static std::once_flag signals; - std::call_once(signals, [] { + // Set up signals. + static std::once_flag flag; + std::call_once(flag, [] { using HandlerType = decltype(sigaction::sa_sigaction); sigset_t signal_mask; sigemptyset(&signal_mask); sigaddset(&signal_mask, ReturnToRunCodeByExceptionLevelChangeSignal); sigaddset(&signal_mask, BreakFromRunCodeSignal); - sigaddset(&signal_mask, GuestFaultSignal); + sigaddset(&signal_mask, GuestAlignmentFaultSignal); + sigaddset(&signal_mask, GuestAccessFaultSignal); struct sigaction return_to_run_code_action {}; return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK; @@ -253,18 +297,19 @@ void ArmNce::Initialize() { break_from_run_code_action.sa_mask = signal_mask; Common::SigAction(BreakFromRunCodeSignal, &break_from_run_code_action, nullptr); - struct sigaction fault_action {}; - fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; - fault_action.sa_sigaction = reinterpret_cast<HandlerType>(&ArmNce::GuestFaultSignalHandler); - fault_action.sa_mask = signal_mask; - Common::SigAction(GuestFaultSignal, &fault_action, &g_orig_action); - - // Simplify call for g_orig_action. - // These fields occupy the same space in memory, so this should be a no-op in practice. - if (!(g_orig_action.sa_flags & SA_SIGINFO)) { - g_orig_action.sa_sigaction = - reinterpret_cast<decltype(g_orig_action.sa_sigaction)>(g_orig_action.sa_handler); - } + struct sigaction alignment_fault_action {}; + alignment_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK; + alignment_fault_action.sa_sigaction = + reinterpret_cast<HandlerType>(&ArmNce::GuestAlignmentFaultSignalHandler); + alignment_fault_action.sa_mask = signal_mask; + Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, nullptr); + + struct sigaction access_fault_action {}; + access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; + access_fault_action.sa_sigaction = + reinterpret_cast<HandlerType>(&ArmNce::GuestAccessFaultSignalHandler); + access_fault_action.sa_mask = signal_mask; + Common::SigAction(GuestAccessFaultSignal, &access_fault_action, &g_orig_segv_action); }); } diff --git a/src/core/arm/nce/arm_nce.h b/src/core/arm/nce/arm_nce.h index f55c10d1d..be9b304c4 100644 --- a/src/core/arm/nce/arm_nce.h +++ b/src/core/arm/nce/arm_nce.h @@ -61,7 +61,8 @@ private: static void ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info, void* raw_context); static void BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context); - static void GuestFaultSignalHandler(int sig, void* info, void* raw_context); + static void GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context); + static void GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context); static void LockThreadParameters(void* tpidr); static void UnlockThreadParameters(void* tpidr); @@ -70,8 +71,11 @@ private: // C++ implementation functions for assembly definitions. static void* RestoreGuestContext(void* raw_context); static void SaveGuestContext(GuestContext* ctx, void* raw_context); - static bool HandleGuestFault(GuestContext* ctx, void* info, void* raw_context); - static void HandleHostFault(int sig, void* info, void* raw_context); + static bool HandleFailedGuestFault(GuestContext* ctx, void* info, void* raw_context); + static bool HandleGuestAlignmentFault(GuestContext* ctx, void* info, void* raw_context); + static bool HandleGuestAccessFault(GuestContext* ctx, void* info, void* raw_context); + static void HandleHostAlignmentFault(int sig, void* info, void* raw_context); + static void HandleHostAccessFault(int sig, void* info, void* raw_context); public: Core::System& m_system; @@ -83,6 +87,9 @@ public: // Core context. GuestContext m_guest_ctx{}; Kernel::KThread* m_running_thread{}; + + // Stack for signal processing. + std::unique_ptr<u8[]> m_stack{}; }; } // namespace Core diff --git a/src/core/arm/nce/arm_nce.s b/src/core/arm/nce/arm_nce.s index 4aeda4740..c68c05949 100644 --- a/src/core/arm/nce/arm_nce.s +++ b/src/core/arm/nce/arm_nce.s @@ -130,11 +130,11 @@ _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_: ret -/* static void Core::ArmNce::GuestFaultSignalHandler(int sig, void* info, void* raw_context) */ -.section .text._ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, "ax", %progbits -.global _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_ -.type _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_, %function -_ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_: +/* static void Core::ArmNce::GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context) */ +.section .text._ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, "ax", %progbits +.global _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_ +.type _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, %function +_ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_: /* Check to see if we have the correct TLS magic. */ mrs x8, tpidr_el0 ldr w9, [x8, #(TpidrEl0TlsMagic)] @@ -146,7 +146,7 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_: /* Incorrect TLS magic, so this is a host fault. */ /* Tail call the handler. */ - b _ZN4Core6ArmNce15HandleHostFaultEiPvS1_ + b _ZN4Core6ArmNce24HandleHostAlignmentFaultEiPvS1_ 1: /* Correct TLS magic, so this is a guest fault. */ @@ -163,7 +163,53 @@ _ZN4Core6ArmNce23GuestFaultSignalHandlerEiPvS1_: msr tpidr_el0, x3 /* Call the handler. */ - bl _ZN4Core6ArmNce16HandleGuestFaultEPNS_12GuestContextEPvS3_ + bl _ZN4Core6ArmNce25HandleGuestAlignmentFaultEPNS_12GuestContextEPvS3_ + + /* If the handler returned false, we want to preserve the host tpidr_el0. */ + cbz x0, 2f + + /* Otherwise, restore guest tpidr_el0. */ + msr tpidr_el0, x19 + +2: + ldr x19, [sp, #0x10] + ldp x29, x30, [sp], #0x20 + ret + +/* static void Core::ArmNce::GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context) */ +.section .text._ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, "ax", %progbits +.global _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_ +.type _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, %function +_ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_: + /* Check to see if we have the correct TLS magic. */ + mrs x8, tpidr_el0 + ldr w9, [x8, #(TpidrEl0TlsMagic)] + + LOAD_IMMEDIATE_32(w10, TlsMagic) + + cmp w9, w10 + b.eq 1f + + /* Incorrect TLS magic, so this is a host fault. */ + /* Tail call the handler. */ + b _ZN4Core6ArmNce21HandleHostAccessFaultEiPvS1_ + +1: + /* Correct TLS magic, so this is a guest fault. */ + stp x29, x30, [sp, #-0x20]! + str x19, [sp, #0x10] + mov x29, sp + + /* Save the old tpidr_el0. */ + mov x19, x8 + + /* Restore host tpidr_el0. */ + ldr x0, [x8, #(TpidrEl0NativeContext)] + ldr x3, [x0, #(GuestContextHostContext + HostContextTpidrEl0)] + msr tpidr_el0, x3 + + /* Call the handler. */ + bl _ZN4Core6ArmNce22HandleGuestAccessFaultEPNS_12GuestContextEPvS3_ /* If the handler returned false, we want to preserve the host tpidr_el0. */ cbz x0, 2f diff --git a/src/core/arm/nce/arm_nce_asm_definitions.h b/src/core/arm/nce/arm_nce_asm_definitions.h index 8a9b285b5..8ea4383f7 100644 --- a/src/core/arm/nce/arm_nce_asm_definitions.h +++ b/src/core/arm/nce/arm_nce_asm_definitions.h @@ -10,7 +10,8 @@ #define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2 #define BreakFromRunCodeSignal SIGURG -#define GuestFaultSignal SIGSEGV +#define GuestAccessFaultSignal SIGSEGV +#define GuestAlignmentFaultSignal SIGBUS #define GuestContextSp 0xF8 #define GuestContextHostContext 0x320 diff --git a/src/core/arm/nce/interpreter_visitor.cpp b/src/core/arm/nce/interpreter_visitor.cpp new file mode 100644 index 000000000..8e81c66a5 --- /dev/null +++ b/src/core/arm/nce/interpreter_visitor.cpp @@ -0,0 +1,825 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs> +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/bit_cast.h" +#include "core/arm/nce/interpreter_visitor.h" + +#include <dynarmic/frontend/A64/decoder/a64.h> + +namespace Core { + +template <u32 BitSize> +u64 SignExtendToLong(u64 value) { + u64 mask = 1ULL << (BitSize - 1); + value &= (1ULL << BitSize) - 1; + return (value ^ mask) - mask; +} + +static u64 SignExtendToLong(u64 value, u64 bitsize) { + switch (bitsize) { + case 8: + return SignExtendToLong<8>(value); + case 16: + return SignExtendToLong<16>(value); + case 32: + return SignExtendToLong<32>(value); + default: + return value; + } +} + +template <u64 BitSize> +u32 SignExtendToWord(u32 value) { + u32 mask = 1ULL << (BitSize - 1); + value &= (1ULL << BitSize) - 1; + return (value ^ mask) - mask; +} + +static u32 SignExtendToWord(u32 value, u64 bitsize) { + switch (bitsize) { + case 8: + return SignExtendToWord<8>(value); + case 16: + return SignExtendToWord<16>(value); + default: + return value; + } +} + +static u64 SignExtend(u64 value, u64 bitsize, u64 regsize) { + if (regsize == 64) { + return SignExtendToLong(value, bitsize); + } else { + return SignExtendToWord(static_cast<u32>(value), bitsize); + } +} + +static u128 VectorGetElement(u128 value, u64 bitsize) { + switch (bitsize) { + case 8: + return {value[0] & ((1ULL << 8) - 1), 0}; + case 16: + return {value[0] & ((1ULL << 16) - 1), 0}; + case 32: + return {value[0] & ((1ULL << 32) - 1), 0}; + case 64: + return {value[0], 0}; + default: + return value; + } +} + +u64 InterpreterVisitor::ExtendReg(size_t bitsize, Reg reg, Imm<3> option, u8 shift) { + ASSERT(shift <= 4); + ASSERT(bitsize == 32 || bitsize == 64); + u64 val = this->GetReg(reg); + size_t len; + u64 extended; + bool signed_extend; + + switch (option.ZeroExtend()) { + case 0b000: { // UXTB + val &= ((1ULL << 8) - 1); + len = 8; + signed_extend = false; + break; + } + case 0b001: { // UXTH + val &= ((1ULL << 16) - 1); + len = 16; + signed_extend = false; + break; + } + case 0b010: { // UXTW + val &= ((1ULL << 32) - 1); + len = 32; + signed_extend = false; + break; + } + case 0b011: { // UXTX + len = 64; + signed_extend = false; + break; + } + case 0b100: { // SXTB + val &= ((1ULL << 8) - 1); + len = 8; + signed_extend = true; + break; + } + case 0b101: { // SXTH + val &= ((1ULL << 16) - 1); + len = 16; + signed_extend = true; + break; + } + case 0b110: { // SXTW + val &= ((1ULL << 32) - 1); + len = 32; + signed_extend = true; + break; + } + case 0b111: { // SXTX + len = 64; + signed_extend = true; + break; + } + default: + UNREACHABLE(); + } + + if (len < bitsize && signed_extend) { + extended = SignExtend(val, len, bitsize); + } else { + extended = val; + } + + return extended << shift; +} + +u128 InterpreterVisitor::GetVec(Vec v) { + return m_fpsimd_regs[static_cast<u32>(v)]; +} + +u64 InterpreterVisitor::GetReg(Reg r) { + return m_regs[static_cast<u32>(r)]; +} + +u64 InterpreterVisitor::GetSp() { + return m_sp; +} + +u64 InterpreterVisitor::GetPc() { + return m_pc; +} + +void InterpreterVisitor::SetVec(Vec v, u128 value) { + m_fpsimd_regs[static_cast<u32>(v)] = value; +} + +void InterpreterVisitor::SetReg(Reg r, u64 value) { + m_regs[static_cast<u32>(r)] = value; +} + +void InterpreterVisitor::SetSp(u64 value) { + m_sp = value; +} + +bool InterpreterVisitor::Ordered(size_t size, bool L, bool o0, Reg Rn, Reg Rt) { + const auto memop = L ? MemOp::Load : MemOp::Store; + const size_t elsize = 8 << size; + const size_t datasize = elsize; + + // Operation + const size_t dbytes = datasize / 8; + + u64 address; + if (Rn == Reg::SP) { + address = this->GetSp(); + } else { + address = this->GetReg(Rn); + } + + switch (memop) { + case MemOp::Store: { + std::atomic_thread_fence(std::memory_order_seq_cst); + u64 value = this->GetReg(Rt); + m_memory.WriteBlock(address, &value, dbytes); + std::atomic_thread_fence(std::memory_order_seq_cst); + break; + } + case MemOp::Load: { + u64 value = 0; + m_memory.ReadBlock(address, &value, dbytes); + this->SetReg(Rt, value); + std::atomic_thread_fence(std::memory_order_seq_cst); + break; + } + default: + UNREACHABLE(); + } + + return true; +} + +bool InterpreterVisitor::STLLR(Imm<2> sz, Reg Rn, Reg Rt) { + const size_t size = sz.ZeroExtend<size_t>(); + const bool L = 0; + const bool o0 = 0; + return this->Ordered(size, L, o0, Rn, Rt); +} + +bool InterpreterVisitor::STLR(Imm<2> sz, Reg Rn, Reg Rt) { + const size_t size = sz.ZeroExtend<size_t>(); + const bool L = 0; + const bool o0 = 1; + return this->Ordered(size, L, o0, Rn, Rt); +} + +bool InterpreterVisitor::LDLAR(Imm<2> sz, Reg Rn, Reg Rt) { + const size_t size = sz.ZeroExtend<size_t>(); + const bool L = 1; + const bool o0 = 0; + return this->Ordered(size, L, o0, Rn, Rt); +} + +bool InterpreterVisitor::LDAR(Imm<2> sz, Reg Rn, Reg Rt) { + const size_t size = sz.ZeroExtend<size_t>(); + const bool L = 1; + const bool o0 = 1; + return this->Ordered(size, L, o0, Rn, Rt); +} + +bool InterpreterVisitor::LDR_lit_gen(bool opc_0, Imm<19> imm19, Reg Rt) { + const size_t size = opc_0 == 0 ? 4 : 8; + const s64 offset = Dynarmic::concatenate(imm19, Imm<2>{0}).SignExtend<s64>(); + const u64 address = this->GetPc() + offset; + + u64 data = 0; + m_memory.ReadBlock(address, &data, size); + + this->SetReg(Rt, data); + return true; +} + +bool InterpreterVisitor::LDR_lit_fpsimd(Imm<2> opc, Imm<19> imm19, Vec Vt) { + if (opc == 0b11) { + // Unallocated encoding + return false; + } + + const u64 size = 4 << opc.ZeroExtend(); + const u64 offset = imm19.SignExtend<u64>() << 2; + const u64 address = this->GetPc() + offset; + + u128 data{}; + m_memory.ReadBlock(address, &data, size); + this->SetVec(Vt, data); + return true; +} + +bool InterpreterVisitor::STP_LDP_gen(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, + Imm<7> imm7, Reg Rt2, Reg Rn, Reg Rt) { + if ((L == 0 && opc.Bit<0>() == 1) || opc == 0b11) { + // Unallocated encoding + return false; + } + + const auto memop = L == 1 ? MemOp::Load : MemOp::Store; + if (memop == MemOp::Load && wback && (Rt == Rn || Rt2 == Rn) && Rn != Reg::R31) { + // Unpredictable instruction + return false; + } + if (memop == MemOp::Store && wback && (Rt == Rn || Rt2 == Rn) && Rn != Reg::R31) { + // Unpredictable instruction + return false; + } + if (memop == MemOp::Load && Rt == Rt2) { + // Unpredictable instruction + return false; + } + + u64 address; + if (Rn == Reg::SP) { + address = this->GetSp(); + } else { + address = this->GetReg(Rn); + } + + const bool postindex = !not_postindex; + const bool signed_ = opc.Bit<0>() != 0; + const size_t scale = 2 + opc.Bit<1>(); + const size_t datasize = 8 << scale; + const u64 offset = imm7.SignExtend<u64>() << scale; + + if (!postindex) { + address += offset; + } + + const size_t dbytes = datasize / 8; + switch (memop) { + case MemOp::Store: { + u64 data1 = this->GetReg(Rt); + u64 data2 = this->GetReg(Rt2); + m_memory.WriteBlock(address, &data1, dbytes); + m_memory.WriteBlock(address + dbytes, &data2, dbytes); + break; + } + case MemOp::Load: { + u64 data1 = 0, data2 = 0; + m_memory.ReadBlock(address, &data1, dbytes); + m_memory.ReadBlock(address + dbytes, &data2, dbytes); + if (signed_) { + this->SetReg(Rt, SignExtend(data1, datasize, 64)); + this->SetReg(Rt2, SignExtend(data2, datasize, 64)); + } else { + this->SetReg(Rt, data1); + this->SetReg(Rt2, data2); + } + break; + } + default: + UNREACHABLE(); + } + + if (wback) { + if (postindex) { + address += offset; + } + + if (Rn == Reg::SP) { + this->SetSp(address); + } else { + this->SetReg(Rn, address); + } + } + + return true; +} + +bool InterpreterVisitor::STP_LDP_fpsimd(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, + Imm<7> imm7, Vec Vt2, Reg Rn, Vec Vt) { + if (opc == 0b11) { + // Unallocated encoding + return false; + } + + const auto memop = L == 1 ? MemOp::Load : MemOp::Store; + if (memop == MemOp::Load && Vt == Vt2) { + // Unpredictable instruction + return false; + } + + u64 address; + if (Rn == Reg::SP) { + address = this->GetSp(); + } else { + address = this->GetReg(Rn); + } + + const bool postindex = !not_postindex; + const size_t scale = 2 + opc.ZeroExtend<size_t>(); + const size_t datasize = 8 << scale; + const u64 offset = imm7.SignExtend<u64>() << scale; + const size_t dbytes = datasize / 8; + + if (!postindex) { + address += offset; + } + + switch (memop) { + case MemOp::Store: { + u128 data1 = VectorGetElement(this->GetVec(Vt), datasize); + u128 data2 = VectorGetElement(this->GetVec(Vt2), datasize); + m_memory.WriteBlock(address, &data1, dbytes); + m_memory.WriteBlock(address + dbytes, &data2, dbytes); + break; + } + case MemOp::Load: { + u128 data1{}, data2{}; + m_memory.ReadBlock(address, &data1, dbytes); + m_memory.ReadBlock(address + dbytes, &data2, dbytes); + this->SetVec(Vt, data1); + this->SetVec(Vt2, data2); + break; + } + default: + UNREACHABLE(); + } + + if (wback) { + if (postindex) { + address += offset; + } + + if (Rn == Reg::SP) { + this->SetSp(address); + } else { + this->SetReg(Rn, address); + } + } + + return true; +} + +bool InterpreterVisitor::RegisterImmediate(bool wback, bool postindex, size_t scale, u64 offset, + Imm<2> size, Imm<2> opc, Reg Rn, Reg Rt) { + MemOp memop; + bool signed_ = false; + size_t regsize = 0; + + if (opc.Bit<1>() == 0) { + memop = opc.Bit<0>() ? MemOp::Load : MemOp::Store; + regsize = size == 0b11 ? 64 : 32; + signed_ = false; + } else if (size == 0b11) { + memop = MemOp::Prefetch; + ASSERT(!opc.Bit<0>()); + } else { + memop = MemOp::Load; + ASSERT(!(size == 0b10 && opc.Bit<0>() == 1)); + regsize = opc.Bit<0>() ? 32 : 64; + signed_ = true; + } + + if (memop == MemOp::Load && wback && Rn == Rt && Rn != Reg::R31) { + // Unpredictable instruction + return false; + } + if (memop == MemOp::Store && wback && Rn == Rt && Rn != Reg::R31) { + // Unpredictable instruction + return false; + } + + u64 address; + if (Rn == Reg::SP) { + address = this->GetSp(); + } else { + address = this->GetReg(Rn); + } + if (!postindex) { + address += offset; + } + + const size_t datasize = 8 << scale; + switch (memop) { + case MemOp::Store: { + u64 data = this->GetReg(Rt); + m_memory.WriteBlock(address, &data, datasize / 8); + break; + } + case MemOp::Load: { + u64 data = 0; + m_memory.ReadBlock(address, &data, datasize / 8); + if (signed_) { + this->SetReg(Rt, SignExtend(data, datasize, regsize)); + } else { + this->SetReg(Rt, data); + } + break; + } + case MemOp::Prefetch: + // this->Prefetch(address, Rt) + break; + } + + if (wback) { + if (postindex) { + address += offset; + } + + if (Rn == Reg::SP) { + this->SetSp(address); + } else { + this->SetReg(Rn, address); + } + } + + return true; +} + +bool InterpreterVisitor::STRx_LDRx_imm_1(Imm<2> size, Imm<2> opc, Imm<9> imm9, bool not_postindex, + Reg Rn, Reg Rt) { + const bool wback = true; + const bool postindex = !not_postindex; + const size_t scale = size.ZeroExtend<size_t>(); + const u64 offset = imm9.SignExtend<u64>(); + + return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt); +} + +bool InterpreterVisitor::STRx_LDRx_imm_2(Imm<2> size, Imm<2> opc, Imm<12> imm12, Reg Rn, Reg Rt) { + const bool wback = false; + const bool postindex = false; + const size_t scale = size.ZeroExtend<size_t>(); + const u64 offset = imm12.ZeroExtend<u64>() << scale; + + return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt); +} + +bool InterpreterVisitor::STURx_LDURx(Imm<2> size, Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) { + const bool wback = false; + const bool postindex = false; + const size_t scale = size.ZeroExtend<size_t>(); + const u64 offset = imm9.SignExtend<u64>(); + + return this->RegisterImmediate(wback, postindex, scale, offset, size, opc, Rn, Rt); +} + +bool InterpreterVisitor::SIMDImmediate(bool wback, bool postindex, size_t scale, u64 offset, + MemOp memop, Reg Rn, Vec Vt) { + const size_t datasize = 8 << scale; + + u64 address; + if (Rn == Reg::SP) { + address = this->GetSp(); + } else { + address = this->GetReg(Rn); + } + + if (!postindex) { + address += offset; + } + + switch (memop) { + case MemOp::Store: { + u128 data = VectorGetElement(this->GetVec(Vt), datasize); + m_memory.WriteBlock(address, &data, datasize / 8); + break; + } + case MemOp::Load: { + u128 data{}; + m_memory.ReadBlock(address, &data, datasize); + this->SetVec(Vt, data); + break; + } + default: + UNREACHABLE(); + } + + if (wback) { + if (postindex) { + address += offset; + } + + if (Rn == Reg::SP) { + this->SetSp(address); + } else { + this->SetReg(Rn, address); + } + } + + return true; +} + +bool InterpreterVisitor::STR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, + bool not_postindex, Reg Rn, Vec Vt) { + const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>(); + if (scale > 4) { + // Unallocated encoding + return false; + } + + const bool wback = true; + const bool postindex = !not_postindex; + const u64 offset = imm9.SignExtend<u64>(); + + return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt); +} + +bool InterpreterVisitor::STR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, + Vec Vt) { + const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>(); + if (scale > 4) { + // Unallocated encoding + return false; + } + + const bool wback = false; + const bool postindex = false; + const u64 offset = imm12.ZeroExtend<u64>() << scale; + + return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt); +} + +bool InterpreterVisitor::LDR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, + bool not_postindex, Reg Rn, Vec Vt) { + const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>(); + if (scale > 4) { + // Unallocated encoding + return false; + } + + const bool wback = true; + const bool postindex = !not_postindex; + const u64 offset = imm9.SignExtend<u64>(); + + return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt); +} + +bool InterpreterVisitor::LDR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, + Vec Vt) { + const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>(); + if (scale > 4) { + // Unallocated encoding + return false; + } + + const bool wback = false; + const bool postindex = false; + const u64 offset = imm12.ZeroExtend<u64>() << scale; + + return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt); +} + +bool InterpreterVisitor::STUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) { + const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>(); + if (scale > 4) { + // Unallocated encoding + return false; + } + + const bool wback = false; + const bool postindex = false; + const u64 offset = imm9.SignExtend<u64>(); + + return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Store, Rn, Vt); +} + +bool InterpreterVisitor::LDUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) { + const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>(); + if (scale > 4) { + // Unallocated encoding + return false; + } + + const bool wback = false; + const bool postindex = false; + const u64 offset = imm9.SignExtend<u64>(); + + return this->SIMDImmediate(wback, postindex, scale, offset, MemOp::Load, Rn, Vt); +} + +bool InterpreterVisitor::RegisterOffset(size_t scale, u8 shift, Imm<2> size, Imm<1> opc_1, + Imm<1> opc_0, Reg Rm, Imm<3> option, Reg Rn, Reg Rt) { + MemOp memop; + size_t regsize = 64; + bool signed_ = false; + + if (opc_1 == 0) { + memop = opc_0 == 1 ? MemOp::Load : MemOp::Store; + regsize = size == 0b11 ? 64 : 32; + signed_ = false; + } else if (size == 0b11) { + memop = MemOp::Prefetch; + if (opc_0 == 1) { + // Unallocated encoding + return false; + } + } else { + memop = MemOp::Load; + if (size == 0b10 && opc_0 == 1) { + // Unallocated encoding + return false; + } + regsize = opc_0 == 1 ? 32 : 64; + signed_ = true; + } + + const size_t datasize = 8 << scale; + + // Operation + const u64 offset = this->ExtendReg(64, Rm, option, shift); + + u64 address; + if (Rn == Reg::SP) { + address = this->GetSp(); + } else { + address = this->GetReg(Rn); + } + address += offset; + + switch (memop) { + case MemOp::Store: { + u64 data = this->GetReg(Rt); + m_memory.WriteBlock(address, &data, datasize / 8); + break; + } + case MemOp::Load: { + u64 data = 0; + m_memory.ReadBlock(address, &data, datasize / 8); + if (signed_) { + this->SetReg(Rt, SignExtend(data, datasize, regsize)); + } else { + this->SetReg(Rt, data); + } + break; + } + case MemOp::Prefetch: + break; + } + + return true; +} + +bool InterpreterVisitor::STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Reg Rt) { + const Imm<1> opc_0{0}; + const size_t scale = size.ZeroExtend<size_t>(); + const u8 shift = S ? static_cast<u8>(scale) : 0; + if (!option.Bit<1>()) { + // Unallocated encoding + return false; + } + return this->RegisterOffset(scale, shift, size, opc_1, opc_0, Rm, option, Rn, Rt); +} + +bool InterpreterVisitor::LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Reg Rt) { + const Imm<1> opc_0{1}; + const size_t scale = size.ZeroExtend<size_t>(); + const u8 shift = S ? static_cast<u8>(scale) : 0; + if (!option.Bit<1>()) { + // Unallocated encoding + return false; + } + return this->RegisterOffset(scale, shift, size, opc_1, opc_0, Rm, option, Rn, Rt); +} + +bool InterpreterVisitor::SIMDOffset(size_t scale, u8 shift, Imm<1> opc_0, Reg Rm, Imm<3> option, + Reg Rn, Vec Vt) { + const auto memop = opc_0 == 1 ? MemOp::Load : MemOp::Store; + const size_t datasize = 8 << scale; + + // Operation + const u64 offset = this->ExtendReg(64, Rm, option, shift); + + u64 address; + if (Rn == Reg::SP) { + address = this->GetSp(); + } else { + address = this->GetReg(Rn); + } + address += offset; + + switch (memop) { + case MemOp::Store: { + u128 data = VectorGetElement(this->GetVec(Vt), datasize); + m_memory.WriteBlock(address, &data, datasize / 8); + break; + } + case MemOp::Load: { + u128 data{}; + m_memory.ReadBlock(address, &data, datasize / 8); + this->SetVec(Vt, data); + break; + } + default: + UNREACHABLE(); + } + + return true; +} + +bool InterpreterVisitor::STR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, + Reg Rn, Vec Vt) { + const Imm<1> opc_0{0}; + const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>(); + if (scale > 4) { + // Unallocated encoding + return false; + } + const u8 shift = S ? static_cast<u8>(scale) : 0; + if (!option.Bit<1>()) { + // Unallocated encoding + return false; + } + return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt); +} + +bool InterpreterVisitor::LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, + Reg Rn, Vec Vt) { + const Imm<1> opc_0{1}; + const size_t scale = Dynarmic::concatenate(opc_1, size).ZeroExtend<size_t>(); + if (scale > 4) { + // Unallocated encoding + return false; + } + const u8 shift = S ? static_cast<u8>(scale) : 0; + if (!option.Bit<1>()) { + // Unallocated encoding + return false; + } + return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt); +} + +std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context, + fpsimd_context* fpsimd_context) { + // Construct the interpreter. + std::span<u64, 31> regs(reinterpret_cast<u64*>(context->regs), 31); + std::span<u128, 32> vregs(reinterpret_cast<u128*>(fpsimd_context->vregs), 32); + u64& sp = *reinterpret_cast<u64*>(&context->sp); + const u64& pc = *reinterpret_cast<u64*>(&context->pc); + + InterpreterVisitor visitor(memory, regs, vregs, sp, pc); + + // Read the instruction at the program counter. + u32 instruction = memory.Read32(pc); + bool was_executed = false; + + // Interpret the instruction. + if (auto decoder = Dynarmic::A64::Decode<VisitorBase>(instruction)) { + was_executed = decoder->get().call(visitor, instruction); + } else { + LOG_ERROR(Core_ARM, "Unallocated encoding: {:#x}", instruction); + } + + if (was_executed) { + return pc + 4; + } + + return std::nullopt; +} + +} // namespace Core diff --git a/src/core/arm/nce/interpreter_visitor.h b/src/core/arm/nce/interpreter_visitor.h new file mode 100644 index 000000000..f90d876ab --- /dev/null +++ b/src/core/arm/nce/interpreter_visitor.h @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs> +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <signal.h> +#include <unistd.h> + +#include "core/arm/nce/visitor_base.h" + +namespace Core { + +namespace Memory { +class Memory; +} + +class InterpreterVisitor final : public VisitorBase { +public: + explicit InterpreterVisitor(Core::Memory::Memory& memory, std::span<u64, 31> regs, + std::span<u128, 32> fpsimd_regs, u64& sp, const u64& pc) + : m_memory(memory), m_regs(regs), m_fpsimd_regs(fpsimd_regs), m_sp(sp), m_pc(pc) {} + ~InterpreterVisitor() override = default; + + enum class MemOp { + Load, + Store, + Prefetch, + }; + + u128 GetVec(Vec v); + u64 GetReg(Reg r); + u64 GetSp(); + u64 GetPc(); + + void SetVec(Vec v, u128 value); + void SetReg(Reg r, u64 value); + void SetSp(u64 value); + + u64 ExtendReg(size_t bitsize, Reg reg, Imm<3> option, u8 shift); + + // Loads and stores - Load/Store Exclusive + bool Ordered(size_t size, bool L, bool o0, Reg Rn, Reg Rt); + bool STLLR(Imm<2> size, Reg Rn, Reg Rt) override; + bool STLR(Imm<2> size, Reg Rn, Reg Rt) override; + bool LDLAR(Imm<2> size, Reg Rn, Reg Rt) override; + bool LDAR(Imm<2> size, Reg Rn, Reg Rt) override; + + // Loads and stores - Load register (literal) + bool LDR_lit_gen(bool opc_0, Imm<19> imm19, Reg Rt) override; + bool LDR_lit_fpsimd(Imm<2> opc, Imm<19> imm19, Vec Vt) override; + + // Loads and stores - Load/Store register pair + bool STP_LDP_gen(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, Reg Rt2, + Reg Rn, Reg Rt) override; + bool STP_LDP_fpsimd(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, Vec Vt2, + Reg Rn, Vec Vt) override; + + // Loads and stores - Load/Store register (immediate) + bool RegisterImmediate(bool wback, bool postindex, size_t scale, u64 offset, Imm<2> size, + Imm<2> opc, Reg Rn, Reg Rt); + bool STRx_LDRx_imm_1(Imm<2> size, Imm<2> opc, Imm<9> imm9, bool not_postindex, Reg Rn, + Reg Rt) override; + bool STRx_LDRx_imm_2(Imm<2> size, Imm<2> opc, Imm<12> imm12, Reg Rn, Reg Rt) override; + bool STURx_LDURx(Imm<2> size, Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) override; + + bool SIMDImmediate(bool wback, bool postindex, size_t scale, u64 offset, MemOp memop, Reg Rn, + Vec Vt); + bool STR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, Reg Rn, + Vec Vt) override; + bool STR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) override; + bool LDR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, Reg Rn, + Vec Vt) override; + bool LDR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) override; + bool STUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) override; + bool LDUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) override; + + // Loads and stores - Load/Store register (register offset) + bool RegisterOffset(size_t scale, u8 shift, Imm<2> size, Imm<1> opc_1, Imm<1> opc_0, Reg Rm, + Imm<3> option, Reg Rn, Reg Rt); + bool STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Reg Rt) override; + bool LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Reg Rt) override; + + bool SIMDOffset(size_t scale, u8 shift, Imm<1> opc_0, Reg Rm, Imm<3> option, Reg Rn, Vec Vt); + bool STR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Vec Vt) override; + bool LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Vec Vt) override; + +private: + Core::Memory::Memory& m_memory; + std::span<u64, 31> m_regs; + std::span<u128, 32> m_fpsimd_regs; + u64& m_sp; + const u64& m_pc; +}; + +std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context, + fpsimd_context* fpsimd_context); + +} // namespace Core diff --git a/src/core/arm/nce/visitor_base.h b/src/core/arm/nce/visitor_base.h new file mode 100644 index 000000000..8fb032912 --- /dev/null +++ b/src/core/arm/nce/visitor_base.h @@ -0,0 +1,2777 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs> +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <dynarmic/frontend/A64/a64_types.h> +#include <dynarmic/frontend/imm.h> + +namespace Core { + +class VisitorBase { +public: + using instruction_return_type = bool; + + template <size_t BitSize> + using Imm = Dynarmic::Imm<BitSize>; + using Reg = Dynarmic::A64::Reg; + using Vec = Dynarmic::A64::Vec; + using Cond = Dynarmic::A64::Cond; + + virtual ~VisitorBase() {} + + virtual bool UnallocatedEncoding() { + return false; + } + + // Data processing - Immediate - PC relative addressing + virtual bool ADR(Imm<2> immlo, Imm<19> immhi, Reg Rd) { + return false; + } + virtual bool ADRP(Imm<2> immlo, Imm<19> immhi, Reg Rd) { + return false; + } + + // Data processing - Immediate - Add/Sub (with tag) + virtual bool ADDG(Imm<6> offset_imm, Imm<4> tag_offset, Reg Rn, Reg Rd) { + return false; + } + virtual bool SUBG(Imm<6> offset_imm, Imm<4> tag_offset, Reg Rn, Reg Rd) { + return false; + } + + // Data processing - Immediate - Add/Sub + virtual bool ADD_imm(bool sf, Imm<2> shift, Imm<12> imm12, Reg Rn, Reg Rd) { + return false; + } + virtual bool ADDS_imm(bool sf, Imm<2> shift, Imm<12> imm12, Reg Rn, Reg Rd) { + return false; + } + virtual bool SUB_imm(bool sf, Imm<2> shift, Imm<12> imm12, Reg Rn, Reg Rd) { + return false; + } + virtual bool SUBS_imm(bool sf, Imm<2> shift, Imm<12> imm12, Reg Rn, Reg Rd) { + return false; + } + + // Data processing - Immediate - Logical + virtual bool AND_imm(bool sf, bool N, Imm<6> immr, Imm<6> imms, Reg Rn, Reg Rd) { + return false; + } + virtual bool ORR_imm(bool sf, bool N, Imm<6> immr, Imm<6> imms, Reg Rn, Reg Rd) { + return false; + } + virtual bool EOR_imm(bool sf, bool N, Imm<6> immr, Imm<6> imms, Reg Rn, Reg Rd) { + return false; + } + virtual bool ANDS_imm(bool sf, bool N, Imm<6> immr, Imm<6> imms, Reg Rn, Reg Rd) { + return false; + } + + // Data processing - Immediate - Move Wide + virtual bool MOVN(bool sf, Imm<2> hw, Imm<16> imm16, Reg Rd) { + return false; + } + virtual bool MOVZ(bool sf, Imm<2> hw, Imm<16> imm16, Reg Rd) { + return false; + } + virtual bool MOVK(bool sf, Imm<2> hw, Imm<16> imm16, Reg Rd) { + return false; + } + + // Data processing - Immediate - Bitfield + virtual bool SBFM(bool sf, bool N, Imm<6> immr, Imm<6> imms, Reg Rn, Reg Rd) { + return false; + } + virtual bool BFM(bool sf, bool N, Imm<6> immr, Imm<6> imms, Reg Rn, Reg Rd) { + return false; + } + virtual bool UBFM(bool sf, bool N, Imm<6> immr, Imm<6> imms, Reg Rn, Reg Rd) { + return false; + } + virtual bool ASR_1(Imm<5> immr, Reg Rn, Reg Rd) { + return false; + } + virtual bool ASR_2(Imm<6> immr, Reg Rn, Reg Rd) { + return false; + } + virtual bool SXTB_1(Reg Rn, Reg Rd) { + return false; + } + virtual bool SXTB_2(Reg Rn, Reg Rd) { + return false; + } + virtual bool SXTH_1(Reg Rn, Reg Rd) { + return false; + } + virtual bool SXTH_2(Reg Rn, Reg Rd) { + return false; + } + virtual bool SXTW(Reg Rn, Reg Rd) { + return false; + } + + // Data processing - Immediate - Extract + virtual bool EXTR(bool sf, bool N, Reg Rm, Imm<6> imms, Reg Rn, Reg Rd) { + return false; + } + + // Conditional branch + virtual bool B_cond(Imm<19> imm19, Cond cond) { + return false; + } + + // Exception generation + virtual bool SVC(Imm<16> imm16) { + return false; + } + virtual bool HVC(Imm<16> imm16) { + return false; + } + virtual bool SMC(Imm<16> imm16) { + return false; + } + virtual bool BRK(Imm<16> imm16) { + return false; + } + virtual bool HLT(Imm<16> imm16) { + return false; + } + virtual bool DCPS1(Imm<16> imm16) { + return false; + } + virtual bool DCPS2(Imm<16> imm16) { + return false; + } + virtual bool DCPS3(Imm<16> imm16) { + return false; + } + + // System + virtual bool MSR_imm(Imm<3> op1, Imm<4> CRm, Imm<3> op2) { + return false; + } + virtual bool HINT(Imm<4> CRm, Imm<3> op2) { + return false; + } + virtual bool NOP() { + return false; + } + virtual bool YIELD() { + return false; + } + virtual bool WFE() { + return false; + } + virtual bool WFI() { + return false; + } + virtual bool SEV() { + return false; + } + virtual bool SEVL() { + return false; + } + virtual bool XPAC_1(bool D, Reg Rd) { + return false; + } + virtual bool XPAC_2() { + return false; + } + virtual bool PACIA_1(bool Z, Reg Rn, Reg Rd) { + return false; + } + virtual bool PACIA_2() { + return false; + } + virtual bool PACIB_1(bool Z, Reg Rn, Reg Rd) { + return false; + } + virtual bool PACIB_2() { + return false; + } + virtual bool AUTIA_1(bool Z, Reg Rn, Reg Rd) { + return false; + } + virtual bool AUTIA_2() { + return false; + } + virtual bool AUTIB_1(bool Z, Reg Rn, Reg Rd) { + return false; + } + virtual bool AUTIB_2() { + return false; + } + virtual bool BTI(Imm<2> upper_op2) { + return false; + } + virtual bool ESB() { + return false; + } + virtual bool PSB() { + return false; + } + virtual bool TSB() { + return false; + } + virtual bool CSDB() { + return false; + } + virtual bool CLREX(Imm<4> CRm) { + return false; + } + virtual bool DSB(Imm<4> CRm) { + return false; + } + virtual bool SSBB() { + return false; + } + virtual bool PSSBB() { + return false; + } + virtual bool DMB(Imm<4> CRm) { + return false; + } + virtual bool ISB(Imm<4> CRm) { + return false; + } + virtual bool SYS(Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3> op2, Reg Rt) { + return false; + } + virtual bool SB() { + return false; + } + virtual bool MSR_reg(Imm<1> o0, Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3> op2, Reg Rt) { + return false; + } + virtual bool SYSL(Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3> op2, Reg Rt) { + return false; + } + virtual bool MRS(Imm<1> o0, Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3> op2, Reg Rt) { + return false; + } + + // System - Flag manipulation instructions + virtual bool CFINV() { + return false; + } + virtual bool RMIF(Imm<6> lsb, Reg Rn, Imm<4> mask) { + return false; + } + virtual bool SETF8(Reg Rn) { + return false; + } + virtual bool SETF16(Reg Rn) { + return false; + } + + // System - Flag format instructions + virtual bool XAFlag() { + return false; + } + virtual bool AXFlag() { + return false; + } + + // SYS: Data Cache + virtual bool DC_IVAC(Reg Rt) { + return false; + } + virtual bool DC_ISW(Reg Rt) { + return false; + } + virtual bool DC_CSW(Reg Rt) { + return false; + } + virtual bool DC_CISW(Reg Rt) { + return false; + } + virtual bool DC_ZVA(Reg Rt) { + return false; + } + virtual bool DC_CVAC(Reg Rt) { + return false; + } + virtual bool DC_CVAU(Reg Rt) { + return false; + } + virtual bool DC_CVAP(Reg Rt) { + return false; + } + virtual bool DC_CIVAC(Reg Rt) { + return false; + } + + // SYS: Instruction Cache + virtual bool IC_IALLU() { + return false; + } + virtual bool IC_IALLUIS() { + return false; + } + virtual bool IC_IVAU(Reg Rt) { + return false; + } + + // Unconditional branch (Register) + virtual bool BR(Reg Rn) { + return false; + } + virtual bool BRA(bool Z, bool M, Reg Rn, Reg Rm) { + return false; + } + virtual bool BLR(Reg Rn) { + return false; + } + virtual bool BLRA(bool Z, bool M, Reg Rn, Reg Rm) { + return false; + } + virtual bool RET(Reg Rn) { + return false; + } + virtual bool RETA(bool M) { + return false; + } + virtual bool ERET() { + return false; + } + virtual bool ERETA(bool M) { + return false; + } + virtual bool DRPS() { + return false; + } + + // Unconditional branch (immediate) + virtual bool B_uncond(Imm<26> imm26) { + return false; + } + virtual bool BL(Imm<26> imm26) { + return false; + } + + // Compare and branch (immediate) + virtual bool CBZ(bool sf, Imm<19> imm19, Reg Rt) { + return false; + } + virtual bool CBNZ(bool sf, Imm<19> imm19, Reg Rt) { + return false; + } + virtual bool TBZ(Imm<1> b5, Imm<5> b40, Imm<14> imm14, Reg Rt) { + return false; + } + virtual bool TBNZ(Imm<1> b5, Imm<5> b40, Imm<14> imm14, Reg Rt) { + return false; + } + + // Loads and stores - Advanced SIMD Load/Store multiple structures + virtual bool STx_mult_1(bool Q, Imm<4> opcode, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool STx_mult_2(bool Q, Reg Rm, Imm<4> opcode, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LDx_mult_1(bool Q, Imm<4> opcode, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LDx_mult_2(bool Q, Reg Rm, Imm<4> opcode, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + + // Loads and stores - Advanced SIMD Load/Store single structures + virtual bool ST1_sngl_1(bool Q, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool ST1_sngl_2(bool Q, Reg Rm, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, + Vec Vt) { + return false; + } + virtual bool ST3_sngl_1(bool Q, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool ST3_sngl_2(bool Q, Reg Rm, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, + Vec Vt) { + return false; + } + virtual bool ST2_sngl_1(bool Q, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool ST2_sngl_2(bool Q, Reg Rm, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, + Vec Vt) { + return false; + } + virtual bool ST4_sngl_1(bool Q, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool ST4_sngl_2(bool Q, Reg Rm, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, + Vec Vt) { + return false; + } + virtual bool LD1_sngl_1(bool Q, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD1_sngl_2(bool Q, Reg Rm, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, + Vec Vt) { + return false; + } + virtual bool LD3_sngl_1(bool Q, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD3_sngl_2(bool Q, Reg Rm, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, + Vec Vt) { + return false; + } + virtual bool LD1R_1(bool Q, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD1R_2(bool Q, Reg Rm, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD3R_1(bool Q, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD3R_2(bool Q, Reg Rm, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD2_sngl_1(bool Q, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD2_sngl_2(bool Q, Reg Rm, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, + Vec Vt) { + return false; + } + virtual bool LD4_sngl_1(bool Q, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD4_sngl_2(bool Q, Reg Rm, Imm<2> upper_opcode, bool S, Imm<2> size, Reg Rn, + Vec Vt) { + return false; + } + virtual bool LD2R_1(bool Q, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD2R_2(bool Q, Reg Rm, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD4R_1(bool Q, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + virtual bool LD4R_2(bool Q, Reg Rm, Imm<2> size, Reg Rn, Vec Vt) { + return false; + } + + // Loads and stores - Load/Store Exclusive + virtual bool STXR(Imm<2> size, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool STLXR(Imm<2> size, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool STXP(Imm<1> size, Reg Rs, Reg Rt2, Reg Rn, Reg Rt) { + return false; + } + virtual bool STLXP(Imm<1> size, Reg Rs, Reg Rt2, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDXR(Imm<2> size, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDAXR(Imm<2> size, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDXP(Imm<1> size, Reg Rt2, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDAXP(Imm<1> size, Reg Rt2, Reg Rn, Reg Rt) { + return false; + } + virtual bool STLLR(Imm<2> size, Reg Rn, Reg Rt) { + return false; + } + virtual bool STLR(Imm<2> size, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDLAR(Imm<2> size, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDAR(Imm<2> size, Reg Rn, Reg Rt) { + return false; + } + virtual bool CASP(bool sz, bool L, Reg Rs, bool o0, Reg Rn, Reg Rt) { + return false; + } + virtual bool CASB(bool L, Reg Rs, bool o0, Reg Rn, Reg Rt) { + return false; + } + virtual bool CASH(bool L, Reg Rs, bool o0, Reg Rn, Reg Rt) { + return false; + } + virtual bool CAS(bool sz, bool L, Reg Rs, bool o0, Reg Rn, Reg Rt) { + return false; + } + + // Loads and stores - Load register (literal) + virtual bool LDR_lit_gen(bool opc_0, Imm<19> imm19, Reg Rt) { + return false; + } + virtual bool LDR_lit_fpsimd(Imm<2> opc, Imm<19> imm19, Vec Vt) { + return false; + } + virtual bool LDRSW_lit(Imm<19> imm19, Reg Rt) { + return false; + } + virtual bool PRFM_lit(Imm<19> imm19, Imm<5> prfop) { + return false; + } + + // Loads and stores - Load/Store no-allocate pair + virtual bool STNP_LDNP_gen(Imm<1> upper_opc, Imm<1> L, Imm<7> imm7, Reg Rt2, Reg Rn, Reg Rt) { + return false; + } + virtual bool STNP_LDNP_fpsimd(Imm<2> opc, Imm<1> L, Imm<7> imm7, Vec Vt2, Reg Rn, Vec Vt) { + return false; + } + + // Loads and stores - Load/Store register pair + virtual bool STP_LDP_gen(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, + Reg Rt2, Reg Rn, Reg Rt) { + return false; + } + virtual bool STP_LDP_fpsimd(Imm<2> opc, bool not_postindex, bool wback, Imm<1> L, Imm<7> imm7, + Vec Vt2, Reg Rn, Vec Vt) { + return false; + } + virtual bool STGP_1(Imm<7> offset_imm, Reg Rt2, Reg Rn, Reg Rt) { + return false; + } + virtual bool STGP_2(Imm<7> offset_imm, Reg Rt2, Reg Rn, Reg Rt) { + return false; + } + virtual bool STGP_3(Imm<7> offset_imm, Reg Rt2, Reg Rn, Reg Rt) { + return false; + } + + // Loads and stores - Load/Store register (immediate) + virtual bool STRx_LDRx_imm_1(Imm<2> size, Imm<2> opc, Imm<9> imm9, bool not_postindex, Reg Rn, + Reg Rt) { + return false; + } + virtual bool STRx_LDRx_imm_2(Imm<2> size, Imm<2> opc, Imm<12> imm12, Reg Rn, Reg Rt) { + return false; + } + virtual bool STURx_LDURx(Imm<2> size, Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool PRFM_imm(Imm<12> imm12, Reg Rn, Reg Rt) { + return false; + } + virtual bool PRFM_unscaled_imm(Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool STR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, + Reg Rn, Vec Vt) { + return false; + } + virtual bool STR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) { + return false; + } + virtual bool LDR_imm_fpsimd_1(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, bool not_postindex, + Reg Rn, Vec Vt) { + return false; + } + virtual bool LDR_imm_fpsimd_2(Imm<2> size, Imm<1> opc_1, Imm<12> imm12, Reg Rn, Vec Vt) { + return false; + } + virtual bool STUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) { + return false; + } + virtual bool LDUR_fpsimd(Imm<2> size, Imm<1> opc_1, Imm<9> imm9, Reg Rn, Vec Vt) { + return false; + } + + // Loads and stores - Load/Store register (unprivileged) + virtual bool STTRB(Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDTRB(Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDTRSB(Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool STTRH(Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDTRH(Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDTRSH(Imm<2> opc, Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool STTR(Imm<2> size, Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDTR(Imm<2> size, Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDTRSW(Imm<9> imm9, Reg Rn, Reg Rt) { + return false; + } + + // Loads and stores - Atomic memory options + virtual bool LDADDB(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDCLRB(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDEORB(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDSETB(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDSMAXB(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDSMINB(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDUMAXB(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDUMINB(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool SWPB(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDAPRB(Reg Rn, Reg Rt) { + return false; + } + virtual bool LDADDH(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDCLRH(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDEORH(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDSETH(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDSMAXH(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDSMINH(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDUMAXH(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDUMINH(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool SWPH(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDAPRH(Reg Rn, Reg Rt) { + return false; + } + virtual bool LDADD(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDCLR(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDEOR(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDSET(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDSMAX(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDSMIN(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDUMAX(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDUMIN(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool SWP(bool A, bool R, Reg Rs, Reg Rn, Reg Rt) { + return false; + } + virtual bool LDAPR(Reg Rn, Reg Rt) { + return false; + } + + // Loads and stores - Load/Store register (register offset) + virtual bool STRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Reg Rt) { + return false; + } + virtual bool LDRx_reg(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Reg Rt) { + return false; + } + virtual bool STR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Vec Vt) { + return false; + } + virtual bool LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3> option, bool S, Reg Rn, + Vec Vt) { + return false; + } + + // Loads and stores - Load/Store memory tags + virtual bool STG_1(Imm<9> imm9, Reg Rn) { + return false; + } + virtual bool STG_2(Imm<9> imm9, Reg Rn) { + return false; + } + virtual bool STG_3(Imm<9> imm9, Reg Rn) { + return false; + } + virtual bool LDG(Imm<9> offset_imm, Reg Rn, Reg Rt) { + return false; + } + virtual bool STZG_1(Imm<9> offset_imm, Reg Rn) { + return false; + } + virtual bool STZG_2(Imm<9> offset_imm, Reg Rn) { + return false; + } + virtual bool STZG_3(Imm<9> offset_imm, Reg Rn) { + return false; + } + virtual bool ST2G_1(Imm<9> offset_imm, Reg Rn) { + return false; + } + virtual bool ST2G_2(Imm<9> offset_imm, Reg Rn) { + return false; + } + virtual bool ST2G_3(Imm<9> offset_imm, Reg Rn) { + return false; + } + virtual bool STGV(Reg Rn, Reg Rt) { + return false; + } + virtual bool STZ2G_1(Imm<9> offset_imm, Reg Rn) { + return false; + } + virtual bool STZ2G_2(Imm<9> offset_imm, Reg Rn) { + return false; + } + virtual bool STZ2G_3(Imm<9> offset_imm, Reg Rn) { + return false; + } + virtual bool LDGV(Reg Rn, Reg Rt) { + return false; + } + + // Loads and stores - Load/Store register (pointer authentication) + virtual bool LDRA(bool M, bool S, Imm<9> imm9, bool W, Reg Rn, Reg Rt) { + return false; + } + + // Data Processing - Register - 2 source + virtual bool UDIV(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool SDIV(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool LSLV(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool LSRV(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool ASRV(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool RORV(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool CRC32(bool sf, Reg Rm, Imm<2> sz, Reg Rn, Reg Rd) { + return false; + } + virtual bool CRC32C(bool sf, Reg Rm, Imm<2> sz, Reg Rn, Reg Rd) { + return false; + } + virtual bool PACGA(Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool SUBP(Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool IRG(Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool GMI(Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool SUBPS(Reg Rm, Reg Rn, Reg Rd) { + return false; + } + + // Data Processing - Register - 1 source + virtual bool RBIT_int(bool sf, Reg Rn, Reg Rd) { + return false; + } + virtual bool REV16_int(bool sf, Reg Rn, Reg Rd) { + return false; + } + virtual bool REV(bool sf, bool opc_0, Reg Rn, Reg Rd) { + return false; + } + virtual bool CLZ_int(bool sf, Reg Rn, Reg Rd) { + return false; + } + virtual bool CLS_int(bool sf, Reg Rn, Reg Rd) { + return false; + } + virtual bool REV32_int(Reg Rn, Reg Rd) { + return false; + } + virtual bool PACDA(bool Z, Reg Rn, Reg Rd) { + return false; + } + virtual bool PACDB(bool Z, Reg Rn, Reg Rd) { + return false; + } + virtual bool AUTDA(bool Z, Reg Rn, Reg Rd) { + return false; + } + virtual bool AUTDB(bool Z, Reg Rn, Reg Rd) { + return false; + } + + // Data Processing - Register - Logical (shifted register) + virtual bool AND_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool BIC_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool ORR_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool ORN_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool EOR_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool EON(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool ANDS_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool BICS(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + + // Data Processing - Register - Add/Sub (shifted register) + virtual bool ADD_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool ADDS_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool SUB_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + virtual bool SUBS_shift(bool sf, Imm<2> shift, Reg Rm, Imm<6> imm6, Reg Rn, Reg Rd) { + return false; + } + + // Data Processing - Register - Add/Sub (shifted register) + virtual bool ADD_ext(bool sf, Reg Rm, Imm<3> option, Imm<3> imm3, Reg Rn, Reg Rd) { + return false; + } + virtual bool ADDS_ext(bool sf, Reg Rm, Imm<3> option, Imm<3> imm3, Reg Rn, Reg Rd) { + return false; + } + virtual bool SUB_ext(bool sf, Reg Rm, Imm<3> option, Imm<3> imm3, Reg Rn, Reg Rd) { + return false; + } + virtual bool SUBS_ext(bool sf, Reg Rm, Imm<3> option, Imm<3> imm3, Reg Rn, Reg Rd) { + return false; + } + + // Data Processing - Register - Add/Sub (with carry) + virtual bool ADC(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool ADCS(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool SBC(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool SBCS(bool sf, Reg Rm, Reg Rn, Reg Rd) { + return false; + } + + // Data Processing - Register - Conditional compare + virtual bool CCMN_reg(bool sf, Reg Rm, Cond cond, Reg Rn, Imm<4> nzcv) { + return false; + } + virtual bool CCMP_reg(bool sf, Reg Rm, Cond cond, Reg Rn, Imm<4> nzcv) { + return false; + } + virtual bool CCMN_imm(bool sf, Imm<5> imm5, Cond cond, Reg Rn, Imm<4> nzcv) { + return false; + } + virtual bool CCMP_imm(bool sf, Imm<5> imm5, Cond cond, Reg Rn, Imm<4> nzcv) { + return false; + } + + // Data Processing - Register - Conditional select + virtual bool CSEL(bool sf, Reg Rm, Cond cond, Reg Rn, Reg Rd) { + return false; + } + virtual bool CSINC(bool sf, Reg Rm, Cond cond, Reg Rn, Reg Rd) { + return false; + } + virtual bool CSINV(bool sf, Reg Rm, Cond cond, Reg Rn, Reg Rd) { + return false; + } + virtual bool CSNEG(bool sf, Reg Rm, Cond cond, Reg Rn, Reg Rd) { + return false; + } + + // Data Processing - Register - 3 source + virtual bool MADD(bool sf, Reg Rm, Reg Ra, Reg Rn, Reg Rd) { + return false; + } + virtual bool MSUB(bool sf, Reg Rm, Reg Ra, Reg Rn, Reg Rd) { + return false; + } + virtual bool SMADDL(Reg Rm, Reg Ra, Reg Rn, Reg Rd) { + return false; + } + virtual bool SMSUBL(Reg Rm, Reg Ra, Reg Rn, Reg Rd) { + return false; + } + virtual bool SMULH(Reg Rm, Reg Rn, Reg Rd) { + return false; + } + virtual bool UMADDL(Reg Rm, Reg Ra, Reg Rn, Reg Rd) { + return false; + } + virtual bool UMSUBL(Reg Rm, Reg Ra, Reg Rn, Reg Rd) { + return false; + } + virtual bool UMULH(Reg Rm, Reg Rn, Reg Rd) { + return false; + } + + // Data Processing - FP and SIMD - AES + virtual bool AESE(Vec Vn, Vec Vd) { + return false; + } + virtual bool AESD(Vec Vn, Vec Vd) { + return false; + } + virtual bool AESMC(Vec Vn, Vec Vd) { + return false; + } + virtual bool AESIMC(Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SHA + virtual bool SHA1C(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA1P(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA1M(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA1SU0(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA256H(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA256H2(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA256SU1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA1H(Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA1SU1(Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA256SU0(Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Scalar copy + virtual bool DUP_elt_1(Imm<5> imm5, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Scalar three + virtual bool FMULX_vec_1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMULX_vec_2(bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMEQ_reg_1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMEQ_reg_2(bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPS_1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPS_2(bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRSQRTS_1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRSQRTS_2(bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGE_reg_1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGE_reg_2(bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FACGE_1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FACGE_2(bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FABD_1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FABD_2(bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGT_reg_1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGT_reg_2(bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FACGT_1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FACGT_2(bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Two register misc FP16 + virtual bool FCVTNS_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTMS_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTAS_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool SCVTF_int_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGT_zero_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMEQ_zero_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMLT_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTPS_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZS_int_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPE_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPX_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTNU_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTMU_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTAU_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool UCVTF_int_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGE_zero_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMLE_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTPU_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZU_int_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FRSQRTE_1(Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Two register misc + virtual bool FCVTNS_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTMS_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTAS_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool SCVTF_int_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGT_zero_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMEQ_zero_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMLT_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTPS_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZS_int_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPE_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPX_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTNU_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTMU_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTAU_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool UCVTF_int_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGE_zero_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMLE_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTPU_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZU_int_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRSQRTE_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Scalar two register misc FP16 + virtual bool FCVTNS_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTMS_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTAS_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool SCVTF_int_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGT_zero_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMEQ_zero_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMLT_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTPS_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZS_int_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPE_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTNU_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTMU_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTAU_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool UCVTF_int_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGE_zero_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMLE_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTPU_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZU_int_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRSQRTE_3(bool Q, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Scalar two register misc + virtual bool FCVTNS_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTMS_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTAS_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool SCVTF_int_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGT_zero_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMEQ_zero_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMLT_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTPS_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZS_int_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPE_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTNU_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTMU_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTAU_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool UCVTF_int_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGE_zero_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMLE_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTPU_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZU_int_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRSQRTE_4(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Scalar three same extra + virtual bool SQRDMLAH_vec_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRDMLAH_vec_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRDMLSH_vec_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRDMLSH_vec_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Scalar two-register misc + virtual bool SUQADD_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQABS_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMGT_zero_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMEQ_zero_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMLT_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool ABS_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQXTN_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool USQADD_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQNEG_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMGE_zero_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMLE_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool NEG_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQXTUN_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQXTN_1(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTXN_1(bool sz, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Scalar pairwise + virtual bool ADDP_pair(Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXNMP_pair_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXNMP_pair_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FADDP_pair_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FADDP_pair_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXP_pair_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXP_pair_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINNMP_pair_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINNMP_pair_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINP_pair_1(Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINP_pair_2(bool sz, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Scalar three different + virtual bool SQDMLAL_vec_1(Imm<2> size, Reg Rm, Reg Rn, Vec Vd) { + return false; + } + virtual bool SQDMLSL_vec_1(Imm<2> size, Reg Rm, Reg Rn, Vec Vd) { + return false; + } + virtual bool SQDMULL_vec_1(Imm<2> size, Reg Rm, Reg Rn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Scalar three same + virtual bool SQADD_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSUB_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMGT_reg_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMGE_reg_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SSHL_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHL_reg_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SRSHL_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRSHL_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool ADD_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMTST_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQDMULH_vec_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQADD_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQSUB_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMHI_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMHS_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool USHL_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQSHL_reg_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool URSHL_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQRSHL_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SUB_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMEQ_reg_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRDMULH_vec_1(Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Scalar shift by immediate + virtual bool SSHR_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SSRA_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SRSHR_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SRSRA_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHL_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHL_imm_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHRN_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRSHRN_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SCVTF_fix_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZS_fix_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool USHR_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool USRA_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool URSHR_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool URSRA_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SRI_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SLI_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHLU_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQSHL_imm_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHRUN_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRSHRUN_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQSHRN_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQRSHRN_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool UCVTF_fix_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZU_fix_1(Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Scalar x indexed element + virtual bool SQDMLAL_elt_1(Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQDMLSL_elt_1(Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQDMULL_elt_1(Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQDMULH_elt_1(Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQRDMULH_elt_1(Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FMLA_elt_1(Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLA_elt_2(bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLS_elt_1(Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLS_elt_2(bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMUL_elt_1(Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMUL_elt_2(bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRDMLAH_elt_1(Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQRDMLSH_elt_1(Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FMULX_elt_1(Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMULX_elt_2(bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Table Lookup + virtual bool TBL(bool Q, Vec Vm, Imm<2> len, size_t Vn, Vec Vd) { + return false; + } + virtual bool TBX(bool Q, Vec Vm, Imm<2> len, size_t Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Permute + virtual bool UZP1(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool TRN1(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool ZIP1(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UZP2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool TRN2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool ZIP2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Extract + virtual bool EXT(bool Q, Vec Vm, Imm<4> imm4, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Copy + virtual bool DUP_elt_2(bool Q, Imm<5> imm5, Vec Vn, Vec Vd) { + return false; + } + virtual bool DUP_gen(bool Q, Imm<5> imm5, Reg Rn, Vec Vd) { + return false; + } + virtual bool SMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) { + return false; + } + virtual bool UMOV(bool Q, Imm<5> imm5, Vec Vn, Reg Rd) { + return false; + } + virtual bool INS_gen(Imm<5> imm5, Reg Rn, Vec Vd) { + return false; + } + virtual bool INS_elt(Imm<5> imm5, Imm<4> imm4, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Three same + virtual bool FMULX_vec_3(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMEQ_reg_3(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPS_3(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRSQRTS_3(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGE_reg_3(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FACGE_3(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FABD_3(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGT_reg_3(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FACGT_3(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXNM_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLA_vec_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FADD_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAX_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINNM_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLS_vec_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FSUB_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMIN_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXNMP_vec_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FADDP_vec_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMUL_vec_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXP_vec_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FDIV_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINNMP_vec_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINP_vec_1(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Three same extra + virtual bool SDOT_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UDOT_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMLA_vec(bool Q, Imm<2> size, Vec Vm, Imm<2> rot, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCADD_vec(bool Q, Imm<2> size, Vec Vm, Imm<1> rot, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Two register misc + virtual bool REV64_asimd(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool REV16_asimd(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SADDLP(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CLS_asimd(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CNT(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SADALP(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool XTN(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTN(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTL(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool URECPE(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool REV32_asimd(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool UADDLP(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CLZ_asimd(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool UADALP(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHLL(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool NOT(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool RBIT_asimd(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool URSQRTE(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool SUQADD_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQABS_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMGT_zero_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMEQ_zero_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMLT_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool ABS_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQXTN_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool USQADD_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQNEG_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMGE_zero_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMLE_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool NEG_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQXTUN_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQXTN_2(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTXN_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTN_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTN_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTM_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTM_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FABS_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FABS_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTP_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTP_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTZ_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTZ_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTA_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTA_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTX_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTX_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FNEG_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FNEG_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTI_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTI_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FSQRT_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FSQRT_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINT32X_1(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINT64X_1(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINT32Z_1(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINT64Z_1(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD across lanes + virtual bool SADDLV(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SMAXV(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool SMINV(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool ADDV(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXNMV_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXNMV_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXV_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXV_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINNMV_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINNMV_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINV_1(bool Q, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINV_2(bool Q, bool sz, Vec Vn, Vec Vd) { + return false; + } + virtual bool UADDLV(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool UMAXV(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + virtual bool UMINV(bool Q, Imm<2> size, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD three different + virtual bool SADDL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SADDW(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SSUBL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SSUBW(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool ADDHN(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SABAL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SUBHN(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SABDL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SMLAL_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SMLSL_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SMULL_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool PMULL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UADDL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UADDW(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool USUBL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool USUBW(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool RADDHN(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UABAL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool RSUBHN(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UABDL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UMLAL_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UMLSL_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UMULL_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQDMLAL_vec_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQDMLSL_vec_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQDMULL_vec_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD three same + virtual bool SHADD(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SRHADD(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHSUB(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SMAX(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SMIN(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SABD(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SABA(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool MLA_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool MUL_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SMAXP(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SMINP(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool ADDP_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLAL_vec_1(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLAL_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool AND_asimd(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool BIC_asimd_reg(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLSL_vec_1(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLSL_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool ORR_asimd_reg(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool ORN_asimd(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UHADD(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool URHADD(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UHSUB(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UMAX(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UMIN(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UABD(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UABA(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool MLS_vec(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool PMUL(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UMAXP(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UMINP(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool EOR_asimd(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool BSL(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool BIT(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool BIF(bool Q, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXNM_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLA_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FADD_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAX_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINNM_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLS_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FSUB_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMIN_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXNMP_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FADDP_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMUL_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXP_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FDIV_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINNMP_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINP_vec_2(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMULX_vec_4(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMEQ_reg_4(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRECPS_4(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRSQRTS_4(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGE_reg_4(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FACGE_4(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FABD_4(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCMGT_reg_4(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FACGT_4(bool Q, bool sz, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQADD_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSUB_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMGT_reg_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMGE_reg_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SSHL_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHL_reg_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SRSHL_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRSHL_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool ADD_vector(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMTST_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQDMULH_vec_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQADD_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQSUB_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMHI_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMHS_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool USHL_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQSHL_reg_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool URSHL_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQRSHL_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SUB_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool CMEQ_reg_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRDMULH_vec_2(bool Q, Imm<2> size, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD modified immediate + virtual bool MOVI(bool Q, bool op, Imm<1> a, Imm<1> b, Imm<1> c, Imm<4> cmode, Imm<1> d, + Imm<1> e, Imm<1> f, Imm<1> g, Imm<1> h, Vec Vd) { + return false; + } + virtual bool FMOV_2(bool Q, bool op, Imm<1> a, Imm<1> b, Imm<1> c, Imm<1> d, Imm<1> e, Imm<1> f, + Imm<1> g, Imm<1> h, Vec Vd) { + return false; + } + virtual bool FMOV_3(bool Q, Imm<1> a, Imm<1> b, Imm<1> c, Imm<1> d, Imm<1> e, Imm<1> f, + Imm<1> g, Imm<1> h, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD Shift by immediate + virtual bool SSHR_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SSRA_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SRSHR_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SRSRA_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHL_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHL_imm_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHRN(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool RSHRN(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHRN_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRSHRN_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SSHLL(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SCVTF_fix_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZS_fix_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool USHR_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool USRA_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool URSHR_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool URSRA_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SRI_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SLI_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHLU_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQSHL_imm_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQSHRUN_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRSHRUN_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQSHRN_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool UQRSHRN_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool USHLL(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool UCVTF_fix_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVTZU_fix_2(bool Q, Imm<4> immh, Imm<3> immb, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SIMD vector x indexed element + virtual bool SMLAL_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQDMLAL_elt_2(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, + Vec Vn, Vec Vd) { + return false; + } + virtual bool SMLSL_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQDMLSL_elt_2(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, + Vec Vn, Vec Vd) { + return false; + } + virtual bool MUL_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SMULL_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vm, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQDMULL_elt_2(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, + Vec Vn, Vec Vd) { + return false; + } + virtual bool SQDMULH_elt_2(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, + Vec Vn, Vec Vd) { + return false; + } + virtual bool SQRDMULH_elt_2(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, + Vec Vn, Vec Vd) { + return false; + } + virtual bool SDOT_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FMLA_elt_3(bool Q, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLA_elt_4(bool Q, bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FMLS_elt_3(bool Q, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMLS_elt_4(bool Q, bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FMUL_elt_3(bool Q, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMUL_elt_4(bool Q, bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FMLAL_elt_1(bool Q, bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FMLAL_elt_2(bool Q, bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FMLSL_elt_1(bool Q, bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FMLSL_elt_2(bool Q, bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool MLA_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool UMLAL_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool MLS_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool UMLSL_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool UMULL_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQRDMLAH_elt_2(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, + Vec Vn, Vec Vd) { + return false; + } + virtual bool UDOT_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool SQRDMLSH_elt_2(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, + Vec Vn, Vec Vd) { + return false; + } + virtual bool FMULX_elt_3(bool Q, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMULX_elt_4(bool Q, bool sz, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<1> H, Vec Vn, + Vec Vd) { + return false; + } + virtual bool FCMLA_elt(bool Q, Imm<2> size, Imm<1> L, Imm<1> M, Imm<4> Vmlo, Imm<2> rot, + Imm<1> H, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Cryptographic three register + virtual bool SM3TT1A(Vec Vm, Imm<2> imm2, Vec Vn, Vec Vd) { + return false; + } + virtual bool SM3TT1B(Vec Vm, Imm<2> imm2, Vec Vn, Vec Vd) { + return false; + } + virtual bool SM3TT2A(Vec Vm, Imm<2> imm2, Vec Vn, Vec Vd) { + return false; + } + virtual bool SM3TT2B(Vec Vm, Imm<2> imm2, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SHA512 three register + virtual bool SHA512H(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA512H2(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SHA512SU1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool RAX1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool XAR(Vec Vm, Imm<6> imm6, Vec Vn, Vec Vd) { + return false; + } + virtual bool SM3PARTW1(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SM3PARTW2(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool SM4EKEY(Vec Vm, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Cryptographic four register + virtual bool EOR3(Vec Vm, Vec Va, Vec Vn, Vec Vd) { + return false; + } + virtual bool BCAX(Vec Vm, Vec Va, Vec Vn, Vec Vd) { + return false; + } + virtual bool SM3SS1(Vec Vm, Vec Va, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - SHA512 two register + virtual bool SHA512SU0(Vec Vn, Vec Vd) { + return false; + } + virtual bool SM4E(Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Conversion between floating point and fixed point + virtual bool SCVTF_float_fix(bool sf, Imm<2> type, Imm<6> scale, Reg Rn, Vec Vd) { + return false; + } + virtual bool UCVTF_float_fix(bool sf, Imm<2> type, Imm<6> scale, Reg Rn, Vec Vd) { + return false; + } + virtual bool FCVTZS_float_fix(bool sf, Imm<2> type, Imm<6> scale, Vec Vn, Reg Rd) { + return false; + } + virtual bool FCVTZU_float_fix(bool sf, Imm<2> type, Imm<6> scale, Vec Vn, Reg Rd) { + return false; + } + + // Data Processing - FP and SIMD - Conversion between floating point and integer + virtual bool FCVTNS_float(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool FCVTNU_float(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool SCVTF_float_int(bool sf, Imm<2> type, Reg Rn, Vec Vd) { + return false; + } + virtual bool UCVTF_float_int(bool sf, Imm<2> type, Reg Rn, Vec Vd) { + return false; + } + virtual bool FCVTAS_float(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool FCVTAU_float(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool FMOV_float_gen(bool sf, Imm<2> type, Imm<1> rmode_0, Imm<1> opc_0, size_t n, + size_t d) { + return false; + } + virtual bool FCVTPS_float(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool FCVTPU_float(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool FCVTMS_float(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool FCVTMU_float(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool FCVTZS_float_int(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool FCVTZU_float_int(bool sf, Imm<2> type, Vec Vn, Reg Rd) { + return false; + } + virtual bool FJCVTZS(Vec Vn, Reg Rd) { + return false; + } + + // Data Processing - FP and SIMD - Floating point data processing + virtual bool FMOV_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FABS_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FNEG_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FSQRT_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FCVT_float(Imm<2> type, Imm<2> opc, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTN_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTP_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTM_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTZ_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTA_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTX_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINTI_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINT32X_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINT64X_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINT32Z_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + virtual bool FRINT64Z_float(Imm<2> type, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Floating point compare + virtual bool FCMP_float(Imm<2> type, Vec Vm, Vec Vn, bool cmp_with_zero) { + return false; + } + virtual bool FCMPE_float(Imm<2> type, Vec Vm, Vec Vn, bool cmp_with_zero) { + return false; + } + + // Data Processing - FP and SIMD - Floating point immediate + virtual bool FMOV_float_imm(Imm<2> type, Imm<8> imm8, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Floating point conditional compare + virtual bool FCCMP_float(Imm<2> type, Vec Vm, Cond cond, Vec Vn, Imm<4> nzcv) { + return false; + } + virtual bool FCCMPE_float(Imm<2> type, Vec Vm, Cond cond, Vec Vn, Imm<4> nzcv) { + return false; + } + + // Data Processing - FP and SIMD - Floating point data processing two register + virtual bool FMUL_float(Imm<2> type, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FDIV_float(Imm<2> type, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FADD_float(Imm<2> type, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FSUB_float(Imm<2> type, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAX_float(Imm<2> type, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMIN_float(Imm<2> type, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMAXNM_float(Imm<2> type, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMINNM_float(Imm<2> type, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + virtual bool FNMUL_float(Imm<2> type, Vec Vm, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Floating point conditional select + virtual bool FCSEL_float(Imm<2> type, Vec Vm, Cond cond, Vec Vn, Vec Vd) { + return false; + } + + // Data Processing - FP and SIMD - Floating point data processing three register + virtual bool FMADD_float(Imm<2> type, Vec Vm, Vec Va, Vec Vn, Vec Vd) { + return false; + } + virtual bool FMSUB_float(Imm<2> type, Vec Vm, Vec Va, Vec Vn, Vec Vd) { + return false; + } + virtual bool FNMADD_float(Imm<2> type, Vec Vm, Vec Va, Vec Vn, Vec Vd) { + return false; + } + virtual bool FNMSUB_float(Imm<2> type, Vec Vm, Vec Va, Vec Vn, Vec Vd) { + return false; + } +}; + +} // namespace Core diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 639842401..b7105c8ff 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -201,8 +201,6 @@ std::string VfsFile::GetFullPath() const { VirtualFile VfsDirectory::GetFileRelative(std::string_view path) const { auto vec = Common::FS::SplitPathComponents(path); - vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), - vec.end()); if (vec.empty()) { return nullptr; } @@ -237,8 +235,6 @@ VirtualFile VfsDirectory::GetFileAbsolute(std::string_view path) const { VirtualDir VfsDirectory::GetDirectoryRelative(std::string_view path) const { auto vec = Common::FS::SplitPathComponents(path); - vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), - vec.end()); if (vec.empty()) { // TODO(DarkLordZach): Return this directory if path is '/' or similar. Can't currently // because of const-ness @@ -303,8 +299,6 @@ std::size_t VfsDirectory::GetSize() const { VirtualFile VfsDirectory::CreateFileRelative(std::string_view path) { auto vec = Common::FS::SplitPathComponents(path); - vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), - vec.end()); if (vec.empty()) { return nullptr; } @@ -334,8 +328,6 @@ VirtualFile VfsDirectory::CreateFileAbsolute(std::string_view path) { VirtualDir VfsDirectory::CreateDirectoryRelative(std::string_view path) { auto vec = Common::FS::SplitPathComponents(path); - vec.erase(std::remove_if(vec.begin(), vec.end(), [](const auto& str) { return str.empty(); }), - vec.end()); if (vec.empty()) { return nullptr; } diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 1c706e4d8..cd9b79786 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -268,7 +268,7 @@ void RealVfsFilesystem::RemoveReferenceFromListLocked(FileReference& reference) RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, const std::string& path_, Mode perms_, std::optional<u64> size_) : base(base_), reference(std::move(reference_)), path(path_), - parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)), + parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponentsCopy(path_)), size(size_), perms(perms_) {} RealVfsFile::~RealVfsFile() { @@ -276,7 +276,7 @@ RealVfsFile::~RealVfsFile() { } std::string RealVfsFile::GetName() const { - return path_components.back(); + return path_components.empty() ? "" : std::string(path_components.back()); } std::size_t RealVfsFile::GetSize() const { @@ -375,7 +375,7 @@ std::vector<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& path_, Mode perms_) : base(base_), path(FS::RemoveTrailingSlash(path_)), parent_path(FS::GetParentPath(path)), - path_components(FS::SplitPathComponents(path)), perms(perms_) { + path_components(FS::SplitPathComponentsCopy(path)), perms(perms_) { if (!FS::Exists(path) && True(perms & Mode::Write)) { void(FS::CreateDirs(path)); } @@ -464,7 +464,7 @@ bool RealVfsDirectory::IsReadable() const { } std::string RealVfsDirectory::GetName() const { - return path_components.back(); + return path_components.empty() ? "" : std::string(path_components.back()); } VirtualDir RealVfsDirectory::GetParentDirectory() const { diff --git a/src/core/hid/input_interpreter.cpp b/src/core/hid/input_interpreter.cpp index a6bdd28f2..072f38a68 100644 --- a/src/core/hid/input_interpreter.cpp +++ b/src/core/hid/input_interpreter.cpp @@ -20,6 +20,9 @@ InputInterpreter::InputInterpreter(Core::System& system) InputInterpreter::~InputInterpreter() = default; void InputInterpreter::PollInput() { + if (npad == nullptr) { + return; + } const auto button_state = npad->GetAndResetPressState(); previous_index = current_index; diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp index 0a973ec8c..d6bd27296 100644 --- a/src/core/hle/kernel/k_memory_manager.cpp +++ b/src/core/hle/kernel/k_memory_manager.cpp @@ -421,8 +421,9 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32 } else { // Set all the allocated memory. for (const auto& block : *out) { - std::memset(m_system.DeviceMemory().GetPointer<void>(block.GetAddress()), fill_pattern, - block.GetSize()); + m_system.DeviceMemory().buffer.ClearBackingRegion(GetInteger(block.GetAddress()) - + Core::DramMemoryMap::Base, + block.GetSize(), fill_pattern); } } diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp index 4c416d809..423289145 100644 --- a/src/core/hle/kernel/k_page_table_base.cpp +++ b/src/core/hle/kernel/k_page_table_base.cpp @@ -81,6 +81,11 @@ void InvalidateInstructionCache(KernelCore& kernel, AddressType addr, u64 size) } } +void ClearBackingRegion(Core::System& system, KPhysicalAddress addr, u64 size, u32 fill_value) { + system.DeviceMemory().buffer.ClearBackingRegion(GetInteger(addr) - Core::DramMemoryMap::Base, + size, fill_value); +} + template <typename AddressType> Result InvalidateDataCache(AddressType addr, u64 size) { R_SUCCEED(); @@ -1363,8 +1368,7 @@ Result KPageTableBase::MapInsecureMemory(KProcessAddress address, size_t size) { // Clear all the newly allocated pages. for (const auto& it : pg) { - std::memset(GetHeapVirtualPointer(m_kernel, it.GetAddress()), - static_cast<u32>(m_heap_fill_value), it.GetSize()); + ClearBackingRegion(m_system, it.GetAddress(), it.GetSize(), m_heap_fill_value); } // Lock the table. @@ -1570,8 +1574,7 @@ Result KPageTableBase::AllocateAndMapPagesImpl(PageLinkedList* page_list, KProce // Clear all pages. for (const auto& it : pg) { - std::memset(GetHeapVirtualPointer(m_kernel, it.GetAddress()), - static_cast<u32>(m_heap_fill_value), it.GetSize()); + ClearBackingRegion(m_system, it.GetAddress(), it.GetSize(), m_heap_fill_value); } // Map the pages. @@ -2159,8 +2162,7 @@ Result KPageTableBase::SetHeapSize(KProcessAddress* out, size_t size) { // Clear all the newly allocated pages. for (const auto& it : pg) { - std::memset(GetHeapVirtualPointer(m_kernel, it.GetAddress()), m_heap_fill_value, - it.GetSize()); + ClearBackingRegion(m_system, it.GetAddress(), it.GetSize(), m_heap_fill_value); } // Map the pages. diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 8cb05ca0b..e479dacde 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -135,7 +135,6 @@ struct KernelCore::Impl { obj = nullptr; } }; - CleanupObject(hid_shared_mem); CleanupObject(font_shared_mem); CleanupObject(irs_shared_mem); CleanupObject(time_shared_mem); @@ -744,22 +743,16 @@ struct KernelCore::Impl { void InitializeHackSharedMemory(KernelCore& kernel) { // Setup memory regions for emulated processes // TODO(bunnei): These should not be hardcoded regions initialized within the kernel - constexpr std::size_t hid_size{0x40000}; constexpr std::size_t font_size{0x1100000}; constexpr std::size_t irs_size{0x8000}; constexpr std::size_t time_size{0x1000}; constexpr std::size_t hidbus_size{0x1000}; - hid_shared_mem = KSharedMemory::Create(system.Kernel()); font_shared_mem = KSharedMemory::Create(system.Kernel()); irs_shared_mem = KSharedMemory::Create(system.Kernel()); time_shared_mem = KSharedMemory::Create(system.Kernel()); hidbus_shared_mem = KSharedMemory::Create(system.Kernel()); - hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, - Svc::MemoryPermission::Read, hid_size); - KSharedMemory::Register(kernel, hid_shared_mem); - font_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, Svc::MemoryPermission::Read, font_size); KSharedMemory::Register(kernel, font_shared_mem); @@ -1190,14 +1183,6 @@ const KSystemResource& KernelCore::GetSystemSystemResource() const { return *impl->sys_system_resource; } -Kernel::KSharedMemory& KernelCore::GetHidSharedMem() { - return *impl->hid_shared_mem; -} - -const Kernel::KSharedMemory& KernelCore::GetHidSharedMem() const { - return *impl->hid_shared_mem; -} - Kernel::KSharedMemory& KernelCore::GetFontSharedMem() { return *impl->font_shared_mem; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 69b5bbd6c..78c88902c 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -239,12 +239,6 @@ public: /// Gets the system resource manager. const KSystemResource& GetSystemSystemResource() const; - /// Gets the shared memory object for HID services. - Kernel::KSharedMemory& GetHidSharedMem(); - - /// Gets the shared memory object for HID services. - const Kernel::KSharedMemory& GetHidSharedMem() const; - /// Gets the shared memory object for font services. Kernel::KSharedMemory& GetFontSharedMem(); diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 2f09cade5..23e56c77a 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -359,7 +359,7 @@ private: void GetActiveChannelCount(HLERequestContext& ctx) { const auto& sink{system.AudioCore().GetOutputSink()}; - u32 channel_count{sink.GetDeviceChannels()}; + u32 channel_count{sink.GetSystemChannels()}; LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", channel_count); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 508db7360..780f8c74d 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -104,11 +104,7 @@ Result VfsDirectoryServiceWrapper::CreateDirectory(const std::string& path_) con const auto components = Common::FS::SplitPathComponents(path); std::string relative_path; for (const auto& component : components) { - // Skip empty path components - if (component.empty()) { - continue; - } - relative_path = Common::FS::SanitizePath(relative_path + '/' + component); + relative_path = Common::FS::SanitizePath(fmt::format("{}/{}", relative_path, component)); auto new_dir = backing->CreateSubdirectory(relative_path); if (new_dir == nullptr) { // TODO(DarkLordZach): Find a better error code for this diff --git a/src/core/hle/service/hid/controllers/applet_resource.cpp b/src/core/hle/service/hid/controllers/applet_resource.cpp index 435b86233..c8e74c764 100644 --- a/src/core/hle/service/hid/controllers/applet_resource.cpp +++ b/src/core/hle/service/hid/controllers/applet_resource.cpp @@ -4,6 +4,7 @@ #include "core/core.h" #include "core/hle/kernel/k_shared_memory.h" #include "core/hle/service/hid/controllers/applet_resource.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" #include "core/hle/service/hid/errors.h" namespace Service::HID { @@ -23,11 +24,24 @@ Result AppletResource::CreateAppletResource(u64 aruid) { return ResultAruidAlreadyRegistered; } - // TODO: Here shared memory is created for the process we don't quite emulate this part so - // obtain this pointer from system - auto& shared_memory = system.Kernel().GetHidSharedMem(); + auto& shared_memory = shared_memory_holder[index]; + if (!shared_memory.IsMapped()) { + const Result result = shared_memory.Initialize(system); + if (result.IsError()) { + return result; + } + if (shared_memory.GetAddress() == nullptr) { + shared_memory.Finalize(); + return ResultSharedMemoryNotInitialized; + } + } + + auto* shared_memory_format = shared_memory.GetAddress(); + if (shared_memory_format != nullptr) { + shared_memory_format->Initialize(); + } - data[index].shared_memory_handle = &shared_memory; + data[index].shared_memory_format = shared_memory_format; data[index].flag.is_assigned.Assign(true); // TODO: InitializeSixAxisControllerConfig(false); active_aruid = aruid; @@ -94,7 +108,7 @@ void AppletResource::UnregisterAppletResourceUserId(u64 aruid) { if (index < AruidIndexMax) { if (data[index].flag.is_assigned) { - data[index].shared_memory_handle = nullptr; + data[index].shared_memory_format = nullptr; data[index].flag.is_assigned.Assign(false); } } @@ -120,7 +134,7 @@ void AppletResource::FreeAppletResourceId(u64 aruid) { auto& aruid_data = data[index]; if (aruid_data.flag.is_assigned) { - aruid_data.shared_memory_handle = nullptr; + aruid_data.shared_memory_format = nullptr; aruid_data.flag.is_assigned.Assign(false); } } @@ -135,7 +149,18 @@ Result AppletResource::GetSharedMemoryHandle(Kernel::KSharedMemory** out_handle, return ResultAruidNotRegistered; } - *out_handle = data[index].shared_memory_handle; + *out_handle = shared_memory_holder[index].GetHandle(); + return ResultSuccess; +} + +Result AppletResource::GetSharedMemoryFormat(SharedMemoryFormat** out_shared_memory_format, + u64 aruid) { + u64 index = GetIndexFromAruid(aruid); + if (index >= AruidIndexMax) { + return ResultAruidNotRegistered; + } + + *out_shared_memory_format = data[index].shared_memory_format; return ResultSuccess; } diff --git a/src/core/hle/service/hid/controllers/applet_resource.h b/src/core/hle/service/hid/controllers/applet_resource.h index 62137db13..e7991f93a 100644 --- a/src/core/hle/service/hid/controllers/applet_resource.h +++ b/src/core/hle/service/hid/controllers/applet_resource.h @@ -8,6 +8,7 @@ #include "common/bit_field.h" #include "common/common_types.h" #include "core/hle/result.h" +#include "core/hle/service/hid/controllers/shared_memory_holder.h" namespace Core { class System; @@ -18,6 +19,8 @@ class KSharedMemory; } namespace Service::HID { +struct SharedMemoryFormat; + class AppletResource { public: explicit AppletResource(Core::System& system_); @@ -32,6 +35,7 @@ public: u64 GetActiveAruid(); Result GetSharedMemoryHandle(Kernel::KSharedMemory** out_handle, u64 aruid); + Result GetSharedMemoryFormat(SharedMemoryFormat** out_shared_memory_format, u64 aruid); u64 GetIndexFromAruid(u64 aruid); @@ -80,12 +84,13 @@ private: struct AruidData { DataStatusFlag flag{}; u64 aruid{}; - Kernel::KSharedMemory* shared_memory_handle{nullptr}; + SharedMemoryFormat* shared_memory_format{nullptr}; }; u64 active_aruid{}; AruidRegisterList registration_list{}; std::array<AruidData, AruidIndexMax> data{}; + std::array<SharedMemoryHolder, AruidIndexMax> shared_memory_holder{}; s32 ref_counter{}; Core::System& system; diff --git a/src/core/hle/service/hid/controllers/console_six_axis.cpp b/src/core/hle/service/hid/controllers/console_six_axis.cpp index b2bf1d78d..3961d2b5f 100644 --- a/src/core/hle/service/hid/controllers/console_six_axis.cpp +++ b/src/core/hle/service/hid/controllers/console_six_axis.cpp @@ -1,23 +1,18 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "core/core.h" #include "core/core_timing.h" #include "core/hid/emulated_console.h" #include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/console_six_axis.h" -#include "core/memory.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" namespace Service::HID { -constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C200; -ConsoleSixAxis::ConsoleSixAxis(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) - : ControllerBase{hid_core_} { +ConsoleSixAxis::ConsoleSixAxis(Core::HID::HIDCore& hid_core_, + ConsoleSixAxisSensorSharedMemoryFormat& console_shared_memory) + : ControllerBase{hid_core_}, shared_memory{console_shared_memory} { console = hid_core.GetEmulatedConsole(); - static_assert(SHARED_MEMORY_OFFSET + sizeof(ConsoleSharedMemory) < shared_memory_size, - "ConsoleSharedMemory is bigger than the shared memory"); - shared_memory = std::construct_at( - reinterpret_cast<ConsoleSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET)); } ConsoleSixAxis::~ConsoleSixAxis() = default; @@ -33,10 +28,10 @@ void ConsoleSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) { const auto motion_status = console->GetMotion(); - shared_memory->sampling_number++; - shared_memory->is_seven_six_axis_sensor_at_rest = motion_status.is_at_rest; - shared_memory->verticalization_error = motion_status.verticalization_error; - shared_memory->gyro_bias = motion_status.gyro_bias; + shared_memory.sampling_number++; + shared_memory.is_seven_six_axis_sensor_at_rest = motion_status.is_at_rest; + shared_memory.verticalization_error = motion_status.verticalization_error; + shared_memory.gyro_bias = motion_status.gyro_bias; } } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/console_six_axis.h b/src/core/hle/service/hid/controllers/console_six_axis.h index 5b7c6a29a..3d1c9ce23 100644 --- a/src/core/hle/service/hid/controllers/console_six_axis.h +++ b/src/core/hle/service/hid/controllers/console_six_axis.h @@ -3,7 +3,6 @@ #pragma once -#include "common/vector_math.h" #include "core/hle/service/hid/controllers/controller_base.h" namespace Core::HID { @@ -11,9 +10,12 @@ class EmulatedConsole; } // namespace Core::HID namespace Service::HID { +struct ConsoleSixAxisSensorSharedMemoryFormat; + class ConsoleSixAxis final : public ControllerBase { public: - explicit ConsoleSixAxis(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); + explicit ConsoleSixAxis(Core::HID::HIDCore& hid_core_, + ConsoleSixAxisSensorSharedMemoryFormat& console_shared_memory); ~ConsoleSixAxis() override; // Called when the controller is initialized @@ -26,18 +28,7 @@ public: void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; private: - // This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat - struct ConsoleSharedMemory { - u64 sampling_number{}; - bool is_seven_six_axis_sensor_at_rest{}; - INSERT_PADDING_BYTES(3); // padding - f32 verticalization_error{}; - Common::Vec3f gyro_bias{}; - INSERT_PADDING_BYTES(4); // padding - }; - static_assert(sizeof(ConsoleSharedMemory) == 0x20, "ConsoleSharedMemory is an invalid size"); - - ConsoleSharedMemory* shared_memory = nullptr; + ConsoleSixAxisSensorSharedMemoryFormat& shared_memory; Core::HID::EmulatedConsole* console = nullptr; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h index 9a44ee41e..4326c7821 100644 --- a/src/core/hle/service/hid/controllers/controller_base.h +++ b/src/core/hle/service/hid/controllers/controller_base.h @@ -39,9 +39,6 @@ public: bool IsControllerActivated() const; - static const std::size_t hid_entry_count = 17; - static const std::size_t shared_memory_size = 0x40000; - protected: bool is_activated{false}; diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp index 9de19ebfc..7d2370b4f 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.cpp +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -1,24 +1,19 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <cstring> -#include "common/common_types.h" #include "common/settings.h" #include "core/core_timing.h" #include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" #include "core/hid/hid_types.h" #include "core/hle/service/hid/controllers/debug_pad.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" namespace Service::HID { -constexpr std::size_t SHARED_MEMORY_OFFSET = 0x00000; - -DebugPad::DebugPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) - : ControllerBase{hid_core_} { - static_assert(SHARED_MEMORY_OFFSET + sizeof(DebugPadSharedMemory) < shared_memory_size, - "DebugPadSharedMemory is bigger than the shared memory"); - shared_memory = std::construct_at( - reinterpret_cast<DebugPadSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET)); + +DebugPad::DebugPad(Core::HID::HIDCore& hid_core_, + DebugPadSharedMemoryFormat& debug_pad_shared_memory) + : ControllerBase{hid_core_}, shared_memory{debug_pad_shared_memory} { controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other); } @@ -30,12 +25,12 @@ void DebugPad::OnRelease() {} void DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { if (!IsControllerActivated()) { - shared_memory->debug_pad_lifo.buffer_count = 0; - shared_memory->debug_pad_lifo.buffer_tail = 0; + shared_memory.debug_pad_lifo.buffer_count = 0; + shared_memory.debug_pad_lifo.buffer_tail = 0; return; } - const auto& last_entry = shared_memory->debug_pad_lifo.ReadCurrentEntry().state; + const auto& last_entry = shared_memory.debug_pad_lifo.ReadCurrentEntry().state; next_state.sampling_number = last_entry.sampling_number + 1; if (Settings::values.debug_pad_enabled) { @@ -49,7 +44,7 @@ void DebugPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { next_state.r_stick = stick_state.right; } - shared_memory->debug_pad_lifo.WriteNextEntry(next_state); + shared_memory.debug_pad_lifo.WriteNextEntry(next_state); } } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h index 5566dba77..8ab29eca8 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.h +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -3,21 +3,24 @@ #pragma once -#include "common/bit_field.h" -#include "common/common_types.h" #include "core/hle/service/hid/controllers/controller_base.h" -#include "core/hle/service/hid/ring_lifo.h" +#include "core/hle/service/hid/controllers/types/debug_pad_types.h" namespace Core::HID { -class EmulatedController; -struct DebugPadButton; -struct AnalogStickState; -} // namespace Core::HID +class HIDCore; +} + +namespace Core::Timing { +class CoreTiming; +} namespace Service::HID { +struct DebugPadSharedMemoryFormat; + class DebugPad final : public ControllerBase { public: - explicit DebugPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); + explicit DebugPad(Core::HID::HIDCore& hid_core_, + DebugPadSharedMemoryFormat& debug_pad_shared_memory); ~DebugPad() override; // Called when the controller is initialized @@ -30,35 +33,8 @@ public: void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; private: - // This is nn::hid::DebugPadAttribute - struct DebugPadAttribute { - union { - u32 raw{}; - BitField<0, 1, u32> connected; - }; - }; - static_assert(sizeof(DebugPadAttribute) == 0x4, "DebugPadAttribute is an invalid size"); - - // This is nn::hid::DebugPadState - struct DebugPadState { - s64 sampling_number{}; - DebugPadAttribute attribute{}; - Core::HID::DebugPadButton pad_state{}; - Core::HID::AnalogStickState r_stick{}; - Core::HID::AnalogStickState l_stick{}; - }; - static_assert(sizeof(DebugPadState) == 0x20, "DebugPadState is an invalid state"); - - struct DebugPadSharedMemory { - // This is nn::hid::detail::DebugPadLifo - Lifo<DebugPadState, hid_entry_count> debug_pad_lifo{}; - static_assert(sizeof(debug_pad_lifo) == 0x2C8, "debug_pad_lifo is an invalid size"); - INSERT_PADDING_WORDS(0x4E); - }; - static_assert(sizeof(DebugPadSharedMemory) == 0x400, "DebugPadSharedMemory is an invalid size"); - DebugPadState next_state{}; - DebugPadSharedMemory* shared_memory = nullptr; + DebugPadSharedMemoryFormat& shared_memory; Core::HID::EmulatedController* controller = nullptr; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index 59b2ec73c..f658005f6 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp @@ -1,17 +1,15 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "common/logging/log.h" #include "common/math_util.h" #include "common/settings.h" -#include "core/core_timing.h" #include "core/frontend/emu_window.h" +#include "core/hid/emulated_console.h" #include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/gesture.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" namespace Service::HID { -constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00; - // HW is around 700, value is set to 400 to make it easier to trigger with mouse constexpr f32 swipe_threshold = 400.0f; // Threshold in pixels/s constexpr f32 angle_threshold = 0.015f; // Threshold in radians @@ -23,19 +21,15 @@ constexpr f32 Square(s32 num) { return static_cast<f32>(num * num); } -Gesture::Gesture(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) - : ControllerBase(hid_core_) { - static_assert(SHARED_MEMORY_OFFSET + sizeof(GestureSharedMemory) < shared_memory_size, - "GestureSharedMemory is bigger than the shared memory"); - shared_memory = std::construct_at( - reinterpret_cast<GestureSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET)); +Gesture::Gesture(Core::HID::HIDCore& hid_core_, GestureSharedMemoryFormat& gesture_shared_memory) + : ControllerBase(hid_core_), shared_memory{gesture_shared_memory} { console = hid_core.GetEmulatedConsole(); } Gesture::~Gesture() = default; void Gesture::OnInit() { - shared_memory->gesture_lifo.buffer_count = 0; - shared_memory->gesture_lifo.buffer_tail = 0; + shared_memory.gesture_lifo.buffer_count = 0; + shared_memory.gesture_lifo.buffer_tail = 0; force_update = true; } @@ -43,8 +37,8 @@ void Gesture::OnRelease() {} void Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) { if (!IsControllerActivated()) { - shared_memory->gesture_lifo.buffer_count = 0; - shared_memory->gesture_lifo.buffer_tail = 0; + shared_memory.gesture_lifo.buffer_count = 0; + shared_memory.gesture_lifo.buffer_tail = 0; return; } @@ -52,7 +46,7 @@ void Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) { GestureProperties gesture = GetGestureProperties(); f32 time_difference = - static_cast<f32>(shared_memory->gesture_lifo.timestamp - last_update_timestamp) / + static_cast<f32>(shared_memory.gesture_lifo.timestamp - last_update_timestamp) / (1000 * 1000 * 1000); // Only update if necessary @@ -60,7 +54,7 @@ void Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) { return; } - last_update_timestamp = shared_memory->gesture_lifo.timestamp; + last_update_timestamp = shared_memory.gesture_lifo.timestamp; UpdateGestureSharedMemory(gesture, time_difference); } @@ -103,7 +97,7 @@ void Gesture::UpdateGestureSharedMemory(GestureProperties& gesture, f32 time_dif GestureType type = GestureType::Idle; GestureAttribute attributes{}; - const auto& last_entry = shared_memory->gesture_lifo.ReadCurrentEntry().state; + const auto& last_entry = shared_memory.gesture_lifo.ReadCurrentEntry().state; // Reset next state to default next_state.sampling_number = last_entry.sampling_number + 1; @@ -133,7 +127,7 @@ void Gesture::UpdateGestureSharedMemory(GestureProperties& gesture, f32 time_dif next_state.points = gesture.points; last_gesture = gesture; - shared_memory->gesture_lifo.WriteNextEntry(next_state); + shared_memory.gesture_lifo.WriteNextEntry(next_state); } void Gesture::NewGesture(GestureProperties& gesture, GestureType& type, @@ -305,11 +299,11 @@ void Gesture::SetSwipeEvent(GestureProperties& gesture, GestureProperties& last_ next_state.direction = GestureDirection::Up; } -const Gesture::GestureState& Gesture::GetLastGestureEntry() const { - return shared_memory->gesture_lifo.ReadCurrentEntry().state; +const GestureState& Gesture::GetLastGestureEntry() const { + return shared_memory.gesture_lifo.ReadCurrentEntry().state; } -Gesture::GestureProperties Gesture::GetGestureProperties() { +GestureProperties Gesture::GetGestureProperties() { GestureProperties gesture; std::array<Core::HID::TouchFinger, MAX_POINTS> active_fingers; const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(), diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h index 4c6f8ee07..41fdfcd03 100644 --- a/src/core/hle/service/hid/controllers/gesture.h +++ b/src/core/hle/service/hid/controllers/gesture.h @@ -4,17 +4,22 @@ #pragma once #include <array> -#include "common/bit_field.h" + #include "common/common_types.h" -#include "common/point.h" -#include "core/hid/emulated_console.h" #include "core/hle/service/hid/controllers/controller_base.h" -#include "core/hle/service/hid/ring_lifo.h" +#include "core/hle/service/hid/controllers/types/touch_types.h" + +namespace Core::HID { +class EmulatedConsole; +} namespace Service::HID { +struct GestureSharedMemoryFormat; + class Gesture final : public ControllerBase { public: - explicit Gesture(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); + explicit Gesture(Core::HID::HIDCore& hid_core_, + GestureSharedMemoryFormat& gesture_shared_memory); ~Gesture() override; // Called when the controller is initialized @@ -27,79 +32,6 @@ public: void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; private: - static constexpr size_t MAX_FINGERS = 16; - static constexpr size_t MAX_POINTS = 4; - - // This is nn::hid::GestureType - enum class GestureType : u32 { - Idle, // Nothing touching the screen - Complete, // Set at the end of a touch event - Cancel, // Set when the number of fingers change - Touch, // A finger just touched the screen - Press, // Set if last type is touch and the finger hasn't moved - Tap, // Fast press then release - Pan, // All points moving together across the screen - Swipe, // Fast press movement and release of a single point - Pinch, // All points moving away/closer to the midpoint - Rotate, // All points rotating from the midpoint - }; - - // This is nn::hid::GestureDirection - enum class GestureDirection : u32 { - None, - Left, - Up, - Right, - Down, - }; - - // This is nn::hid::GestureAttribute - struct GestureAttribute { - union { - u32 raw{}; - - BitField<4, 1, u32> is_new_touch; - BitField<8, 1, u32> is_double_tap; - }; - }; - static_assert(sizeof(GestureAttribute) == 4, "GestureAttribute is an invalid size"); - - // This is nn::hid::GestureState - struct GestureState { - s64 sampling_number{}; - s64 detection_count{}; - GestureType type{GestureType::Idle}; - GestureDirection direction{GestureDirection::None}; - Common::Point<s32> pos{}; - Common::Point<s32> delta{}; - f32 vel_x{}; - f32 vel_y{}; - GestureAttribute attributes{}; - f32 scale{}; - f32 rotation_angle{}; - s32 point_count{}; - std::array<Common::Point<s32>, 4> points{}; - }; - static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size"); - - struct GestureProperties { - std::array<Common::Point<s32>, MAX_POINTS> points{}; - std::size_t active_points{}; - Common::Point<s32> mid_point{}; - s64 detection_count{}; - u64 delta_time{}; - f32 average_distance{}; - f32 angle{}; - }; - - struct GestureSharedMemory { - // This is nn::hid::detail::GestureLifo - Lifo<GestureState, hid_entry_count> gesture_lifo{}; - static_assert(sizeof(gesture_lifo) == 0x708, "gesture_lifo is an invalid size"); - INSERT_PADDING_WORDS(0x3E); - }; - static_assert(sizeof(GestureSharedMemory) == 0x800, "GestureSharedMemory is an invalid size"); - // Reads input from all available input engines void ReadTouchInput(); @@ -142,7 +74,7 @@ private: GestureProperties GetGestureProperties(); GestureState next_state{}; - GestureSharedMemory* shared_memory = nullptr; + GestureSharedMemoryFormat& shared_memory; Core::HID::EmulatedConsole* console = nullptr; std::array<Core::HID::TouchFinger, MAX_POINTS> fingers{}; diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index ddb1b0ba4..871e5036a 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -1,23 +1,18 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <cstring> -#include "common/common_types.h" #include "common/settings.h" #include "core/core_timing.h" #include "core/hid/emulated_devices.h" #include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/keyboard.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" namespace Service::HID { -constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800; - -Keyboard::Keyboard(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) - : ControllerBase{hid_core_} { - static_assert(SHARED_MEMORY_OFFSET + sizeof(KeyboardSharedMemory) < shared_memory_size, - "KeyboardSharedMemory is bigger than the shared memory"); - shared_memory = std::construct_at( - reinterpret_cast<KeyboardSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET)); + +Keyboard::Keyboard(Core::HID::HIDCore& hid_core_, + KeyboardSharedMemoryFormat& keyboard_shared_memory) + : ControllerBase{hid_core_}, shared_memory{keyboard_shared_memory} { emulated_devices = hid_core.GetEmulatedDevices(); } @@ -29,12 +24,12 @@ void Keyboard::OnRelease() {} void Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing) { if (!IsControllerActivated()) { - shared_memory->keyboard_lifo.buffer_count = 0; - shared_memory->keyboard_lifo.buffer_tail = 0; + shared_memory.keyboard_lifo.buffer_count = 0; + shared_memory.keyboard_lifo.buffer_tail = 0; return; } - const auto& last_entry = shared_memory->keyboard_lifo.ReadCurrentEntry().state; + const auto& last_entry = shared_memory.keyboard_lifo.ReadCurrentEntry().state; next_state.sampling_number = last_entry.sampling_number + 1; if (Settings::values.keyboard_enabled) { @@ -46,7 +41,7 @@ void Keyboard::OnUpdate(const Core::Timing::CoreTiming& core_timing) { next_state.attribute.is_connected.Assign(1); } - shared_memory->keyboard_lifo.WriteNextEntry(next_state); + shared_memory.keyboard_lifo.WriteNextEntry(next_state); } } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h index 172ec1309..4d72171b9 100644 --- a/src/core/hle/service/hid/controllers/keyboard.h +++ b/src/core/hle/service/hid/controllers/keyboard.h @@ -3,20 +3,16 @@ #pragma once -#include "common/common_types.h" #include "core/hle/service/hid/controllers/controller_base.h" -#include "core/hle/service/hid/ring_lifo.h" - -namespace Core::HID { -class EmulatedDevices; -struct KeyboardModifier; -struct KeyboardKey; -} // namespace Core::HID +#include "core/hle/service/hid/controllers/types/keyboard_types.h" namespace Service::HID { +struct KeyboardSharedMemoryFormat; + class Keyboard final : public ControllerBase { public: - explicit Keyboard(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); + explicit Keyboard(Core::HID::HIDCore& hid_core_, + KeyboardSharedMemoryFormat& keyboard_shared_memory); ~Keyboard() override; // Called when the controller is initialized @@ -29,25 +25,8 @@ public: void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; private: - // This is nn::hid::detail::KeyboardState - struct KeyboardState { - s64 sampling_number{}; - Core::HID::KeyboardModifier modifier{}; - Core::HID::KeyboardAttribute attribute{}; - Core::HID::KeyboardKey key{}; - }; - static_assert(sizeof(KeyboardState) == 0x30, "KeyboardState is an invalid size"); - - struct KeyboardSharedMemory { - // This is nn::hid::detail::KeyboardLifo - Lifo<KeyboardState, hid_entry_count> keyboard_lifo{}; - static_assert(sizeof(keyboard_lifo) == 0x3D8, "keyboard_lifo is an invalid size"); - INSERT_PADDING_WORDS(0xA); - }; - static_assert(sizeof(KeyboardSharedMemory) == 0x400, "KeyboardSharedMemory is an invalid size"); - KeyboardState next_state{}; - KeyboardSharedMemory* shared_memory = nullptr; + KeyboardSharedMemoryFormat& shared_memory; Core::HID::EmulatedDevices* emulated_devices = nullptr; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp index 6e5a04e34..de5b2c804 100644 --- a/src/core/hle/service/hid/controllers/mouse.cpp +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -1,22 +1,17 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <cstring> -#include "common/common_types.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" #include "core/hid/emulated_devices.h" #include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/mouse.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" namespace Service::HID { -constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400; -Mouse::Mouse(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) : ControllerBase{hid_core_} { - static_assert(SHARED_MEMORY_OFFSET + sizeof(MouseSharedMemory) < shared_memory_size, - "MouseSharedMemory is bigger than the shared memory"); - shared_memory = std::construct_at( - reinterpret_cast<MouseSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET)); +Mouse::Mouse(Core::HID::HIDCore& hid_core_, MouseSharedMemoryFormat& mouse_shared_memory) + : ControllerBase{hid_core_}, shared_memory{mouse_shared_memory} { emulated_devices = hid_core.GetEmulatedDevices(); } @@ -27,14 +22,14 @@ void Mouse::OnRelease() {} void Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing) { if (!IsControllerActivated()) { - shared_memory->mouse_lifo.buffer_count = 0; - shared_memory->mouse_lifo.buffer_tail = 0; + shared_memory.mouse_lifo.buffer_count = 0; + shared_memory.mouse_lifo.buffer_tail = 0; return; } next_state = {}; - const auto& last_entry = shared_memory->mouse_lifo.ReadCurrentEntry().state; + const auto& last_entry = shared_memory.mouse_lifo.ReadCurrentEntry().state; next_state.sampling_number = last_entry.sampling_number + 1; if (Settings::values.mouse_enabled) { @@ -53,7 +48,7 @@ void Mouse::OnUpdate(const Core::Timing::CoreTiming& core_timing) { next_state.button = mouse_button_state; } - shared_memory->mouse_lifo.WriteNextEntry(next_state); + shared_memory.mouse_lifo.WriteNextEntry(next_state); } } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h index a80f3823f..363f316a5 100644 --- a/src/core/hle/service/hid/controllers/mouse.h +++ b/src/core/hle/service/hid/controllers/mouse.h @@ -3,9 +3,7 @@ #pragma once -#include "common/common_types.h" #include "core/hle/service/hid/controllers/controller_base.h" -#include "core/hle/service/hid/ring_lifo.h" namespace Core::HID { class EmulatedDevices; @@ -14,9 +12,11 @@ struct AnalogStickState; } // namespace Core::HID namespace Service::HID { +struct MouseSharedMemoryFormat; + class Mouse final : public ControllerBase { public: - explicit Mouse(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); + explicit Mouse(Core::HID::HIDCore& hid_core_, MouseSharedMemoryFormat& mouse_shared_memory); ~Mouse() override; // Called when the controller is initialized @@ -29,17 +29,9 @@ public: void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; private: - struct MouseSharedMemory { - // This is nn::hid::detail::MouseLifo - Lifo<Core::HID::MouseState, hid_entry_count> mouse_lifo{}; - static_assert(sizeof(mouse_lifo) == 0x350, "mouse_lifo is an invalid size"); - INSERT_PADDING_WORDS(0x2C); - }; - static_assert(sizeof(MouseSharedMemory) == 0x400, "MouseSharedMemory is an invalid size"); - Core::HID::MouseState next_state{}; Core::HID::AnalogStickState last_mouse_wheel_state{}; - MouseSharedMemory* shared_memory = nullptr; + MouseSharedMemoryFormat& shared_memory; Core::HID::EmulatedDevices* emulated_devices = nullptr; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 08ee9de9c..53a737cf5 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -17,12 +17,12 @@ #include "core/hle/kernel/k_event.h" #include "core/hle/kernel/k_readable_event.h" #include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" #include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/hid_util.h" #include "core/hle/service/kernel_helpers.h" namespace Service::HID { -constexpr std::size_t NPAD_OFFSET = 0x9A00; constexpr std::array<Core::HID::NpadIdType, 10> npad_id_list{ Core::HID::NpadIdType::Player1, Core::HID::NpadIdType::Player2, Core::HID::NpadIdType::Player3, Core::HID::NpadIdType::Player4, Core::HID::NpadIdType::Player5, Core::HID::NpadIdType::Player6, @@ -30,14 +30,12 @@ constexpr std::array<Core::HID::NpadIdType, 10> npad_id_list{ Core::HID::NpadIdType::Handheld, }; -NPad::NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_, +NPad::NPad(Core::HID::HIDCore& hid_core_, NpadSharedMemoryFormat& npad_shared_memory_format, KernelHelpers::ServiceContext& service_context_) : ControllerBase{hid_core_}, service_context{service_context_} { - static_assert(NPAD_OFFSET + (NPAD_COUNT * sizeof(NpadInternalState)) < shared_memory_size); for (std::size_t i = 0; i < controller_data.size(); ++i) { auto& controller = controller_data[i]; - controller.shared_memory = std::construct_at(reinterpret_cast<NpadInternalState*>( - raw_shared_memory_ + NPAD_OFFSET + (i * sizeof(NpadInternalState)))); + controller.shared_memory = &npad_shared_memory_format.npad_entry[i].internal_state; controller.device = hid_core.GetEmulatedControllerByIndex(i); controller.vibration[Core::HID::EmulatedDeviceIndex::LeftIndex].latest_vibration_value = Core::HID::DEFAULT_VIBRATION_VALUE; @@ -617,7 +615,7 @@ void NPad::SetHoldType(NpadJoyHoldType joy_hold_type) { hold_type = joy_hold_type; } -NPad::NpadJoyHoldType NPad::GetHoldType() const { +NpadJoyHoldType NPad::GetHoldType() const { return hold_type; } @@ -630,7 +628,7 @@ void NPad::SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_m handheld_activation_mode = activation_mode; } -NPad::NpadHandheldActivationMode NPad::GetNpadHandheldActivationMode() const { +NpadHandheldActivationMode NPad::GetNpadHandheldActivationMode() const { return handheld_activation_mode; } @@ -638,7 +636,7 @@ void NPad::SetNpadCommunicationMode(NpadCommunicationMode communication_mode_) { communication_mode = communication_mode_; } -NPad::NpadCommunicationMode NPad::GetNpadCommunicationMode() const { +NpadCommunicationMode NPad::GetNpadCommunicationMode() const { return communication_mode; } @@ -978,27 +976,27 @@ Result NPad::ResetIsSixAxisSensorDeviceNewlyAssigned( return ResultSuccess; } -NPad::SixAxisLifo& NPad::GetSixAxisFullkeyLifo(Core::HID::NpadIdType npad_id) { +NpadSixAxisSensorLifo& NPad::GetSixAxisFullkeyLifo(Core::HID::NpadIdType npad_id) { return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_fullkey_lifo; } -NPad::SixAxisLifo& NPad::GetSixAxisHandheldLifo(Core::HID::NpadIdType npad_id) { +NpadSixAxisSensorLifo& NPad::GetSixAxisHandheldLifo(Core::HID::NpadIdType npad_id) { return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_handheld_lifo; } -NPad::SixAxisLifo& NPad::GetSixAxisDualLeftLifo(Core::HID::NpadIdType npad_id) { +NpadSixAxisSensorLifo& NPad::GetSixAxisDualLeftLifo(Core::HID::NpadIdType npad_id) { return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_dual_left_lifo; } -NPad::SixAxisLifo& NPad::GetSixAxisDualRightLifo(Core::HID::NpadIdType npad_id) { +NpadSixAxisSensorLifo& NPad::GetSixAxisDualRightLifo(Core::HID::NpadIdType npad_id) { return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_dual_right_lifo; } -NPad::SixAxisLifo& NPad::GetSixAxisLeftLifo(Core::HID::NpadIdType npad_id) { +NpadSixAxisSensorLifo& NPad::GetSixAxisLeftLifo(Core::HID::NpadIdType npad_id) { return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_left_lifo; } -NPad::SixAxisLifo& NPad::GetSixAxisRightLifo(Core::HID::NpadIdType npad_id) { +NpadSixAxisSensorLifo& NPad::GetSixAxisRightLifo(Core::HID::NpadIdType npad_id) { return GetControllerFromNpadIdType(npad_id).shared_memory->sixaxis_right_lifo; } @@ -1343,7 +1341,7 @@ const Core::HID::SixAxisSensorProperties& NPad::GetSixaxisProperties( } } -NPad::AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) { +AppletDetailedUiType NPad::GetAppletDetailedUiType(Core::HID::NpadIdType npad_id) { const auto& shared_memory = GetControllerFromNpadIdType(npad_id).shared_memory; return { diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 9167c93f0..4e2412356 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -8,12 +8,10 @@ #include <mutex> #include <span> -#include "common/bit_field.h" #include "common/common_types.h" - #include "core/hid/hid_types.h" #include "core/hle/service/hid/controllers/controller_base.h" -#include "core/hle/service/hid/ring_lifo.h" +#include "core/hle/service/hid/controllers/types/npad_types.h" namespace Core::HID { class EmulatedController; @@ -32,10 +30,13 @@ class ServiceContext; union Result; namespace Service::HID { +struct NpadInternalState; +struct NpadSixAxisSensorLifo; +struct NpadSharedMemoryFormat; class NPad final : public ControllerBase { public: - explicit NPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_, + explicit NPad(Core::HID::HIDCore& hid_core_, NpadSharedMemoryFormat& npad_shared_memory_format, KernelHelpers::ServiceContext& service_context_); ~NPad() override; @@ -48,89 +49,6 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; - // This is nn::hid::NpadJoyHoldType - enum class NpadJoyHoldType : u64 { - Vertical = 0, - Horizontal = 1, - }; - - // This is nn::hid::NpadJoyAssignmentMode - enum class NpadJoyAssignmentMode : u32 { - Dual = 0, - Single = 1, - }; - - // This is nn::hid::NpadJoyDeviceType - enum class NpadJoyDeviceType : s64 { - Left = 0, - Right = 1, - }; - - // This is nn::hid::NpadHandheldActivationMode - enum class NpadHandheldActivationMode : u64 { - Dual = 0, - Single = 1, - None = 2, - MaxActivationMode = 3, - }; - - // This is nn::hid::system::AppletFooterUiAttributesSet - struct AppletFooterUiAttributes { - INSERT_PADDING_BYTES(0x4); - }; - - // This is nn::hid::system::AppletFooterUiType - enum class AppletFooterUiType : u8 { - None = 0, - HandheldNone = 1, - HandheldJoyConLeftOnly = 2, - HandheldJoyConRightOnly = 3, - HandheldJoyConLeftJoyConRight = 4, - JoyDual = 5, - JoyDualLeftOnly = 6, - JoyDualRightOnly = 7, - JoyLeftHorizontal = 8, - JoyLeftVertical = 9, - JoyRightHorizontal = 10, - JoyRightVertical = 11, - SwitchProController = 12, - CompatibleProController = 13, - CompatibleJoyCon = 14, - LarkHvc1 = 15, - LarkHvc2 = 16, - LarkNesLeft = 17, - LarkNesRight = 18, - Lucia = 19, - Verification = 20, - Lagon = 21, - }; - - using AppletFooterUiVariant = u8; - - // This is "nn::hid::system::AppletDetailedUiType". - struct AppletDetailedUiType { - AppletFooterUiVariant ui_variant; - INSERT_PADDING_BYTES(0x2); - AppletFooterUiType footer; - }; - static_assert(sizeof(AppletDetailedUiType) == 0x4, "AppletDetailedUiType is an invalid size"); - // This is nn::hid::NpadCommunicationMode - enum class NpadCommunicationMode : u64 { - Mode_5ms = 0, - Mode_10ms = 1, - Mode_15ms = 2, - Default = 3, - }; - - enum class NpadRevision : u32 { - Revision0 = 0, - Revision1 = 1, - Revision2 = 2, - Revision3 = 3, - }; - - using SixAxisLifo = Lifo<Core::HID::SixAxisSensorState, hid_entry_count>; - void SetSupportedStyleSet(Core::HID::NpadStyleTag style_set); Core::HID::NpadStyleTag GetSupportedStyleSet() const; @@ -188,12 +106,12 @@ public: Result ResetIsSixAxisSensorDeviceNewlyAssigned( const Core::HID::SixAxisSensorHandle& sixaxis_handle); - SixAxisLifo& GetSixAxisFullkeyLifo(Core::HID::NpadIdType npad_id); - SixAxisLifo& GetSixAxisHandheldLifo(Core::HID::NpadIdType npad_id); - SixAxisLifo& GetSixAxisDualLeftLifo(Core::HID::NpadIdType npad_id); - SixAxisLifo& GetSixAxisDualRightLifo(Core::HID::NpadIdType npad_id); - SixAxisLifo& GetSixAxisLeftLifo(Core::HID::NpadIdType npad_id); - SixAxisLifo& GetSixAxisRightLifo(Core::HID::NpadIdType npad_id); + NpadSixAxisSensorLifo& GetSixAxisFullkeyLifo(Core::HID::NpadIdType npad_id); + NpadSixAxisSensorLifo& GetSixAxisHandheldLifo(Core::HID::NpadIdType npad_id); + NpadSixAxisSensorLifo& GetSixAxisDualLeftLifo(Core::HID::NpadIdType npad_id); + NpadSixAxisSensorLifo& GetSixAxisDualRightLifo(Core::HID::NpadIdType npad_id); + NpadSixAxisSensorLifo& GetSixAxisLeftLifo(Core::HID::NpadIdType npad_id); + NpadSixAxisSensorLifo& GetSixAxisRightLifo(Core::HID::NpadIdType npad_id); Result GetLedPattern(Core::HID::NpadIdType npad_id, Core::HID::LedPattern& pattern) const; Result IsUnintendedHomeButtonInputProtectionEnabled(Core::HID::NpadIdType npad_id, @@ -221,214 +139,6 @@ public: AppletDetailedUiType GetAppletDetailedUiType(Core::HID::NpadIdType npad_id); private: - static constexpr std::size_t NPAD_COUNT = 10; - - // This is nn::hid::detail::ColorAttribute - enum class ColorAttribute : u32 { - Ok = 0, - ReadError = 1, - NoController = 2, - }; - static_assert(sizeof(ColorAttribute) == 4, "ColorAttribute is an invalid size"); - - // This is nn::hid::detail::NpadFullKeyColorState - struct NpadFullKeyColorState { - ColorAttribute attribute{ColorAttribute::NoController}; - Core::HID::NpadControllerColor fullkey{}; - }; - static_assert(sizeof(NpadFullKeyColorState) == 0xC, "NpadFullKeyColorState is an invalid size"); - - // This is nn::hid::detail::NpadJoyColorState - struct NpadJoyColorState { - ColorAttribute attribute{ColorAttribute::NoController}; - Core::HID::NpadControllerColor left{}; - Core::HID::NpadControllerColor right{}; - }; - static_assert(sizeof(NpadJoyColorState) == 0x14, "NpadJoyColorState is an invalid size"); - - // This is nn::hid::NpadAttribute - struct NpadAttribute { - union { - u32 raw{}; - BitField<0, 1, u32> is_connected; - BitField<1, 1, u32> is_wired; - BitField<2, 1, u32> is_left_connected; - BitField<3, 1, u32> is_left_wired; - BitField<4, 1, u32> is_right_connected; - BitField<5, 1, u32> is_right_wired; - }; - }; - static_assert(sizeof(NpadAttribute) == 4, "NpadAttribute is an invalid size"); - - // This is nn::hid::NpadFullKeyState - // This is nn::hid::NpadHandheldState - // This is nn::hid::NpadJoyDualState - // This is nn::hid::NpadJoyLeftState - // This is nn::hid::NpadJoyRightState - // This is nn::hid::NpadPalmaState - // This is nn::hid::NpadSystemExtState - struct NPadGenericState { - s64_le sampling_number{}; - Core::HID::NpadButtonState npad_buttons{}; - Core::HID::AnalogStickState l_stick{}; - Core::HID::AnalogStickState r_stick{}; - NpadAttribute connection_status{}; - INSERT_PADDING_BYTES(4); // Reserved - }; - static_assert(sizeof(NPadGenericState) == 0x28, "NPadGenericState is an invalid size"); - - // This is nn::hid::server::NpadGcTriggerState - struct NpadGcTriggerState { - s64 sampling_number{}; - s32 l_analog{}; - s32 r_analog{}; - }; - static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size"); - - // This is nn::hid::NpadSystemProperties - struct NPadSystemProperties { - union { - s64 raw{}; - BitField<0, 1, s64> is_charging_joy_dual; - BitField<1, 1, s64> is_charging_joy_left; - BitField<2, 1, s64> is_charging_joy_right; - BitField<3, 1, s64> is_powered_joy_dual; - BitField<4, 1, s64> is_powered_joy_left; - BitField<5, 1, s64> is_powered_joy_right; - BitField<9, 1, s64> is_system_unsupported_button; - BitField<10, 1, s64> is_system_ext_unsupported_button; - BitField<11, 1, s64> is_vertical; - BitField<12, 1, s64> is_horizontal; - BitField<13, 1, s64> use_plus; - BitField<14, 1, s64> use_minus; - BitField<15, 1, s64> use_directional_buttons; - }; - }; - static_assert(sizeof(NPadSystemProperties) == 0x8, "NPadSystemProperties is an invalid size"); - - // This is nn::hid::NpadSystemButtonProperties - struct NpadSystemButtonProperties { - union { - s32 raw{}; - BitField<0, 1, s32> is_home_button_protection_enabled; - }; - }; - static_assert(sizeof(NpadSystemButtonProperties) == 0x4, - "NPadButtonProperties is an invalid size"); - - // This is nn::hid::system::DeviceType - struct DeviceType { - union { - u32 raw{}; - BitField<0, 1, s32> fullkey; - BitField<1, 1, s32> debug_pad; - BitField<2, 1, s32> handheld_left; - BitField<3, 1, s32> handheld_right; - BitField<4, 1, s32> joycon_left; - BitField<5, 1, s32> joycon_right; - BitField<6, 1, s32> palma; - BitField<7, 1, s32> lark_hvc_left; - BitField<8, 1, s32> lark_hvc_right; - BitField<9, 1, s32> lark_nes_left; - BitField<10, 1, s32> lark_nes_right; - BitField<11, 1, s32> handheld_lark_hvc_left; - BitField<12, 1, s32> handheld_lark_hvc_right; - BitField<13, 1, s32> handheld_lark_nes_left; - BitField<14, 1, s32> handheld_lark_nes_right; - BitField<15, 1, s32> lucia; - BitField<16, 1, s32> lagon; - BitField<17, 1, s32> lager; - BitField<31, 1, s32> system; - }; - }; - - // This is nn::hid::detail::NfcXcdDeviceHandleStateImpl - struct NfcXcdDeviceHandleStateImpl { - u64 handle{}; - bool is_available{}; - bool is_activated{}; - INSERT_PADDING_BYTES(0x6); // Reserved - u64 sampling_number{}; - }; - static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18, - "NfcXcdDeviceHandleStateImpl is an invalid size"); - - // This is nn::hid::NpadLarkType - enum class NpadLarkType : u32 { - Invalid, - H1, - H2, - NL, - NR, - }; - - // This is nn::hid::NpadLuciaType - enum class NpadLuciaType : u32 { - Invalid, - J, - E, - U, - }; - - // This is nn::hid::NpadLagonType - enum class NpadLagonType : u32 { - Invalid, - }; - - // This is nn::hid::NpadLagerType - enum class NpadLagerType : u32 { - Invalid, - J, - E, - U, - }; - - // This is nn::hid::detail::NpadInternalState - struct NpadInternalState { - Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; - NpadJoyAssignmentMode assignment_mode{NpadJoyAssignmentMode::Dual}; - NpadFullKeyColorState fullkey_color{}; - NpadJoyColorState joycon_color{}; - Lifo<NPadGenericState, hid_entry_count> fullkey_lifo{}; - Lifo<NPadGenericState, hid_entry_count> handheld_lifo{}; - Lifo<NPadGenericState, hid_entry_count> joy_dual_lifo{}; - Lifo<NPadGenericState, hid_entry_count> joy_left_lifo{}; - Lifo<NPadGenericState, hid_entry_count> joy_right_lifo{}; - Lifo<NPadGenericState, hid_entry_count> palma_lifo{}; - Lifo<NPadGenericState, hid_entry_count> system_ext_lifo{}; - Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_fullkey_lifo{}; - Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_handheld_lifo{}; - Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_dual_left_lifo{}; - Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_dual_right_lifo{}; - Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_left_lifo{}; - Lifo<Core::HID::SixAxisSensorState, hid_entry_count> sixaxis_right_lifo{}; - DeviceType device_type{}; - INSERT_PADDING_BYTES(0x4); // Reserved - NPadSystemProperties system_properties{}; - NpadSystemButtonProperties button_properties{}; - Core::HID::NpadBatteryLevel battery_level_dual{}; - Core::HID::NpadBatteryLevel battery_level_left{}; - Core::HID::NpadBatteryLevel battery_level_right{}; - AppletFooterUiAttributes applet_footer_attributes{}; - AppletFooterUiType applet_footer_type{AppletFooterUiType::None}; - INSERT_PADDING_BYTES(0x5B); // Reserved - INSERT_PADDING_BYTES(0x20); // Unknown - Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; - NpadLarkType lark_type_l_and_main{}; - NpadLarkType lark_type_r{}; - NpadLuciaType lucia_type{}; - NpadLagonType lagon_type{}; - NpadLagerType lager_type{}; - Core::HID::SixAxisSensorProperties sixaxis_fullkey_properties; - Core::HID::SixAxisSensorProperties sixaxis_handheld_properties; - Core::HID::SixAxisSensorProperties sixaxis_dual_left_properties; - Core::HID::SixAxisSensorProperties sixaxis_dual_right_properties; - Core::HID::SixAxisSensorProperties sixaxis_left_properties; - Core::HID::SixAxisSensorProperties sixaxis_right_properties; - INSERT_PADDING_BYTES(0xc06); // Unknown - }; - static_assert(sizeof(NpadInternalState) == 0x5000, "NpadInternalState is an invalid size"); - struct VibrationData { bool device_mounted{}; Core::HID::VibrationValue latest_vibration_value{}; @@ -479,7 +189,7 @@ private: std::atomic<u64> press_state{}; - std::array<NpadControllerData, NPAD_COUNT> controller_data{}; + std::array<NpadControllerData, NpadCount> controller_data{}; KernelHelpers::ServiceContext& service_context; std::mutex mutex; std::vector<Core::HID::NpadIdType> supported_npad_id_types{}; diff --git a/src/core/hle/service/hid/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp index 588ff9d62..aa0454b5e 100644 --- a/src/core/hle/service/hid/controllers/palma.cpp +++ b/src/core/hle/service/hid/controllers/palma.cpp @@ -12,8 +12,7 @@ namespace Service::HID { -Palma::Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_, - KernelHelpers::ServiceContext& service_context_) +Palma::Palma(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_) : ControllerBase{hid_core_}, service_context{service_context_} { controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other); operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent"); diff --git a/src/core/hle/service/hid/controllers/palma.h b/src/core/hle/service/hid/controllers/palma.h index a6047f36a..73884230d 100644 --- a/src/core/hle/service/hid/controllers/palma.h +++ b/src/core/hle/service/hid/controllers/palma.h @@ -97,8 +97,7 @@ public: static_assert(sizeof(PalmaConnectionHandle) == 0x8, "PalmaConnectionHandle has incorrect size."); - explicit Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_, - KernelHelpers::ServiceContext& service_context_); + explicit Palma(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_); ~Palma() override; // Called when the controller is initialized diff --git a/src/core/hle/service/hid/controllers/shared_memory_format.h b/src/core/hle/service/hid/controllers/shared_memory_format.h new file mode 100644 index 000000000..2986c113e --- /dev/null +++ b/src/core/hle/service/hid/controllers/shared_memory_format.h @@ -0,0 +1,240 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/vector_math.h" +#include "core/hid/hid_types.h" +#include "core/hle/service/hid//controllers/types/debug_pad_types.h" +#include "core/hle/service/hid//controllers/types/keyboard_types.h" +#include "core/hle/service/hid//controllers/types/mouse_types.h" +#include "core/hle/service/hid//controllers/types/npad_types.h" +#include "core/hle/service/hid//controllers/types/touch_types.h" +#include "core/hle/service/hid/ring_lifo.h" + +namespace Service::HID { +static const std::size_t HidEntryCount = 17; + +struct CommonHeader { + s64 timestamp{}; + s64 total_entry_count{}; + s64 last_entry_index{}; + s64 entry_count{}; +}; +static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size"); + +// This is nn::hid::detail::DebugPadSharedMemoryFormat +struct DebugPadSharedMemoryFormat { + // This is nn::hid::detail::DebugPadLifo + Lifo<DebugPadState, HidEntryCount> debug_pad_lifo{}; + static_assert(sizeof(debug_pad_lifo) == 0x2C8, "debug_pad_lifo is an invalid size"); + INSERT_PADDING_WORDS(0x4E); +}; +static_assert(sizeof(DebugPadSharedMemoryFormat) == 0x400, + "DebugPadSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::TouchScreenSharedMemoryFormat +struct TouchScreenSharedMemoryFormat { + // This is nn::hid::detail::TouchScreenLifo + Lifo<TouchScreenState, HidEntryCount> touch_screen_lifo{}; + static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size"); + INSERT_PADDING_WORDS(0xF2); +}; +static_assert(sizeof(TouchScreenSharedMemoryFormat) == 0x3000, + "TouchScreenSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::MouseSharedMemoryFormat +struct MouseSharedMemoryFormat { + // This is nn::hid::detail::MouseLifo + Lifo<Core::HID::MouseState, HidEntryCount> mouse_lifo{}; + static_assert(sizeof(mouse_lifo) == 0x350, "mouse_lifo is an invalid size"); + INSERT_PADDING_WORDS(0x2C); +}; +static_assert(sizeof(MouseSharedMemoryFormat) == 0x400, + "MouseSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::KeyboardSharedMemoryFormat +struct KeyboardSharedMemoryFormat { + // This is nn::hid::detail::KeyboardLifo + Lifo<KeyboardState, HidEntryCount> keyboard_lifo{}; + static_assert(sizeof(keyboard_lifo) == 0x3D8, "keyboard_lifo is an invalid size"); + INSERT_PADDING_WORDS(0xA); +}; +static_assert(sizeof(KeyboardSharedMemoryFormat) == 0x400, + "KeyboardSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::DigitizerSharedMemoryFormat +struct DigitizerSharedMemoryFormat { + CommonHeader header; + INSERT_PADDING_BYTES(0xFE0); +}; +static_assert(sizeof(DigitizerSharedMemoryFormat) == 0x1000, + "DigitizerSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::HomeButtonSharedMemoryFormat +struct HomeButtonSharedMemoryFormat { + CommonHeader header; + INSERT_PADDING_BYTES(0x1E0); +}; +static_assert(sizeof(HomeButtonSharedMemoryFormat) == 0x200, + "HomeButtonSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::SleepButtonSharedMemoryFormat +struct SleepButtonSharedMemoryFormat { + CommonHeader header; + INSERT_PADDING_BYTES(0x1E0); +}; +static_assert(sizeof(SleepButtonSharedMemoryFormat) == 0x200, + "SleepButtonSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::CaptureButtonSharedMemoryFormat +struct CaptureButtonSharedMemoryFormat { + CommonHeader header; + INSERT_PADDING_BYTES(0x1E0); +}; +static_assert(sizeof(CaptureButtonSharedMemoryFormat) == 0x200, + "CaptureButtonSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::InputDetectorSharedMemoryFormat +struct InputDetectorSharedMemoryFormat { + CommonHeader header; + INSERT_PADDING_BYTES(0x7E0); +}; +static_assert(sizeof(InputDetectorSharedMemoryFormat) == 0x800, + "InputDetectorSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::UniquePadSharedMemoryFormat +struct UniquePadSharedMemoryFormat { + CommonHeader header; + INSERT_PADDING_BYTES(0x3FE0); +}; +static_assert(sizeof(UniquePadSharedMemoryFormat) == 0x4000, + "UniquePadSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::NpadSixAxisSensorLifo +struct NpadSixAxisSensorLifo { + Lifo<Core::HID::SixAxisSensorState, HidEntryCount> lifo; +}; + +// This is nn::hid::detail::NpadInternalState +struct NpadInternalState { + Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; + NpadJoyAssignmentMode assignment_mode{NpadJoyAssignmentMode::Dual}; + NpadFullKeyColorState fullkey_color{}; + NpadJoyColorState joycon_color{}; + Lifo<NPadGenericState, HidEntryCount> fullkey_lifo{}; + Lifo<NPadGenericState, HidEntryCount> handheld_lifo{}; + Lifo<NPadGenericState, HidEntryCount> joy_dual_lifo{}; + Lifo<NPadGenericState, HidEntryCount> joy_left_lifo{}; + Lifo<NPadGenericState, HidEntryCount> joy_right_lifo{}; + Lifo<NPadGenericState, HidEntryCount> palma_lifo{}; + Lifo<NPadGenericState, HidEntryCount> system_ext_lifo{}; + NpadSixAxisSensorLifo sixaxis_fullkey_lifo{}; + NpadSixAxisSensorLifo sixaxis_handheld_lifo{}; + NpadSixAxisSensorLifo sixaxis_dual_left_lifo{}; + NpadSixAxisSensorLifo sixaxis_dual_right_lifo{}; + NpadSixAxisSensorLifo sixaxis_left_lifo{}; + NpadSixAxisSensorLifo sixaxis_right_lifo{}; + DeviceType device_type{}; + INSERT_PADDING_BYTES(0x4); // Reserved + NPadSystemProperties system_properties{}; + NpadSystemButtonProperties button_properties{}; + Core::HID::NpadBatteryLevel battery_level_dual{}; + Core::HID::NpadBatteryLevel battery_level_left{}; + Core::HID::NpadBatteryLevel battery_level_right{}; + AppletFooterUiAttributes applet_footer_attributes{}; + AppletFooterUiType applet_footer_type{AppletFooterUiType::None}; + INSERT_PADDING_BYTES(0x5B); // Reserved + INSERT_PADDING_BYTES(0x20); // Unknown + Lifo<NpadGcTriggerState, HidEntryCount> gc_trigger_lifo{}; + NpadLarkType lark_type_l_and_main{}; + NpadLarkType lark_type_r{}; + NpadLuciaType lucia_type{}; + NpadLagerType lager_type{}; + Core::HID::SixAxisSensorProperties sixaxis_fullkey_properties; + Core::HID::SixAxisSensorProperties sixaxis_handheld_properties; + Core::HID::SixAxisSensorProperties sixaxis_dual_left_properties; + Core::HID::SixAxisSensorProperties sixaxis_dual_right_properties; + Core::HID::SixAxisSensorProperties sixaxis_left_properties; + Core::HID::SixAxisSensorProperties sixaxis_right_properties; +}; +static_assert(sizeof(NpadInternalState) == 0x43F8, "NpadInternalState is an invalid size"); + +// This is nn::hid::detail::NpadSharedMemoryEntry +struct NpadSharedMemoryEntry { + NpadInternalState internal_state; + INSERT_PADDING_BYTES(0xC08); +}; +static_assert(sizeof(NpadSharedMemoryEntry) == 0x5000, "NpadSharedMemoryEntry is an invalid size"); + +// This is nn::hid::detail::NpadSharedMemoryFormat +struct NpadSharedMemoryFormat { + std::array<NpadSharedMemoryEntry, NpadCount> npad_entry; +}; +static_assert(sizeof(NpadSharedMemoryFormat) == 0x32000, + "NpadSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::GestureSharedMemoryFormat +struct GestureSharedMemoryFormat { + // This is nn::hid::detail::GestureLifo + Lifo<GestureState, HidEntryCount> gesture_lifo{}; + static_assert(sizeof(gesture_lifo) == 0x708, "gesture_lifo is an invalid size"); + INSERT_PADDING_WORDS(0x3E); +}; +static_assert(sizeof(GestureSharedMemoryFormat) == 0x800, + "GestureSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::ConsoleSixAxisSensorSharedMemoryFormat +struct ConsoleSixAxisSensorSharedMemoryFormat { + u64 sampling_number{}; + bool is_seven_six_axis_sensor_at_rest{}; + INSERT_PADDING_BYTES(3); // padding + f32 verticalization_error{}; + Common::Vec3f gyro_bias{}; + INSERT_PADDING_BYTES(4); // padding +}; +static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20, + "ConsoleSixAxisSensorSharedMemoryFormat is an invalid size"); + +// This is nn::hid::detail::SharedMemoryFormat +struct SharedMemoryFormat { + void Initialize() {} + + DebugPadSharedMemoryFormat debug_pad; + TouchScreenSharedMemoryFormat touch_screen; + MouseSharedMemoryFormat mouse; + KeyboardSharedMemoryFormat keyboard; + DigitizerSharedMemoryFormat digitizer; + HomeButtonSharedMemoryFormat home_button; + SleepButtonSharedMemoryFormat sleep_button; + CaptureButtonSharedMemoryFormat capture_button; + InputDetectorSharedMemoryFormat input_detector; + UniquePadSharedMemoryFormat unique_pad; + NpadSharedMemoryFormat npad; + GestureSharedMemoryFormat gesture; + ConsoleSixAxisSensorSharedMemoryFormat console; + INSERT_PADDING_BYTES(0x19E0); + MouseSharedMemoryFormat debug_mouse; + INSERT_PADDING_BYTES(0x2000); +}; +static_assert(offsetof(SharedMemoryFormat, debug_pad) == 0x0, "debug_pad has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, touch_screen) == 0x400, "touch_screen has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, mouse) == 0x3400, "mouse has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, keyboard) == 0x3800, "keyboard has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, digitizer) == 0x3C00, "digitizer has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, home_button) == 0x4C00, "home_button has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, sleep_button) == 0x4E00, + "sleep_button has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, capture_button) == 0x5000, + "capture_button has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, input_detector) == 0x5200, + "input_detector has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, npad) == 0x9A00, "npad has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, gesture) == 0x3BA00, "gesture has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, console) == 0x3C200, "console has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, debug_mouse) == 0x3DC00, "debug_mouse has wrong offset"); +static_assert(sizeof(SharedMemoryFormat) == 0x40000, "SharedMemoryFormat is an invalid size"); + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/shared_memory_holder.cpp b/src/core/hle/service/hid/controllers/shared_memory_holder.cpp new file mode 100644 index 000000000..51581188e --- /dev/null +++ b/src/core/hle/service/hid/controllers/shared_memory_holder.cpp @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "core/core.h" +#include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" +#include "core/hle/service/hid/controllers/shared_memory_holder.h" +#include "core/hle/service/hid/errors.h" + +namespace Service::HID { +SharedMemoryHolder::SharedMemoryHolder() {} + +SharedMemoryHolder::~SharedMemoryHolder() { + Finalize(); +} + +Result SharedMemoryHolder::Initialize(Core::System& system) { + shared_memory = Kernel::KSharedMemory::Create(system.Kernel()); + const Result result = shared_memory->Initialize( + system.DeviceMemory(), nullptr, Kernel::Svc::MemoryPermission::None, + Kernel::Svc::MemoryPermission::Read, sizeof(SharedMemoryFormat)); + if (result.IsError()) { + return result; + } + Kernel::KSharedMemory::Register(system.Kernel(), shared_memory); + + is_created = true; + is_mapped = true; + address = std::construct_at(reinterpret_cast<SharedMemoryFormat*>(shared_memory->GetPointer())); + return ResultSuccess; +} + +void SharedMemoryHolder::Finalize() { + if (address != nullptr) { + shared_memory->Close(); + } + is_created = false; + is_mapped = false; + address = nullptr; +} + +bool SharedMemoryHolder::IsMapped() { + return is_mapped; +} + +SharedMemoryFormat* SharedMemoryHolder::GetAddress() { + return address; +} + +Kernel::KSharedMemory* SharedMemoryHolder::GetHandle() { + return shared_memory; +} +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/shared_memory_holder.h b/src/core/hle/service/hid/controllers/shared_memory_holder.h new file mode 100644 index 000000000..943407c00 --- /dev/null +++ b/src/core/hle/service/hid/controllers/shared_memory_holder.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "core/hle/result.h" + +namespace Core { +class System; +} + +namespace Kernel { +class KSharedMemory; +} + +namespace Service::HID { +struct SharedMemoryFormat; + +// This is nn::hid::detail::SharedMemoryHolder +class SharedMemoryHolder { +public: + SharedMemoryHolder(); + ~SharedMemoryHolder(); + + Result Initialize(Core::System& system); + void Finalize(); + + bool IsMapped(); + SharedMemoryFormat* GetAddress(); + Kernel::KSharedMemory* GetHandle(); + +private: + bool is_owner{}; + bool is_created{}; + bool is_mapped{}; + INSERT_PADDING_BYTES(0x5); + Kernel::KSharedMemory* shared_memory; + INSERT_PADDING_BYTES(0x38); + SharedMemoryFormat* address = nullptr; +}; +// Correct size is 0x50 bytes +static_assert(sizeof(SharedMemoryHolder) == 0x50, "SharedMemoryHolder is an invalid size"); + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/six_axis.cpp b/src/core/hle/service/hid/controllers/six_axis.cpp index 3d24a5c04..36b72f9ea 100644 --- a/src/core/hle/service/hid/controllers/six_axis.cpp +++ b/src/core/hle/service/hid/controllers/six_axis.cpp @@ -6,6 +6,7 @@ #include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" #include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" #include "core/hle/service/hid/controllers/six_axis.h" #include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/hid_util.h" @@ -132,30 +133,30 @@ void SixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) { } sixaxis_fullkey_state.sampling_number = - sixaxis_fullkey_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_fullkey_lifo.lifo.ReadCurrentEntry().state.sampling_number + 1; sixaxis_handheld_state.sampling_number = - sixaxis_handheld_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_handheld_lifo.lifo.ReadCurrentEntry().state.sampling_number + 1; sixaxis_dual_left_state.sampling_number = - sixaxis_dual_left_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_dual_left_lifo.lifo.ReadCurrentEntry().state.sampling_number + 1; sixaxis_dual_right_state.sampling_number = - sixaxis_dual_right_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_dual_right_lifo.lifo.ReadCurrentEntry().state.sampling_number + 1; sixaxis_left_lifo_state.sampling_number = - sixaxis_left_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_left_lifo.lifo.ReadCurrentEntry().state.sampling_number + 1; sixaxis_right_lifo_state.sampling_number = - sixaxis_right_lifo.ReadCurrentEntry().state.sampling_number + 1; + sixaxis_right_lifo.lifo.ReadCurrentEntry().state.sampling_number + 1; if (IndexToNpadIdType(i) == Core::HID::NpadIdType::Handheld) { // This buffer only is updated on handheld on HW - sixaxis_handheld_lifo.WriteNextEntry(sixaxis_handheld_state); + sixaxis_handheld_lifo.lifo.WriteNextEntry(sixaxis_handheld_state); } else { // Handheld doesn't update this buffer on HW - sixaxis_fullkey_lifo.WriteNextEntry(sixaxis_fullkey_state); + sixaxis_fullkey_lifo.lifo.WriteNextEntry(sixaxis_fullkey_state); } - sixaxis_dual_left_lifo.WriteNextEntry(sixaxis_dual_left_state); - sixaxis_dual_right_lifo.WriteNextEntry(sixaxis_dual_right_state); - sixaxis_left_lifo.WriteNextEntry(sixaxis_left_lifo_state); - sixaxis_right_lifo.WriteNextEntry(sixaxis_right_lifo_state); + sixaxis_dual_left_lifo.lifo.WriteNextEntry(sixaxis_dual_left_state); + sixaxis_dual_right_lifo.lifo.WriteNextEntry(sixaxis_dual_right_state); + sixaxis_left_lifo.lifo.WriteNextEntry(sixaxis_left_lifo_state); + sixaxis_right_lifo.lifo.WriteNextEntry(sixaxis_right_lifo_state); } } diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp index 9e2f3ab21..e2a5f5d79 100644 --- a/src/core/hle/service/hid/controllers/stubbed.cpp +++ b/src/core/hle/service/hid/controllers/stubbed.cpp @@ -1,18 +1,15 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <cstring> -#include "common/common_types.h" #include "core/core_timing.h" -#include "core/hid/hid_core.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" #include "core/hle/service/hid/controllers/stubbed.h" namespace Service::HID { -Controller_Stubbed::Controller_Stubbed(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) - : ControllerBase{hid_core_} { - raw_shared_memory = raw_shared_memory_; -} +Controller_Stubbed::Controller_Stubbed(Core::HID::HIDCore& hid_core_, + CommonHeader& ring_lifo_header) + : ControllerBase{hid_core_}, header{ring_lifo_header} {} Controller_Stubbed::~Controller_Stubbed() = default; @@ -25,18 +22,10 @@ void Controller_Stubbed::OnUpdate(const Core::Timing::CoreTiming& core_timing) { return; } - CommonHeader header{}; header.timestamp = core_timing.GetGlobalTimeNs().count(); header.total_entry_count = 17; header.entry_count = 0; header.last_entry_index = 0; - - std::memcpy(raw_shared_memory + common_offset, &header, sizeof(CommonHeader)); -} - -void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) { - common_offset = off; - smart_update = true; } } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h index 1483a968e..d2052fb17 100644 --- a/src/core/hle/service/hid/controllers/stubbed.h +++ b/src/core/hle/service/hid/controllers/stubbed.h @@ -3,13 +3,14 @@ #pragma once -#include "common/common_types.h" #include "core/hle/service/hid/controllers/controller_base.h" namespace Service::HID { +struct CommonHeader; + class Controller_Stubbed final : public ControllerBase { public: - explicit Controller_Stubbed(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); + explicit Controller_Stubbed(Core::HID::HIDCore& hid_core_, CommonHeader& ring_lifo_header); ~Controller_Stubbed() override; // Called when the controller is initialized @@ -21,19 +22,8 @@ public: // When the controller is requesting an update for the shared memory void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; - void SetCommonHeaderOffset(std::size_t off); - private: - struct CommonHeader { - s64 timestamp{}; - s64 total_entry_count{}; - s64 last_entry_index{}; - s64 entry_count{}; - }; - static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size"); - - u8* raw_shared_memory = nullptr; + CommonHeader& header; bool smart_update{}; - std::size_t common_offset{}; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index fcd973414..469750006 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -2,26 +2,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <algorithm> -#include <cstring> #include "common/common_types.h" #include "common/settings.h" -#include "core/core.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" #include "core/hid/emulated_console.h" #include "core/hid/hid_core.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" #include "core/hle/service/hid/controllers/touchscreen.h" namespace Service::HID { -constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400; -TouchScreen::TouchScreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) - : ControllerBase{hid_core_}, touchscreen_width(Layout::ScreenUndocked::Width), +TouchScreen::TouchScreen(Core::HID::HIDCore& hid_core_, + TouchScreenSharedMemoryFormat& touch_shared_memory) + : ControllerBase{hid_core_}, shared_memory{touch_shared_memory}, + touchscreen_width(Layout::ScreenUndocked::Width), touchscreen_height(Layout::ScreenUndocked::Height) { - static_assert(SHARED_MEMORY_OFFSET + sizeof(TouchSharedMemory) < shared_memory_size, - "TouchSharedMemory is bigger than the shared memory"); - shared_memory = std::construct_at( - reinterpret_cast<TouchSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET)); console = hid_core.GetEmulatedConsole(); } @@ -32,11 +28,11 @@ void TouchScreen::OnInit() {} void TouchScreen::OnRelease() {} void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) { - shared_memory->touch_screen_lifo.timestamp = core_timing.GetGlobalTimeNs().count(); + shared_memory.touch_screen_lifo.timestamp = core_timing.GetGlobalTimeNs().count(); if (!IsControllerActivated()) { - shared_memory->touch_screen_lifo.buffer_count = 0; - shared_memory->touch_screen_lifo.buffer_tail = 0; + shared_memory.touch_screen_lifo.buffer_count = 0; + shared_memory.touch_screen_lifo.buffer_tail = 0; return; } @@ -86,7 +82,7 @@ void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) { static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter)); const u64 timestamp = static_cast<u64>(core_timing.GetGlobalTimeNs().count()); - const auto& last_entry = shared_memory->touch_screen_lifo.ReadCurrentEntry().state; + const auto& last_entry = shared_memory.touch_screen_lifo.ReadCurrentEntry().state; next_state.sampling_number = last_entry.sampling_number + 1; next_state.entry_count = static_cast<s32>(active_fingers_count); @@ -118,7 +114,7 @@ void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) { } } - shared_memory->touch_screen_lifo.WriteNextEntry(next_state); + shared_memory.touch_screen_lifo.WriteNextEntry(next_state); } void TouchScreen::SetTouchscreenDimensions(u32 width, u32 height) { diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index 79f026a81..5b6305bfc 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -3,20 +3,23 @@ #pragma once -#include "common/common_funcs.h" -#include "common/common_types.h" +#include <array> + #include "core/hid/hid_types.h" #include "core/hle/service/hid/controllers/controller_base.h" -#include "core/hle/service/hid/ring_lifo.h" +#include "core/hle/service/hid/controllers/types/touch_types.h" namespace Core::HID { class EmulatedConsole; } // namespace Core::HID namespace Service::HID { +struct TouchScreenSharedMemoryFormat; + class TouchScreen final : public ControllerBase { public: - explicit TouchScreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); + explicit TouchScreen(Core::HID::HIDCore& hid_core_, + TouchScreenSharedMemoryFormat& touch_shared_memory); ~TouchScreen() override; // Called when the controller is initialized @@ -31,27 +34,8 @@ public: void SetTouchscreenDimensions(u32 width, u32 height); private: - static constexpr std::size_t MAX_FINGERS = 16; - - // This is nn::hid::TouchScreenState - struct TouchScreenState { - s64 sampling_number{}; - s32 entry_count{}; - INSERT_PADDING_BYTES(4); // Reserved - std::array<Core::HID::TouchState, MAX_FINGERS> states{}; - }; - static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size"); - - struct TouchSharedMemory { - // This is nn::hid::detail::TouchScreenLifo - Lifo<TouchScreenState, hid_entry_count> touch_screen_lifo{}; - static_assert(sizeof(touch_screen_lifo) == 0x2C38, "touch_screen_lifo is an invalid size"); - INSERT_PADDING_WORDS(0xF2); - }; - static_assert(sizeof(TouchSharedMemory) == 0x3000, "TouchSharedMemory is an invalid size"); - TouchScreenState next_state{}; - TouchSharedMemory* shared_memory = nullptr; + TouchScreenSharedMemoryFormat& shared_memory; Core::HID::EmulatedConsole* console = nullptr; std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers{}; diff --git a/src/core/hle/service/hid/controllers/types/debug_pad_types.h b/src/core/hle/service/hid/controllers/types/debug_pad_types.h new file mode 100644 index 000000000..a96171b62 --- /dev/null +++ b/src/core/hle/service/hid/controllers/types/debug_pad_types.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "core/hid/hid_types.h" + +namespace Service::HID { + +// This is nn::hid::DebugPadAttribute +struct DebugPadAttribute { + union { + u32 raw{}; + BitField<0, 1, u32> connected; + }; +}; +static_assert(sizeof(DebugPadAttribute) == 0x4, "DebugPadAttribute is an invalid size"); + +// This is nn::hid::DebugPadState +struct DebugPadState { + s64 sampling_number{}; + DebugPadAttribute attribute{}; + Core::HID::DebugPadButton pad_state{}; + Core::HID::AnalogStickState r_stick{}; + Core::HID::AnalogStickState l_stick{}; +}; +static_assert(sizeof(DebugPadState) == 0x20, "DebugPadState is an invalid state"); + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/types/gesture_types.h b/src/core/hle/service/hid/controllers/types/gesture_types.h new file mode 100644 index 000000000..b4f034cd3 --- /dev/null +++ b/src/core/hle/service/hid/controllers/types/gesture_types.h @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <array> +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/point.h" + +namespace Service::HID { +static constexpr size_t MAX_FINGERS = 16; +static constexpr size_t MAX_POINTS = 4; + +// This is nn::hid::GestureType +enum class GestureType : u32 { + Idle, // Nothing touching the screen + Complete, // Set at the end of a touch event + Cancel, // Set when the number of fingers change + Touch, // A finger just touched the screen + Press, // Set if last type is touch and the finger hasn't moved + Tap, // Fast press then release + Pan, // All points moving together across the screen + Swipe, // Fast press movement and release of a single point + Pinch, // All points moving away/closer to the midpoint + Rotate, // All points rotating from the midpoint +}; + +// This is nn::hid::GestureDirection +enum class GestureDirection : u32 { + None, + Left, + Up, + Right, + Down, +}; + +// This is nn::hid::GestureAttribute +struct GestureAttribute { + union { + u32 raw{}; + + BitField<4, 1, u32> is_new_touch; + BitField<8, 1, u32> is_double_tap; + }; +}; +static_assert(sizeof(GestureAttribute) == 4, "GestureAttribute is an invalid size"); + +// This is nn::hid::GestureState +struct GestureState { + s64 sampling_number{}; + s64 detection_count{}; + GestureType type{GestureType::Idle}; + GestureDirection direction{GestureDirection::None}; + Common::Point<s32> pos{}; + Common::Point<s32> delta{}; + f32 vel_x{}; + f32 vel_y{}; + GestureAttribute attributes{}; + f32 scale{}; + f32 rotation_angle{}; + s32 point_count{}; + std::array<Common::Point<s32>, 4> points{}; +}; +static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size"); + +struct GestureProperties { + std::array<Common::Point<s32>, MAX_POINTS> points{}; + std::size_t active_points{}; + Common::Point<s32> mid_point{}; + s64 detection_count{}; + u64 delta_time{}; + f32 average_distance{}; + f32 angle{}; +}; + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/types/keyboard_types.h b/src/core/hle/service/hid/controllers/types/keyboard_types.h new file mode 100644 index 000000000..f44a536b9 --- /dev/null +++ b/src/core/hle/service/hid/controllers/types/keyboard_types.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" +#include "core/hid/hid_types.h" + +namespace Service::HID { + +// This is nn::hid::detail::KeyboardState +struct KeyboardState { + s64 sampling_number{}; + Core::HID::KeyboardModifier modifier{}; + Core::HID::KeyboardAttribute attribute{}; + Core::HID::KeyboardKey key{}; +}; +static_assert(sizeof(KeyboardState) == 0x30, "KeyboardState is an invalid size"); + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/types/mouse_types.h b/src/core/hle/service/hid/controllers/types/mouse_types.h new file mode 100644 index 000000000..8bd6e167c --- /dev/null +++ b/src/core/hle/service/hid/controllers/types/mouse_types.h @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace Service::HID {} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/types/npad_types.h b/src/core/hle/service/hid/controllers/types/npad_types.h new file mode 100644 index 000000000..a5ce2562b --- /dev/null +++ b/src/core/hle/service/hid/controllers/types/npad_types.h @@ -0,0 +1,254 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hid/hid_types.h" + +namespace Service::HID { +static constexpr std::size_t NpadCount = 10; + +// This is nn::hid::NpadJoyHoldType +enum class NpadJoyHoldType : u64 { + Vertical = 0, + Horizontal = 1, +}; + +// This is nn::hid::NpadJoyAssignmentMode +enum class NpadJoyAssignmentMode : u32 { + Dual = 0, + Single = 1, +}; + +// This is nn::hid::NpadJoyDeviceType +enum class NpadJoyDeviceType : s64 { + Left = 0, + Right = 1, +}; + +// This is nn::hid::NpadHandheldActivationMode +enum class NpadHandheldActivationMode : u64 { + Dual = 0, + Single = 1, + None = 2, + MaxActivationMode = 3, +}; + +// This is nn::hid::system::AppletFooterUiAttributesSet +struct AppletFooterUiAttributes { + INSERT_PADDING_BYTES(0x4); +}; + +// This is nn::hid::system::AppletFooterUiType +enum class AppletFooterUiType : u8 { + None = 0, + HandheldNone = 1, + HandheldJoyConLeftOnly = 2, + HandheldJoyConRightOnly = 3, + HandheldJoyConLeftJoyConRight = 4, + JoyDual = 5, + JoyDualLeftOnly = 6, + JoyDualRightOnly = 7, + JoyLeftHorizontal = 8, + JoyLeftVertical = 9, + JoyRightHorizontal = 10, + JoyRightVertical = 11, + SwitchProController = 12, + CompatibleProController = 13, + CompatibleJoyCon = 14, + LarkHvc1 = 15, + LarkHvc2 = 16, + LarkNesLeft = 17, + LarkNesRight = 18, + Lucia = 19, + Verification = 20, + Lagon = 21, +}; + +using AppletFooterUiVariant = u8; + +// This is "nn::hid::system::AppletDetailedUiType". +struct AppletDetailedUiType { + AppletFooterUiVariant ui_variant; + INSERT_PADDING_BYTES(0x2); + AppletFooterUiType footer; +}; +static_assert(sizeof(AppletDetailedUiType) == 0x4, "AppletDetailedUiType is an invalid size"); +// This is nn::hid::NpadCommunicationMode +enum class NpadCommunicationMode : u64 { + Mode_5ms = 0, + Mode_10ms = 1, + Mode_15ms = 2, + Default = 3, +}; + +enum class NpadRevision : u32 { + Revision0 = 0, + Revision1 = 1, + Revision2 = 2, + Revision3 = 3, +}; + +// This is nn::hid::detail::ColorAttribute +enum class ColorAttribute : u32 { + Ok = 0, + ReadError = 1, + NoController = 2, +}; +static_assert(sizeof(ColorAttribute) == 4, "ColorAttribute is an invalid size"); + +// This is nn::hid::detail::NpadFullKeyColorState +struct NpadFullKeyColorState { + ColorAttribute attribute{ColorAttribute::NoController}; + Core::HID::NpadControllerColor fullkey{}; +}; +static_assert(sizeof(NpadFullKeyColorState) == 0xC, "NpadFullKeyColorState is an invalid size"); + +// This is nn::hid::detail::NpadJoyColorState +struct NpadJoyColorState { + ColorAttribute attribute{ColorAttribute::NoController}; + Core::HID::NpadControllerColor left{}; + Core::HID::NpadControllerColor right{}; +}; +static_assert(sizeof(NpadJoyColorState) == 0x14, "NpadJoyColorState is an invalid size"); + +// This is nn::hid::NpadAttribute +struct NpadAttribute { + union { + u32 raw{}; + BitField<0, 1, u32> is_connected; + BitField<1, 1, u32> is_wired; + BitField<2, 1, u32> is_left_connected; + BitField<3, 1, u32> is_left_wired; + BitField<4, 1, u32> is_right_connected; + BitField<5, 1, u32> is_right_wired; + }; +}; +static_assert(sizeof(NpadAttribute) == 4, "NpadAttribute is an invalid size"); + +// This is nn::hid::NpadFullKeyState +// This is nn::hid::NpadHandheldState +// This is nn::hid::NpadJoyDualState +// This is nn::hid::NpadJoyLeftState +// This is nn::hid::NpadJoyRightState +// This is nn::hid::NpadPalmaState +// This is nn::hid::NpadSystemExtState +struct NPadGenericState { + s64_le sampling_number{}; + Core::HID::NpadButtonState npad_buttons{}; + Core::HID::AnalogStickState l_stick{}; + Core::HID::AnalogStickState r_stick{}; + NpadAttribute connection_status{}; + INSERT_PADDING_BYTES(4); // Reserved +}; +static_assert(sizeof(NPadGenericState) == 0x28, "NPadGenericState is an invalid size"); + +// This is nn::hid::server::NpadGcTriggerState +struct NpadGcTriggerState { + s64 sampling_number{}; + s32 l_analog{}; + s32 r_analog{}; +}; +static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size"); + +// This is nn::hid::NpadSystemProperties +struct NPadSystemProperties { + union { + s64 raw{}; + BitField<0, 1, s64> is_charging_joy_dual; + BitField<1, 1, s64> is_charging_joy_left; + BitField<2, 1, s64> is_charging_joy_right; + BitField<3, 1, s64> is_powered_joy_dual; + BitField<4, 1, s64> is_powered_joy_left; + BitField<5, 1, s64> is_powered_joy_right; + BitField<9, 1, s64> is_system_unsupported_button; + BitField<10, 1, s64> is_system_ext_unsupported_button; + BitField<11, 1, s64> is_vertical; + BitField<12, 1, s64> is_horizontal; + BitField<13, 1, s64> use_plus; + BitField<14, 1, s64> use_minus; + BitField<15, 1, s64> use_directional_buttons; + }; +}; +static_assert(sizeof(NPadSystemProperties) == 0x8, "NPadSystemProperties is an invalid size"); + +// This is nn::hid::NpadSystemButtonProperties +struct NpadSystemButtonProperties { + union { + s32 raw{}; + BitField<0, 1, s32> is_home_button_protection_enabled; + }; +}; +static_assert(sizeof(NpadSystemButtonProperties) == 0x4, "NPadButtonProperties is an invalid size"); + +// This is nn::hid::system::DeviceType +struct DeviceType { + union { + u32 raw{}; + BitField<0, 1, s32> fullkey; + BitField<1, 1, s32> debug_pad; + BitField<2, 1, s32> handheld_left; + BitField<3, 1, s32> handheld_right; + BitField<4, 1, s32> joycon_left; + BitField<5, 1, s32> joycon_right; + BitField<6, 1, s32> palma; + BitField<7, 1, s32> lark_hvc_left; + BitField<8, 1, s32> lark_hvc_right; + BitField<9, 1, s32> lark_nes_left; + BitField<10, 1, s32> lark_nes_right; + BitField<11, 1, s32> handheld_lark_hvc_left; + BitField<12, 1, s32> handheld_lark_hvc_right; + BitField<13, 1, s32> handheld_lark_nes_left; + BitField<14, 1, s32> handheld_lark_nes_right; + BitField<15, 1, s32> lucia; + BitField<16, 1, s32> lagon; + BitField<17, 1, s32> lager; + BitField<31, 1, s32> system; + }; +}; + +// This is nn::hid::detail::NfcXcdDeviceHandleStateImpl +struct NfcXcdDeviceHandleStateImpl { + u64 handle{}; + bool is_available{}; + bool is_activated{}; + INSERT_PADDING_BYTES(0x6); // Reserved + u64 sampling_number{}; +}; +static_assert(sizeof(NfcXcdDeviceHandleStateImpl) == 0x18, + "NfcXcdDeviceHandleStateImpl is an invalid size"); + +// This is nn::hid::NpadLarkType +enum class NpadLarkType : u32 { + Invalid, + H1, + H2, + NL, + NR, +}; + +// This is nn::hid::NpadLuciaType +enum class NpadLuciaType : u32 { + Invalid, + J, + E, + U, +}; + +// This is nn::hid::NpadLagonType +enum class NpadLagonType : u32 { + Invalid, +}; + +// This is nn::hid::NpadLagerType +enum class NpadLagerType : u32 { + Invalid, + J, + E, + U, +}; + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/types/touch_types.h b/src/core/hle/service/hid/controllers/types/touch_types.h new file mode 100644 index 000000000..efeaa796d --- /dev/null +++ b/src/core/hle/service/hid/controllers/types/touch_types.h @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include <array> + +#include <array> +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/point.h" +#include "core/hid/hid_types.h" + +namespace Service::HID { +static constexpr std::size_t MAX_FINGERS = 16; +static constexpr size_t MAX_POINTS = 4; + +// This is nn::hid::GestureType +enum class GestureType : u32 { + Idle, // Nothing touching the screen + Complete, // Set at the end of a touch event + Cancel, // Set when the number of fingers change + Touch, // A finger just touched the screen + Press, // Set if last type is touch and the finger hasn't moved + Tap, // Fast press then release + Pan, // All points moving together across the screen + Swipe, // Fast press movement and release of a single point + Pinch, // All points moving away/closer to the midpoint + Rotate, // All points rotating from the midpoint +}; + +// This is nn::hid::GestureDirection +enum class GestureDirection : u32 { + None, + Left, + Up, + Right, + Down, +}; + +// This is nn::hid::GestureAttribute +struct GestureAttribute { + union { + u32 raw{}; + + BitField<4, 1, u32> is_new_touch; + BitField<8, 1, u32> is_double_tap; + }; +}; +static_assert(sizeof(GestureAttribute) == 4, "GestureAttribute is an invalid size"); + +// This is nn::hid::GestureState +struct GestureState { + s64 sampling_number{}; + s64 detection_count{}; + GestureType type{GestureType::Idle}; + GestureDirection direction{GestureDirection::None}; + Common::Point<s32> pos{}; + Common::Point<s32> delta{}; + f32 vel_x{}; + f32 vel_y{}; + GestureAttribute attributes{}; + f32 scale{}; + f32 rotation_angle{}; + s32 point_count{}; + std::array<Common::Point<s32>, 4> points{}; +}; +static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size"); + +struct GestureProperties { + std::array<Common::Point<s32>, MAX_POINTS> points{}; + std::size_t active_points{}; + Common::Point<s32> mid_point{}; + s64 detection_count{}; + u64 delta_time{}; + f32 average_distance{}; + f32 angle{}; +}; + +// This is nn::hid::TouchScreenState +struct TouchScreenState { + s64 sampling_number{}; + s32 entry_count{}; + INSERT_PADDING_BYTES(4); // Reserved + std::array<Core::HID::TouchState, MAX_FINGERS> states{}; +}; +static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size"); + +} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp deleted file mode 100644 index 0aaed1fa7..000000000 --- a/src/core/hle/service/hid/controllers/xpad.cpp +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <cstring> -#include "common/common_types.h" -#include "core/core_timing.h" -#include "core/hid/hid_core.h" -#include "core/hle/service/hid/controllers/xpad.h" - -namespace Service::HID { -constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00; - -XPad::XPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_) : ControllerBase{hid_core_} { - static_assert(SHARED_MEMORY_OFFSET + sizeof(XpadSharedMemory) < shared_memory_size, - "XpadSharedMemory is bigger than the shared memory"); - shared_memory = std::construct_at( - reinterpret_cast<XpadSharedMemory*>(raw_shared_memory_ + SHARED_MEMORY_OFFSET)); -} -XPad::~XPad() = default; - -void XPad::OnInit() {} - -void XPad::OnRelease() {} - -void XPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { - if (!IsControllerActivated()) { - shared_memory->basic_xpad_lifo.buffer_count = 0; - shared_memory->basic_xpad_lifo.buffer_tail = 0; - return; - } - - const auto& last_entry = shared_memory->basic_xpad_lifo.ReadCurrentEntry().state; - next_state.sampling_number = last_entry.sampling_number + 1; - // TODO(ogniK): Update xpad states - - shared_memory->basic_xpad_lifo.WriteNextEntry(next_state); -} - -} // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h deleted file mode 100644 index 9e63a317a..000000000 --- a/src/core/hle/service/hid/controllers/xpad.h +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "common/bit_field.h" -#include "common/common_types.h" -#include "core/hid/hid_types.h" -#include "core/hle/service/hid/controllers/controller_base.h" -#include "core/hle/service/hid/ring_lifo.h" - -namespace Service::HID { -class XPad final : public ControllerBase { -public: - explicit XPad(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); - ~XPad() override; - - // Called when the controller is initialized - void OnInit() override; - - // When the controller is released - void OnRelease() override; - - // When the controller is requesting an update for the shared memory - void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; - -private: - // This is nn::hid::BasicXpadAttributeSet - struct BasicXpadAttributeSet { - union { - u32 raw{}; - BitField<0, 1, u32> is_connected; - BitField<1, 1, u32> is_wired; - BitField<2, 1, u32> is_left_connected; - BitField<3, 1, u32> is_left_wired; - BitField<4, 1, u32> is_right_connected; - BitField<5, 1, u32> is_right_wired; - }; - }; - static_assert(sizeof(BasicXpadAttributeSet) == 4, "BasicXpadAttributeSet is an invalid size"); - - // This is nn::hid::BasicXpadButtonSet - struct BasicXpadButtonSet { - union { - u32 raw{}; - // Button states - BitField<0, 1, u32> a; - BitField<1, 1, u32> b; - BitField<2, 1, u32> x; - BitField<3, 1, u32> y; - BitField<4, 1, u32> l_stick; - BitField<5, 1, u32> r_stick; - BitField<6, 1, u32> l; - BitField<7, 1, u32> r; - BitField<8, 1, u32> zl; - BitField<9, 1, u32> zr; - BitField<10, 1, u32> plus; - BitField<11, 1, u32> minus; - - // D-Pad - BitField<12, 1, u32> d_left; - BitField<13, 1, u32> d_up; - BitField<14, 1, u32> d_right; - BitField<15, 1, u32> d_down; - - // Left JoyStick - BitField<16, 1, u32> l_stick_left; - BitField<17, 1, u32> l_stick_up; - BitField<18, 1, u32> l_stick_right; - BitField<19, 1, u32> l_stick_down; - - // Right JoyStick - BitField<20, 1, u32> r_stick_left; - BitField<21, 1, u32> r_stick_up; - BitField<22, 1, u32> r_stick_right; - BitField<23, 1, u32> r_stick_down; - - // Not always active? - BitField<24, 1, u32> left_sl; - BitField<25, 1, u32> left_sr; - - BitField<26, 1, u32> right_sl; - BitField<27, 1, u32> right_sr; - - BitField<28, 1, u32> palma; - BitField<30, 1, u32> handheld_left_b; - }; - }; - static_assert(sizeof(BasicXpadButtonSet) == 4, "BasicXpadButtonSet is an invalid size"); - - // This is nn::hid::detail::BasicXpadState - struct BasicXpadState { - s64 sampling_number{}; - BasicXpadAttributeSet attributes{}; - BasicXpadButtonSet pad_states{}; - Core::HID::AnalogStickState l_stick{}; - Core::HID::AnalogStickState r_stick{}; - }; - static_assert(sizeof(BasicXpadState) == 0x20, "BasicXpadState is an invalid size"); - - struct XpadSharedMemory { - // This is nn::hid::detail::BasicXpadLifo - Lifo<BasicXpadState, hid_entry_count> basic_xpad_lifo{}; - static_assert(sizeof(basic_xpad_lifo) == 0x2C8, "basic_xpad_lifo is an invalid size"); - INSERT_PADDING_WORDS(0x4E); - }; - static_assert(sizeof(XpadSharedMemory) == 0x400, "XpadSharedMemory is an invalid size"); - - BasicXpadState next_state{}; - XpadSharedMemory* shared_memory = nullptr; -}; -} // namespace Service::HID diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp index b06ea467e..de24b0401 100644 --- a/src/core/hle/service/hid/hid_server.cpp +++ b/src/core/hle/service/hid/hid_server.cpp @@ -28,6 +28,7 @@ #include "core/hle/service/hid/controllers/seven_six_axis.h" #include "core/hle/service/hid/controllers/six_axis.h" #include "core/hle/service/hid/controllers/touchscreen.h" +#include "core/hle/service/hid/controllers/types/npad_types.h" namespace Service::HID { @@ -1099,7 +1100,7 @@ void IHidServer::GetPlayerLedPattern(HLERequestContext& ctx) { void IHidServer::ActivateNpadWithRevision(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; struct Parameters { - NPad::NpadRevision revision; + NpadRevision revision; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; }; @@ -1122,7 +1123,7 @@ void IHidServer::ActivateNpadWithRevision(HLERequestContext& ctx) { void IHidServer::SetNpadJoyHoldType(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto hold_type{rp.PopEnum<NPad::NpadJoyHoldType>()}; + const auto hold_type{rp.PopEnum<NpadJoyHoldType>()}; GetResourceManager()->GetNpad()->SetHoldType(hold_type); @@ -1157,8 +1158,8 @@ void IHidServer::SetNpadJoyAssignmentModeSingleByDefault(HLERequestContext& ctx) Core::HID::NpadIdType new_npad_id{}; auto controller = GetResourceManager()->GetNpad(); - controller->SetNpadMode(new_npad_id, parameters.npad_id, NPad::NpadJoyDeviceType::Left, - NPad::NpadJoyAssignmentMode::Single); + controller->SetNpadMode(new_npad_id, parameters.npad_id, NpadJoyDeviceType::Left, + NpadJoyAssignmentMode::Single); LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, parameters.applet_resource_user_id); @@ -1173,7 +1174,7 @@ void IHidServer::SetNpadJoyAssignmentModeSingle(HLERequestContext& ctx) { Core::HID::NpadIdType npad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - NPad::NpadJoyDeviceType npad_joy_device_type; + NpadJoyDeviceType npad_joy_device_type; }; static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); @@ -1182,7 +1183,7 @@ void IHidServer::SetNpadJoyAssignmentModeSingle(HLERequestContext& ctx) { Core::HID::NpadIdType new_npad_id{}; auto controller = GetResourceManager()->GetNpad(); controller->SetNpadMode(new_npad_id, parameters.npad_id, parameters.npad_joy_device_type, - NPad::NpadJoyAssignmentMode::Single); + NpadJoyAssignmentMode::Single); LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}", parameters.npad_id, parameters.applet_resource_user_id, @@ -1205,7 +1206,7 @@ void IHidServer::SetNpadJoyAssignmentModeDual(HLERequestContext& ctx) { Core::HID::NpadIdType new_npad_id{}; auto controller = GetResourceManager()->GetNpad(); - controller->SetNpadMode(new_npad_id, parameters.npad_id, {}, NPad::NpadJoyAssignmentMode::Dual); + controller->SetNpadMode(new_npad_id, parameters.npad_id, {}, NpadJoyAssignmentMode::Dual); LOG_DEBUG(Service_HID, "called, npad_id={}, applet_resource_user_id={}", parameters.npad_id, parameters.applet_resource_user_id); // Spams a lot when controller applet is open @@ -1257,7 +1258,7 @@ void IHidServer::StopLrAssignmentMode(HLERequestContext& ctx) { void IHidServer::SetNpadHandheldActivationMode(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto activation_mode{rp.PopEnum<NPad::NpadHandheldActivationMode>()}; + const auto activation_mode{rp.PopEnum<NpadHandheldActivationMode>()}; GetResourceManager()->GetNpad()->SetNpadHandheldActivationMode(activation_mode); @@ -1349,7 +1350,7 @@ void IHidServer::SetNpadJoyAssignmentModeSingleWithDestination(HLERequestContext Core::HID::NpadIdType npad_id; INSERT_PADDING_WORDS_NOINIT(1); u64 applet_resource_user_id; - NPad::NpadJoyDeviceType npad_joy_device_type; + NpadJoyDeviceType npad_joy_device_type; }; static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size."); @@ -1359,7 +1360,7 @@ void IHidServer::SetNpadJoyAssignmentModeSingleWithDestination(HLERequestContext auto controller = GetResourceManager()->GetNpad(); const auto is_reassigned = controller->SetNpadMode(new_npad_id, parameters.npad_id, parameters.npad_joy_device_type, - NPad::NpadJoyAssignmentMode::Single); + NpadJoyAssignmentMode::Single); LOG_INFO(Service_HID, "called, npad_id={}, applet_resource_user_id={}, npad_joy_device_type={}", parameters.npad_id, parameters.applet_resource_user_id, @@ -2315,7 +2316,7 @@ void IHidServer::SetDisallowedPalmaConnection(HLERequestContext& ctx) { void IHidServer::SetNpadCommunicationMode(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto applet_resource_user_id{rp.Pop<u64>()}; - const auto communication_mode{rp.PopEnum<NPad::NpadCommunicationMode>()}; + const auto communication_mode{rp.PopEnum<NpadCommunicationMode>()}; GetResourceManager()->GetNpad()->SetNpadCommunicationMode(communication_mode); diff --git a/src/core/hle/service/hid/hid_system_server.cpp b/src/core/hle/service/hid/hid_system_server.cpp index 4d33456a3..5cc88c4a1 100644 --- a/src/core/hle/service/hid/hid_system_server.cpp +++ b/src/core/hle/service/hid/hid_system_server.cpp @@ -5,6 +5,7 @@ #include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/hid/controllers/palma.h" #include "core/hle/service/hid/controllers/touchscreen.h" +#include "core/hle/service/hid/controllers/types/npad_types.h" #include "core/hle/service/hid/errors.h" #include "core/hle/service/hid/hid_system_server.h" #include "core/hle/service/hid/resource_manager.h" @@ -328,7 +329,7 @@ void IHidSystemServer::GetAppletDetailedUiType(HLERequestContext& ctx) { LOG_DEBUG(Service_HID, "called, npad_id_type={}", npad_id_type); // Spams a lot when controller applet is running - const NPad::AppletDetailedUiType detailed_ui_type = + const AppletDetailedUiType detailed_ui_type = GetResourceManager()->GetNpad()->GetAppletDetailedUiType(npad_id_type); IPC::ResponseBuilder rb{ctx, 3}; diff --git a/src/core/hle/service/hid/resource_manager.cpp b/src/core/hle/service/hid/resource_manager.cpp index 89cdc19cc..6c6cbd802 100644 --- a/src/core/hle/service/hid/resource_manager.cpp +++ b/src/core/hle/service/hid/resource_manager.cpp @@ -18,10 +18,10 @@ #include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/hid/controllers/palma.h" #include "core/hle/service/hid/controllers/seven_six_axis.h" +#include "core/hle/service/hid/controllers/shared_memory_format.h" #include "core/hle/service/hid/controllers/six_axis.h" #include "core/hle/service/hid/controllers/stubbed.h" #include "core/hle/service/hid/controllers/touchscreen.h" -#include "core/hle/service/hid/controllers/xpad.h" namespace Service::HID { @@ -45,40 +45,43 @@ void ResourceManager::Initialize() { return; } - u8* shared_memory = system.Kernel().GetHidSharedMem().GetPointer(); - debug_pad = std::make_shared<DebugPad>(system.HIDCore(), shared_memory); - mouse = std::make_shared<Mouse>(system.HIDCore(), shared_memory); - debug_mouse = std::make_shared<DebugMouse>(system.HIDCore(), shared_memory); - keyboard = std::make_shared<Keyboard>(system.HIDCore(), shared_memory); - unique_pad = std::make_shared<UniquePad>(system.HIDCore(), shared_memory); - npad = std::make_shared<NPad>(system.HIDCore(), shared_memory, service_context); - gesture = std::make_shared<Gesture>(system.HIDCore(), shared_memory); - touch_screen = std::make_shared<TouchScreen>(system.HIDCore(), shared_memory); - xpad = std::make_shared<XPad>(system.HIDCore(), shared_memory); + system.HIDCore().ReloadInputDevices(); + is_initialized = true; +} + +void ResourceManager::InitializeController(u64 aruid) { + SharedMemoryFormat* shared_memory = nullptr; + const auto result = applet_resource->GetSharedMemoryFormat(&shared_memory, aruid); + if (result.IsError()) { + return; + } + + debug_pad = std::make_shared<DebugPad>(system.HIDCore(), shared_memory->debug_pad); + mouse = std::make_shared<Mouse>(system.HIDCore(), shared_memory->mouse); + debug_mouse = std::make_shared<DebugMouse>(system.HIDCore(), shared_memory->debug_mouse); + keyboard = std::make_shared<Keyboard>(system.HIDCore(), shared_memory->keyboard); + unique_pad = std::make_shared<UniquePad>(system.HIDCore(), shared_memory->unique_pad.header); + npad = std::make_shared<NPad>(system.HIDCore(), shared_memory->npad, service_context); + gesture = std::make_shared<Gesture>(system.HIDCore(), shared_memory->gesture); + touch_screen = std::make_shared<TouchScreen>(system.HIDCore(), shared_memory->touch_screen); - palma = std::make_shared<Palma>(system.HIDCore(), shared_memory, service_context); + palma = std::make_shared<Palma>(system.HIDCore(), service_context); - home_button = std::make_shared<HomeButton>(system.HIDCore(), shared_memory); - sleep_button = std::make_shared<SleepButton>(system.HIDCore(), shared_memory); - capture_button = std::make_shared<CaptureButton>(system.HIDCore(), shared_memory); + home_button = std::make_shared<HomeButton>(system.HIDCore(), shared_memory->home_button.header); + sleep_button = + std::make_shared<SleepButton>(system.HIDCore(), shared_memory->sleep_button.header); + capture_button = + std::make_shared<CaptureButton>(system.HIDCore(), shared_memory->capture_button.header); + digitizer = std::make_shared<Digitizer>(system.HIDCore(), shared_memory->digitizer.header); six_axis = std::make_shared<SixAxis>(system.HIDCore(), npad); - console_six_axis = std::make_shared<ConsoleSixAxis>(system.HIDCore(), shared_memory); + console_six_axis = std::make_shared<ConsoleSixAxis>(system.HIDCore(), shared_memory->console); seven_six_axis = std::make_shared<SevenSixAxis>(system); - home_button->SetCommonHeaderOffset(0x4C00); - sleep_button->SetCommonHeaderOffset(0x4E00); - capture_button->SetCommonHeaderOffset(0x5000); - unique_pad->SetCommonHeaderOffset(0x5A00); - debug_mouse->SetCommonHeaderOffset(0x3DC00); - // Homebrew doesn't try to activate some controllers, so we activate them by default npad->Activate(); six_axis->Activate(); touch_screen->Activate(); - - system.HIDCore().ReloadInputDevices(); - is_initialized = true; } std::shared_ptr<AppletResource> ResourceManager::GetAppletResource() const { @@ -101,6 +104,10 @@ std::shared_ptr<DebugPad> ResourceManager::GetDebugPad() const { return debug_pad; } +std::shared_ptr<Digitizer> ResourceManager::GetDigitizer() const { + return digitizer; +} + std::shared_ptr<Gesture> ResourceManager::GetGesture() const { return gesture; } @@ -163,7 +170,11 @@ Result ResourceManager::CreateAppletResource(u64 aruid) { Result ResourceManager::CreateAppletResourceImpl(u64 aruid) { std::scoped_lock lock{shared_mutex}; - return applet_resource->CreateAppletResource(aruid); + const auto result = applet_resource->CreateAppletResource(aruid); + if (result.IsSuccess()) { + InitializeController(aruid); + } + return result; } Result ResourceManager::RegisterCoreAppletResource() { @@ -220,6 +231,7 @@ void ResourceManager::UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { auto& core_timing = system.CoreTiming(); debug_pad->OnUpdate(core_timing); + digitizer->OnUpdate(core_timing); unique_pad->OnUpdate(core_timing); gesture->OnUpdate(core_timing); touch_screen->OnUpdate(core_timing); @@ -227,7 +239,6 @@ void ResourceManager::UpdateControllers(std::uintptr_t user_data, home_button->OnUpdate(core_timing); sleep_button->OnUpdate(core_timing); capture_button->OnUpdate(core_timing); - xpad->OnUpdate(core_timing); } void ResourceManager::UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { diff --git a/src/core/hle/service/hid/resource_manager.h b/src/core/hle/service/hid/resource_manager.h index 15c1beb1a..5ad7cb564 100644 --- a/src/core/hle/service/hid/resource_manager.h +++ b/src/core/hle/service/hid/resource_manager.h @@ -31,10 +31,10 @@ class Palma; class SevenSixAxis; class SixAxis; class TouchScreen; -class XPad; using CaptureButton = Controller_Stubbed; -using DebugMouse = Controller_Stubbed; +using DebugMouse = Mouse; +using Digitizer = Controller_Stubbed; using HomeButton = Controller_Stubbed; using SleepButton = Controller_Stubbed; using UniquePad = Controller_Stubbed; @@ -46,12 +46,14 @@ public: ~ResourceManager(); void Initialize(); + void InitializeController(u64 aruid); std::shared_ptr<AppletResource> GetAppletResource() const; std::shared_ptr<CaptureButton> GetCaptureButton() const; std::shared_ptr<ConsoleSixAxis> GetConsoleSixAxis() const; std::shared_ptr<DebugMouse> GetDebugMouse() const; std::shared_ptr<DebugPad> GetDebugPad() const; + std::shared_ptr<Digitizer> GetDigitizer() const; std::shared_ptr<Gesture> GetGesture() const; std::shared_ptr<HomeButton> GetHomeButton() const; std::shared_ptr<Keyboard> GetKeyboard() const; @@ -96,6 +98,7 @@ private: std::shared_ptr<ConsoleSixAxis> console_six_axis = nullptr; std::shared_ptr<DebugMouse> debug_mouse = nullptr; std::shared_ptr<DebugPad> debug_pad = nullptr; + std::shared_ptr<Digitizer> digitizer = nullptr; std::shared_ptr<Gesture> gesture = nullptr; std::shared_ptr<HomeButton> home_button = nullptr; std::shared_ptr<Keyboard> keyboard = nullptr; @@ -107,7 +110,6 @@ private: std::shared_ptr<SleepButton> sleep_button = nullptr; std::shared_ptr<TouchScreen> touch_screen = nullptr; std::shared_ptr<UniquePad> unique_pad = nullptr; - std::shared_ptr<XPad> xpad = nullptr; // TODO: Create these resources // std::shared_ptr<AudioControl> audio_control = nullptr; diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp index 75bf31e32..2fef6cc1a 100644 --- a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp +++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp @@ -204,8 +204,9 @@ Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u // Record the display id. m_display_id = display_id; - // Create a layer for the display. + // Create and open a layer for the display. m_layer_id = m_flinger.CreateLayer(m_display_id).value(); + m_flinger.OpenLayer(m_layer_id); // Set up the buffer. m_buffer_id = m_next_buffer_id++; diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp index 0745434c5..6352b09a9 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.cpp +++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp @@ -176,17 +176,37 @@ void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id) { display.CreateLayer(layer_id, buffer_id, nvdrv->container); } +void Nvnflinger::OpenLayer(u64 layer_id) { + const auto lock_guard = Lock(); + + for (auto& display : displays) { + if (auto* layer = display.FindLayer(layer_id); layer) { + layer->Open(); + } + } +} + void Nvnflinger::CloseLayer(u64 layer_id) { const auto lock_guard = Lock(); for (auto& display : displays) { - display.CloseLayer(layer_id); + if (auto* layer = display.FindLayer(layer_id); layer) { + layer->Close(); + } + } +} + +void Nvnflinger::DestroyLayer(u64 layer_id) { + const auto lock_guard = Lock(); + + for (auto& display : displays) { + display.DestroyLayer(layer_id); } } std::optional<u32> Nvnflinger::FindBufferQueueId(u64 display_id, u64 layer_id) { const auto lock_guard = Lock(); - const auto* const layer = FindOrCreateLayer(display_id, layer_id); + const auto* const layer = FindLayer(display_id, layer_id); if (layer == nullptr) { return std::nullopt; @@ -240,24 +260,6 @@ VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) { return display->FindLayer(layer_id); } -VI::Layer* Nvnflinger::FindOrCreateLayer(u64 display_id, u64 layer_id) { - auto* const display = FindDisplay(display_id); - - if (display == nullptr) { - return nullptr; - } - - auto* layer = display->FindLayer(layer_id); - - if (layer == nullptr) { - LOG_DEBUG(Service_Nvnflinger, "Layer at id {} not found. Trying to create it.", layer_id); - CreateLayerAtId(*display, layer_id); - return display->FindLayer(layer_id); - } - - return layer; -} - void Nvnflinger::Compose() { for (auto& display : displays) { // Trigger vsync for this display at the end of drawing diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h index f5d73acdb..871285764 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.h +++ b/src/core/hle/service/nvnflinger/nvnflinger.h @@ -73,9 +73,15 @@ public: /// If an invalid display ID is specified, then an empty optional is returned. [[nodiscard]] std::optional<u64> CreateLayer(u64 display_id); + /// Opens a layer on all displays for the given layer ID. + void OpenLayer(u64 layer_id); + /// Closes a layer on all displays for the given layer ID. void CloseLayer(u64 layer_id); + /// Destroys the given layer ID. + void DestroyLayer(u64 layer_id); + /// Finds the buffer queue ID of the specified layer in the specified display. /// /// If an invalid display ID or layer ID is provided, then an empty optional is returned. @@ -117,11 +123,6 @@ private: /// Finds the layer identified by the specified ID in the desired display. [[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id); - /// Finds the layer identified by the specified ID in the desired display, - /// or creates the layer if it is not found. - /// To be used when the system expects the specified ID to already exist. - [[nodiscard]] VI::Layer* FindOrCreateLayer(u64 display_id, u64 layer_id); - /// Creates a layer with the specified layer ID in the desired display. void CreateLayerAtId(VI::Display& display, u64 layer_id); diff --git a/src/core/hle/service/set/appln_settings.cpp b/src/core/hle/service/set/appln_settings.cpp new file mode 100644 index 000000000..a5d802757 --- /dev/null +++ b/src/core/hle/service/set/appln_settings.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/set/appln_settings.h" + +namespace Service::Set { + +ApplnSettings DefaultApplnSettings() { + return {}; +} + +} // namespace Service::Set diff --git a/src/core/hle/service/set/appln_settings.h b/src/core/hle/service/set/appln_settings.h new file mode 100644 index 000000000..126375860 --- /dev/null +++ b/src/core/hle/service/set/appln_settings.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <cstddef> + +#include "common/common_types.h" + +namespace Service::Set { +struct ApplnSettings { + std::array<u8, 0x10> reserved_000; + + // nn::util::Uuid MiiAuthorId, copied from system settings 0x94B0 + std::array<u8, 0x10> mii_author_id; + + std::array<u8, 0x30> reserved_020; + + // nn::settings::system::ServiceDiscoveryControlSettings + std::array<u8, 0x4> service_discovery_control_settings; + + std::array<u8, 0x20> reserved_054; + + bool in_repair_process_enable_flag; + + std::array<u8, 0x3> pad_075; +}; +static_assert(offsetof(ApplnSettings, mii_author_id) == 0x10); +static_assert(offsetof(ApplnSettings, service_discovery_control_settings) == 0x50); +static_assert(offsetof(ApplnSettings, in_repair_process_enable_flag) == 0x74); +static_assert(sizeof(ApplnSettings) == 0x78, "ApplnSettings has the wrong size!"); + +ApplnSettings DefaultApplnSettings(); + +} // namespace Service::Set diff --git a/src/core/hle/service/set/device_settings.cpp b/src/core/hle/service/set/device_settings.cpp new file mode 100644 index 000000000..e423ce38a --- /dev/null +++ b/src/core/hle/service/set/device_settings.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/set/device_settings.h" + +namespace Service::Set { + +DeviceSettings DefaultDeviceSettings() { + return {}; +} + +} // namespace Service::Set diff --git a/src/core/hle/service/set/device_settings.h b/src/core/hle/service/set/device_settings.h new file mode 100644 index 000000000..f291d0ebe --- /dev/null +++ b/src/core/hle/service/set/device_settings.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <cstddef> + +#include "common/common_types.h" + +namespace Service::Set { +struct DeviceSettings { + std::array<u8, 0x10> reserved_000; + + // nn::settings::BatteryLot + std::array<u8, 0x18> ptm_battery_lot; + // nn::settings::system::PtmFuelGaugeParameter + std::array<u8, 0x18> ptm_fuel_gauge_parameter; + u8 ptm_battery_version; + // nn::settings::system::PtmCycleCountReliability + u32 ptm_cycle_count_reliability; + + std::array<u8, 0x48> reserved_048; + + // nn::settings::system::AnalogStickUserCalibration L + std::array<u8, 0x10> analog_user_stick_calibration_l; + // nn::settings::system::AnalogStickUserCalibration R + std::array<u8, 0x10> analog_user_stick_calibration_r; + + std::array<u8, 0x20> reserved_0B0; + + // nn::settings::system::ConsoleSixAxisSensorAccelerationBias + std::array<u8, 0xC> console_six_axis_sensor_acceleration_bias; + // nn::settings::system::ConsoleSixAxisSensorAngularVelocityBias + std::array<u8, 0xC> console_six_axis_sensor_angular_velocity_bias; + // nn::settings::system::ConsoleSixAxisSensorAccelerationGain + std::array<u8, 0x24> console_six_axis_sensor_acceleration_gain; + // nn::settings::system::ConsoleSixAxisSensorAngularVelocityGain + std::array<u8, 0x24> console_six_axis_sensor_angular_velocity_gain; + // nn::settings::system::ConsoleSixAxisSensorAngularVelocityTimeBias + std::array<u8, 0xC> console_six_axis_sensor_angular_velocity_time_bias; + // nn::settings::system::ConsoleSixAxisSensorAngularAcceleration + std::array<u8, 0x24> console_six_axis_sensor_angular_acceleration; +}; +static_assert(offsetof(DeviceSettings, ptm_battery_lot) == 0x10); +static_assert(offsetof(DeviceSettings, ptm_cycle_count_reliability) == 0x44); +static_assert(offsetof(DeviceSettings, analog_user_stick_calibration_l) == 0x90); +static_assert(offsetof(DeviceSettings, console_six_axis_sensor_acceleration_bias) == 0xD0); +static_assert(offsetof(DeviceSettings, console_six_axis_sensor_angular_acceleration) == 0x13C); +static_assert(sizeof(DeviceSettings) == 0x160, "DeviceSettings has the wrong size!"); + +DeviceSettings DefaultDeviceSettings(); + +} // namespace Service::Set diff --git a/src/core/hle/service/set/private_settings.cpp b/src/core/hle/service/set/private_settings.cpp new file mode 100644 index 000000000..70bf65727 --- /dev/null +++ b/src/core/hle/service/set/private_settings.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/set/private_settings.h" + +namespace Service::Set { + +PrivateSettings DefaultPrivateSettings() { + return {}; +} + +} // namespace Service::Set diff --git a/src/core/hle/service/set/private_settings.h b/src/core/hle/service/set/private_settings.h new file mode 100644 index 000000000..b63eaf45c --- /dev/null +++ b/src/core/hle/service/set/private_settings.h @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/uuid.h" +#include "core/hle/service/time/clock_types.h" + +namespace Service::Set { + +/// This is nn::settings::system::InitialLaunchFlag +struct InitialLaunchFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> InitialLaunchCompletionFlag; + BitField<8, 1, u32> InitialLaunchUserAdditionFlag; + BitField<16, 1, u32> InitialLaunchTimestampFlag; + }; +}; +static_assert(sizeof(InitialLaunchFlag) == 4, "InitialLaunchFlag is an invalid size"); + +/// This is nn::settings::system::InitialLaunchSettings +struct InitialLaunchSettings { + InitialLaunchFlag flags; + INSERT_PADDING_BYTES(0x4); + Service::Time::Clock::SteadyClockTimePoint timestamp; +}; +static_assert(sizeof(InitialLaunchSettings) == 0x20, "InitialLaunchSettings is incorrect size"); + +#pragma pack(push, 4) +struct InitialLaunchSettingsPacked { + InitialLaunchFlag flags; + Service::Time::Clock::SteadyClockTimePoint timestamp; +}; +#pragma pack(pop) +static_assert(sizeof(InitialLaunchSettingsPacked) == 0x1C, + "InitialLaunchSettingsPacked is incorrect size"); + +struct PrivateSettings { + std::array<u8, 0x10> reserved_00; + + // nn::settings::system::InitialLaunchSettings + InitialLaunchSettings initial_launch_settings; + + std::array<u8, 0x20> reserved_30; + + Common::UUID external_clock_source_id; + s64 shutdown_rtc_value; + s64 external_steady_clock_internal_offset; + + std::array<u8, 0x60> reserved_70; + + // nn::settings::system::PlatformRegion + std::array<u8, 0x4> platform_region; + + std::array<u8, 0x4> reserved_D4; +}; +static_assert(offsetof(PrivateSettings, initial_launch_settings) == 0x10); +static_assert(offsetof(PrivateSettings, external_clock_source_id) == 0x50); +static_assert(offsetof(PrivateSettings, reserved_70) == 0x70); +static_assert(offsetof(PrivateSettings, platform_region) == 0xD0); +static_assert(sizeof(PrivateSettings) == 0xD8, "PrivateSettings has the wrong size!"); + +PrivateSettings DefaultPrivateSettings(); + +} // namespace Service::Set diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index b61a3560d..6ef3da410 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -4,35 +4,13 @@ #pragma once #include "core/hle/service/service.h" +#include "core/hle/service/set/system_settings.h" namespace Core { class System; } namespace Service::Set { - -/// This is "nn::settings::LanguageCode", which is a NUL-terminated string stored in a u64. -enum class LanguageCode : u64 { - JA = 0x000000000000616A, - EN_US = 0x00000053552D6E65, - FR = 0x0000000000007266, - DE = 0x0000000000006564, - IT = 0x0000000000007469, - ES = 0x0000000000007365, - ZH_CN = 0x0000004E432D687A, - KO = 0x0000000000006F6B, - NL = 0x0000000000006C6E, - PT = 0x0000000000007470, - RU = 0x0000000000007572, - ZH_TW = 0x00000057542D687A, - EN_GB = 0x00000042472D6E65, - FR_CA = 0x00000041432D7266, - ES_419 = 0x00003931342D7365, - ZH_HANS = 0x00736E61482D687A, - ZH_HANT = 0x00746E61482D687A, - PT_BR = 0x00000052422D7470, -}; - enum class KeyboardLayout : u64 { Japanese = 0, EnglishUs = 1, diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp index 48304e6d1..0653779d5 100644 --- a/src/core/hle/service/set/set_sys.cpp +++ b/src/core/hle/service/set/set_sys.cpp @@ -1,7 +1,12 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <fstream> + #include "common/assert.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/settings.h" #include "common/string_util.h" @@ -19,6 +24,16 @@ namespace Service::Set { +namespace { +constexpr u32 SETTINGS_VERSION{1u}; +constexpr auto SETTINGS_MAGIC = Common::MakeMagic('y', 'u', 'z', 'u', '_', 's', 'e', 't'); +struct SettingsHeader { + u64 magic; + u32 version; + u32 reserved; +}; +} // Anonymous namespace + Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& system, GetFirmwareVersionType type) { constexpr u64 FirmwareVersionSystemDataId = 0x0100000000000809; @@ -72,11 +87,120 @@ Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System& return ResultSuccess; } +bool SET_SYS::LoadSettingsFile(std::filesystem::path& path, auto&& default_func) { + using settings_type = decltype(default_func()); + + if (!Common::FS::CreateDirs(path)) { + return false; + } + + auto settings_file = path / "settings.dat"; + auto exists = std::filesystem::exists(settings_file); + auto file_size_ok = exists && std::filesystem::file_size(settings_file) == + sizeof(SettingsHeader) + sizeof(settings_type); + + auto ResetToDefault = [&]() { + auto default_settings{default_func()}; + + SettingsHeader hdr{ + .magic = SETTINGS_MAGIC, + .version = SETTINGS_VERSION, + .reserved = 0u, + }; + + std::ofstream out_settings_file(settings_file, std::ios::out | std::ios::binary); + out_settings_file.write(reinterpret_cast<const char*>(&hdr), sizeof(hdr)); + out_settings_file.write(reinterpret_cast<const char*>(&default_settings), + sizeof(settings_type)); + out_settings_file.flush(); + out_settings_file.close(); + }; + + constexpr auto IsHeaderValid = [](std::ifstream& file) -> bool { + if (!file.is_open()) { + return false; + } + SettingsHeader hdr{}; + file.read(reinterpret_cast<char*>(&hdr), sizeof(hdr)); + return hdr.magic == SETTINGS_MAGIC && hdr.version == SETTINGS_VERSION; + }; + + if (!exists || !file_size_ok) { + ResetToDefault(); + } + + std::ifstream file(settings_file, std::ios::binary | std::ios::in); + if (!IsHeaderValid(file)) { + file.close(); + ResetToDefault(); + file = std::ifstream(settings_file, std::ios::binary | std::ios::in); + if (!IsHeaderValid(file)) { + return false; + } + } + + if constexpr (std::is_same_v<settings_type, PrivateSettings>) { + file.read(reinterpret_cast<char*>(&m_private_settings), sizeof(settings_type)); + } else if constexpr (std::is_same_v<settings_type, DeviceSettings>) { + file.read(reinterpret_cast<char*>(&m_device_settings), sizeof(settings_type)); + } else if constexpr (std::is_same_v<settings_type, ApplnSettings>) { + file.read(reinterpret_cast<char*>(&m_appln_settings), sizeof(settings_type)); + } else if constexpr (std::is_same_v<settings_type, SystemSettings>) { + file.read(reinterpret_cast<char*>(&m_system_settings), sizeof(settings_type)); + } else { + UNREACHABLE(); + } + file.close(); + + return true; +} + +bool SET_SYS::StoreSettingsFile(std::filesystem::path& path, auto& settings) { + using settings_type = std::decay_t<decltype(settings)>; + + if (!Common::FS::IsDir(path)) { + return false; + } + + auto settings_base = path / "settings"; + auto settings_tmp_file = settings_base; + settings_tmp_file = settings_tmp_file.replace_extension("tmp"); + std::ofstream file(settings_tmp_file, std::ios::binary | std::ios::out); + if (!file.is_open()) { + return false; + } + + SettingsHeader hdr{ + .magic = SETTINGS_MAGIC, + .version = SETTINGS_VERSION, + .reserved = 0u, + }; + file.write(reinterpret_cast<const char*>(&hdr), sizeof(hdr)); + + if constexpr (std::is_same_v<settings_type, PrivateSettings>) { + file.write(reinterpret_cast<const char*>(&m_private_settings), sizeof(settings_type)); + } else if constexpr (std::is_same_v<settings_type, DeviceSettings>) { + file.write(reinterpret_cast<const char*>(&m_device_settings), sizeof(settings_type)); + } else if constexpr (std::is_same_v<settings_type, ApplnSettings>) { + file.write(reinterpret_cast<const char*>(&m_appln_settings), sizeof(settings_type)); + } else if constexpr (std::is_same_v<settings_type, SystemSettings>) { + file.write(reinterpret_cast<const char*>(&m_system_settings), sizeof(settings_type)); + } else { + UNREACHABLE(); + } + file.close(); + + std::filesystem::rename(settings_tmp_file, settings_base.replace_extension("dat")); + + return true; +} + void SET_SYS::SetLanguageCode(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - language_code_setting = rp.PopEnum<LanguageCode>(); + m_system_settings.language_code = rp.PopEnum<LanguageCode>(); + SetSaveNeeded(); - LOG_INFO(Service_SET, "called, language_code={}", language_code_setting); + LOG_INFO(Service_SET, "called, language_code={}", m_system_settings.language_code); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -112,19 +236,68 @@ void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) { rb.Push(result); } +void SET_SYS::GetExternalSteadyClockSourceId(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + Common::UUID id{}; + auto res = GetExternalSteadyClockSourceId(id); + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(Common::UUID) / sizeof(u32)}; + rb.Push(res); + rb.PushRaw(id); +} + +void SET_SYS::SetExternalSteadyClockSourceId(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + IPC::RequestParser rp{ctx}; + auto id{rp.PopRaw<Common::UUID>()}; + + auto res = SetExternalSteadyClockSourceId(id); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + +void SET_SYS::GetUserSystemClockContext(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + Service::Time::Clock::SystemClockContext context{}; + auto res = GetUserSystemClockContext(context); + + IPC::ResponseBuilder rb{ctx, + 2 + sizeof(Service::Time::Clock::SystemClockContext) / sizeof(u32)}; + rb.Push(res); + rb.PushRaw(context); +} + +void SET_SYS::SetUserSystemClockContext(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + IPC::RequestParser rp{ctx}; + auto context{rp.PopRaw<Service::Time::Clock::SystemClockContext>()}; + + auto res = SetUserSystemClockContext(context); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + void SET_SYS::GetAccountSettings(HLERequestContext& ctx) { LOG_INFO(Service_SET, "called"); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.PushRaw(account_settings); + rb.PushRaw(m_system_settings.account_settings); } void SET_SYS::SetAccountSettings(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - account_settings = rp.PopRaw<AccountSettings>(); + m_system_settings.account_settings = rp.PopRaw<AccountSettings>(); + SetSaveNeeded(); - LOG_INFO(Service_SET, "called, account_settings_flags={}", account_settings.flags); + LOG_INFO(Service_SET, "called, account_settings_flags={}", + m_system_settings.account_settings.flags); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -133,11 +306,11 @@ void SET_SYS::SetAccountSettings(HLERequestContext& ctx) { void SET_SYS::GetEulaVersions(HLERequestContext& ctx) { LOG_INFO(Service_SET, "called"); - ctx.WriteBuffer(eula_versions); + ctx.WriteBuffer(m_system_settings.eula_versions); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast<u32>(eula_versions.size())); + rb.Push(m_system_settings.eula_version_count); } void SET_SYS::SetEulaVersions(HLERequestContext& ctx) { @@ -145,13 +318,12 @@ void SET_SYS::SetEulaVersions(HLERequestContext& ctx) { const auto buffer_data = ctx.ReadBuffer(); LOG_INFO(Service_SET, "called, elements={}", elements); + ASSERT(elements <= m_system_settings.eula_versions.size()); - eula_versions.resize(elements); - for (std::size_t index = 0; index < elements; index++) { - const std::size_t start_index = index * sizeof(EulaVersion); - memcpy(eula_versions.data() + start_index, buffer_data.data() + start_index, - sizeof(EulaVersion)); - } + m_system_settings.eula_version_count = static_cast<u32>(elements); + std::memcpy(&m_system_settings.eula_versions, buffer_data.data(), + sizeof(EulaVersion) * elements); + SetSaveNeeded(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -162,14 +334,15 @@ void SET_SYS::GetColorSetId(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.PushEnum(color_set); + rb.PushEnum(m_system_settings.color_set_id); } void SET_SYS::SetColorSetId(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - color_set = rp.PopEnum<ColorSet>(); + m_system_settings.color_set_id = rp.PopEnum<ColorSet>(); + SetSaveNeeded(); - LOG_DEBUG(Service_SET, "called, color_set={}", color_set); + LOG_DEBUG(Service_SET, "called, color_set={}", m_system_settings.color_set_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -180,17 +353,21 @@ void SET_SYS::GetNotificationSettings(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 8}; rb.Push(ResultSuccess); - rb.PushRaw(notification_settings); + rb.PushRaw(m_system_settings.notification_settings); } void SET_SYS::SetNotificationSettings(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - notification_settings = rp.PopRaw<NotificationSettings>(); + m_system_settings.notification_settings = rp.PopRaw<NotificationSettings>(); + SetSaveNeeded(); LOG_INFO(Service_SET, "called, flags={}, volume={}, head_time={}:{}, tailt_time={}:{}", - notification_settings.flags.raw, notification_settings.volume, - notification_settings.start_time.hour, notification_settings.start_time.minute, - notification_settings.stop_time.hour, notification_settings.stop_time.minute); + m_system_settings.notification_settings.flags.raw, + m_system_settings.notification_settings.volume, + m_system_settings.notification_settings.start_time.hour, + m_system_settings.notification_settings.start_time.minute, + m_system_settings.notification_settings.stop_time.hour, + m_system_settings.notification_settings.stop_time.minute); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -199,11 +376,11 @@ void SET_SYS::SetNotificationSettings(HLERequestContext& ctx) { void SET_SYS::GetAccountNotificationSettings(HLERequestContext& ctx) { LOG_INFO(Service_SET, "called"); - ctx.WriteBuffer(account_notifications); + ctx.WriteBuffer(m_system_settings.account_notification_settings); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast<u32>(account_notifications.size())); + rb.Push(m_system_settings.account_notification_settings_count); } void SET_SYS::SetAccountNotificationSettings(HLERequestContext& ctx) { @@ -212,12 +389,12 @@ void SET_SYS::SetAccountNotificationSettings(HLERequestContext& ctx) { LOG_INFO(Service_SET, "called, elements={}", elements); - account_notifications.resize(elements); - for (std::size_t index = 0; index < elements; index++) { - const std::size_t start_index = index * sizeof(AccountNotificationSettings); - memcpy(account_notifications.data() + start_index, buffer_data.data() + start_index, - sizeof(AccountNotificationSettings)); - } + ASSERT(elements <= m_system_settings.account_notification_settings.size()); + + m_system_settings.account_notification_settings_count = static_cast<u32>(elements); + std::memcpy(&m_system_settings.account_notification_settings, buffer_data.data(), + elements * sizeof(AccountNotificationSettings)); + SetSaveNeeded(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -244,6 +421,14 @@ static Settings GetSettings() { ret["hbloader"]["applet_heap_size"] = ToBytes(u64{0x0}); ret["hbloader"]["applet_heap_reservation_size"] = ToBytes(u64{0x8600000}); + // Time + ret["time"]["notify_time_to_fs_interval_seconds"] = ToBytes(s32{600}); + ret["time"]["standard_network_clock_sufficient_accuracy_minutes"] = + ToBytes(s32{43200}); // 30 days + ret["time"]["standard_steady_clock_rtc_update_interval_minutes"] = ToBytes(s32{5}); + ret["time"]["standard_steady_clock_test_offset_minutes"] = ToBytes(s32{0}); + ret["time"]["standard_user_clock_initial_year"] = ToBytes(s32{2023}); + return ret; } @@ -273,8 +458,6 @@ void SET_SYS::GetSettingsItemValueSize(HLERequestContext& ctx) { } void SET_SYS::GetSettingsItemValue(HLERequestContext& ctx) { - LOG_DEBUG(Service_SET, "called"); - // The category of the setting. This corresponds to the top-level keys of // system_settings.ini. const auto setting_category_buf{ctx.ReadBuffer(0)}; @@ -285,14 +468,13 @@ void SET_SYS::GetSettingsItemValue(HLERequestContext& ctx) { const auto setting_name_buf{ctx.ReadBuffer(1)}; const std::string setting_name{setting_name_buf.begin(), setting_name_buf.end()}; - auto settings{GetSettings()}; - Result response{ResultUnknown}; + std::vector<u8> value; + auto response = GetSettingsItemValue(value, setting_category, setting_name); - if (settings.contains(setting_category) && settings[setting_category].contains(setting_name)) { - auto setting_value = settings[setting_category][setting_name]; - ctx.WriteBuffer(setting_value.data(), setting_value.size()); - response = ResultSuccess; - } + LOG_INFO(Service_SET, "called. category={}, name={} -- res=0x{:X}", setting_category, + setting_name, response.raw); + + ctx.WriteBuffer(value.data(), value.size()); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(response); @@ -303,19 +485,23 @@ void SET_SYS::GetTvSettings(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 10}; rb.Push(ResultSuccess); - rb.PushRaw(tv_settings); + rb.PushRaw(m_system_settings.tv_settings); } void SET_SYS::SetTvSettings(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - tv_settings = rp.PopRaw<TvSettings>(); + m_system_settings.tv_settings = rp.PopRaw<TvSettings>(); + SetSaveNeeded(); LOG_INFO(Service_SET, "called, flags={}, cmu_mode={}, constrast_ratio={}, hdmi_content_type={}, " "rgb_range={}, tv_gama={}, tv_resolution={}, tv_underscan={}", - tv_settings.flags.raw, tv_settings.cmu_mode, tv_settings.constrast_ratio, - tv_settings.hdmi_content_type, tv_settings.rgb_range, tv_settings.tv_gama, - tv_settings.tv_resolution, tv_settings.tv_underscan); + m_system_settings.tv_settings.flags.raw, m_system_settings.tv_settings.cmu_mode, + m_system_settings.tv_settings.constrast_ratio, + m_system_settings.tv_settings.hdmi_content_type, + m_system_settings.tv_settings.rgb_range, m_system_settings.tv_settings.tv_gama, + m_system_settings.tv_settings.tv_resolution, + m_system_settings.tv_settings.tv_underscan); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -329,16 +515,87 @@ void SET_SYS::GetQuestFlag(HLERequestContext& ctx) { rb.PushEnum(QuestFlag::Retail); } +void SET_SYS::GetDeviceTimeZoneLocationName(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "called"); + + Service::Time::TimeZone::LocationName name{}; + auto res = GetDeviceTimeZoneLocationName(name); + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(Service::Time::TimeZone::LocationName) / sizeof(u32)}; + rb.Push(res); + rb.PushRaw<Service::Time::TimeZone::LocationName>(name); +} + +void SET_SYS::SetDeviceTimeZoneLocationName(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "called"); + + IPC::RequestParser rp{ctx}; + auto name{rp.PopRaw<Service::Time::TimeZone::LocationName>()}; + + auto res = SetDeviceTimeZoneLocationName(name); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + void SET_SYS::SetRegionCode(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - region_code = rp.PopEnum<RegionCode>(); + m_system_settings.region_code = rp.PopEnum<RegionCode>(); + SetSaveNeeded(); - LOG_INFO(Service_SET, "called, region_code={}", region_code); + LOG_INFO(Service_SET, "called, region_code={}", m_system_settings.region_code); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } +void SET_SYS::GetNetworkSystemClockContext(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + Service::Time::Clock::SystemClockContext context{}; + auto res = GetNetworkSystemClockContext(context); + + IPC::ResponseBuilder rb{ctx, + 2 + sizeof(Service::Time::Clock::SystemClockContext) / sizeof(u32)}; + rb.Push(res); + rb.PushRaw(context); +} + +void SET_SYS::SetNetworkSystemClockContext(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + IPC::RequestParser rp{ctx}; + auto context{rp.PopRaw<Service::Time::Clock::SystemClockContext>()}; + + auto res = SetNetworkSystemClockContext(context); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + +void SET_SYS::IsUserSystemClockAutomaticCorrectionEnabled(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + bool enabled{}; + auto res = IsUserSystemClockAutomaticCorrectionEnabled(enabled); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(res); + rb.PushRaw(enabled); +} + +void SET_SYS::SetUserSystemClockAutomaticCorrectionEnabled(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + IPC::RequestParser rp{ctx}; + auto enabled{rp.Pop<bool>()}; + + auto res = SetUserSystemClockAutomaticCorrectionEnabled(enabled); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + void SET_SYS::GetPrimaryAlbumStorage(HLERequestContext& ctx) { LOG_WARNING(Service_SET, "(STUBBED) called"); @@ -352,16 +609,18 @@ void SET_SYS::GetSleepSettings(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 5}; rb.Push(ResultSuccess); - rb.PushRaw(sleep_settings); + rb.PushRaw(m_system_settings.sleep_settings); } void SET_SYS::SetSleepSettings(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - sleep_settings = rp.PopRaw<SleepSettings>(); + m_system_settings.sleep_settings = rp.PopRaw<SleepSettings>(); + SetSaveNeeded(); LOG_INFO(Service_SET, "called, flags={}, handheld_sleep_plan={}, console_sleep_plan={}", - sleep_settings.flags.raw, sleep_settings.handheld_sleep_plan, - sleep_settings.console_sleep_plan); + m_system_settings.sleep_settings.flags.raw, + m_system_settings.sleep_settings.handheld_sleep_plan, + m_system_settings.sleep_settings.console_sleep_plan); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -371,15 +630,20 @@ void SET_SYS::GetInitialLaunchSettings(HLERequestContext& ctx) { LOG_INFO(Service_SET, "called"); IPC::ResponseBuilder rb{ctx, 10}; rb.Push(ResultSuccess); - rb.PushRaw(launch_settings); + rb.PushRaw(m_system_settings.initial_launch_settings_packed); } void SET_SYS::SetInitialLaunchSettings(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - launch_settings = rp.PopRaw<InitialLaunchSettings>(); + auto inital_launch_settings = rp.PopRaw<InitialLaunchSettings>(); - LOG_INFO(Service_SET, "called, flags={}, timestamp={}", launch_settings.flags.raw, - launch_settings.timestamp.time_point); + m_system_settings.initial_launch_settings_packed.flags = inital_launch_settings.flags; + m_system_settings.initial_launch_settings_packed.timestamp = inital_launch_settings.timestamp; + SetSaveNeeded(); + + LOG_INFO(Service_SET, "called, flags={}, timestamp={}", + m_system_settings.initial_launch_settings_packed.flags.raw, + m_system_settings.initial_launch_settings_packed.timestamp.time_point); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -437,13 +701,37 @@ void SET_SYS::GetAutoUpdateEnableFlag(HLERequestContext& ctx) { void SET_SYS::GetBatteryPercentageFlag(HLERequestContext& ctx) { u8 battery_percentage_flag{1}; - LOG_DEBUG(Service_SET, "(STUBBED) called, battery_percentage_flag={}", battery_percentage_flag); + LOG_WARNING(Service_SET, "(STUBBED) called, battery_percentage_flag={}", + battery_percentage_flag); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(battery_percentage_flag); } +void SET_SYS::SetExternalSteadyClockInternalOffset(HLERequestContext& ctx) { + LOG_DEBUG(Service_SET, "called."); + + IPC::RequestParser rp{ctx}; + auto offset{rp.Pop<s64>()}; + + auto res = SetExternalSteadyClockInternalOffset(offset); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + +void SET_SYS::GetExternalSteadyClockInternalOffset(HLERequestContext& ctx) { + LOG_DEBUG(Service_SET, "called."); + + s64 offset{}; + auto res = GetExternalSteadyClockInternalOffset(offset); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(res); + rb.Push(offset); +} + void SET_SYS::GetErrorReportSharePermission(HLERequestContext& ctx) { LOG_WARNING(Service_SET, "(STUBBED) called"); @@ -453,18 +741,19 @@ void SET_SYS::GetErrorReportSharePermission(HLERequestContext& ctx) { } void SET_SYS::GetAppletLaunchFlags(HLERequestContext& ctx) { - LOG_INFO(Service_SET, "called, applet_launch_flag={}", applet_launch_flag); + LOG_INFO(Service_SET, "called, applet_launch_flag={}", m_system_settings.applet_launch_flag); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(applet_launch_flag); + rb.Push(m_system_settings.applet_launch_flag); } void SET_SYS::SetAppletLaunchFlags(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - applet_launch_flag = rp.Pop<u32>(); + m_system_settings.applet_launch_flag = rp.Pop<u32>(); + SetSaveNeeded(); - LOG_INFO(Service_SET, "called, applet_launch_flag={}", applet_launch_flag); + LOG_INFO(Service_SET, "called, applet_launch_flag={}", m_system_settings.applet_launch_flag); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -489,6 +778,52 @@ void SET_SYS::GetKeyboardLayout(HLERequestContext& ctx) { rb.Push(static_cast<u32>(selected_keyboard_layout)); } +void SET_SYS::GetDeviceTimeZoneLocationUpdatedTime(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "called."); + + Service::Time::Clock::SteadyClockTimePoint time_point{}; + auto res = GetDeviceTimeZoneLocationUpdatedTime(time_point); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(res); + rb.PushRaw<Service::Time::Clock::SteadyClockTimePoint>(time_point); +} + +void SET_SYS::SetDeviceTimeZoneLocationUpdatedTime(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "called."); + + IPC::RequestParser rp{ctx}; + auto time_point{rp.PopRaw<Service::Time::Clock::SteadyClockTimePoint>()}; + + auto res = SetDeviceTimeZoneLocationUpdatedTime(time_point); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + +void SET_SYS::GetUserSystemClockAutomaticCorrectionUpdatedTime(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "called."); + + Service::Time::Clock::SteadyClockTimePoint time_point{}; + auto res = GetUserSystemClockAutomaticCorrectionUpdatedTime(time_point); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(res); + rb.PushRaw<Service::Time::Clock::SteadyClockTimePoint>(time_point); +} + +void SET_SYS::SetUserSystemClockAutomaticCorrectionUpdatedTime(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "called."); + + IPC::RequestParser rp{ctx}; + auto time_point{rp.PopRaw<Service::Time::Clock::SteadyClockTimePoint>()}; + + auto res = SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(res); +} + void SET_SYS::GetChineseTraditionalInputMethod(HLERequestContext& ctx) { LOG_WARNING(Service_SET, "(STUBBED) called"); @@ -508,7 +843,7 @@ void SET_SYS::GetHomeMenuScheme(HLERequestContext& ctx) { .extra = 0xFF000000, }; - IPC::ResponseBuilder rb{ctx, 7}; + IPC::ResponseBuilder rb{ctx, 2 + sizeof(HomeMenuScheme) / sizeof(u32)}; rb.Push(ResultSuccess); rb.PushRaw(default_color); } @@ -520,6 +855,7 @@ void SET_SYS::GetHomeMenuSchemeModel(HLERequestContext& ctx) { rb.Push(ResultSuccess); rb.Push(0); } + void SET_SYS::GetFieldTestingFlag(HLERequestContext& ctx) { LOG_WARNING(Service_SET, "(STUBBED) called"); @@ -528,7 +864,7 @@ void SET_SYS::GetFieldTestingFlag(HLERequestContext& ctx) { rb.Push<u8>(false); } -SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { +SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"}, m_system{system} { // clang-format off static const FunctionInfo functions[] = { {0, &SET_SYS::SetLanguageCode, "SetLanguageCode"}, @@ -543,10 +879,10 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {10, nullptr, "SetBacklightSettings"}, {11, nullptr, "SetBluetoothDevicesSettings"}, {12, nullptr, "GetBluetoothDevicesSettings"}, - {13, nullptr, "GetExternalSteadyClockSourceId"}, - {14, nullptr, "SetExternalSteadyClockSourceId"}, - {15, nullptr, "GetUserSystemClockContext"}, - {16, nullptr, "SetUserSystemClockContext"}, + {13, &SET_SYS::GetExternalSteadyClockSourceId, "GetExternalSteadyClockSourceId"}, + {14, &SET_SYS::SetExternalSteadyClockSourceId, "SetExternalSteadyClockSourceId"}, + {15, &SET_SYS::GetUserSystemClockContext, "GetUserSystemClockContext"}, + {16, &SET_SYS::SetUserSystemClockContext, "SetUserSystemClockContext"}, {17, &SET_SYS::GetAccountSettings, "GetAccountSettings"}, {18, &SET_SYS::SetAccountSettings, "SetAccountSettings"}, {19, nullptr, "GetAudioVolume"}, @@ -581,15 +917,15 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {50, nullptr, "SetDataDeletionSettings"}, {51, nullptr, "GetInitialSystemAppletProgramId"}, {52, nullptr, "GetOverlayDispProgramId"}, - {53, nullptr, "GetDeviceTimeZoneLocationName"}, - {54, nullptr, "SetDeviceTimeZoneLocationName"}, + {53, &SET_SYS::GetDeviceTimeZoneLocationName, "GetDeviceTimeZoneLocationName"}, + {54, &SET_SYS::SetDeviceTimeZoneLocationName, "SetDeviceTimeZoneLocationName"}, {55, nullptr, "GetWirelessCertificationFileSize"}, {56, nullptr, "GetWirelessCertificationFile"}, {57, &SET_SYS::SetRegionCode, "SetRegionCode"}, - {58, nullptr, "GetNetworkSystemClockContext"}, - {59, nullptr, "SetNetworkSystemClockContext"}, - {60, nullptr, "IsUserSystemClockAutomaticCorrectionEnabled"}, - {61, nullptr, "SetUserSystemClockAutomaticCorrectionEnabled"}, + {58, &SET_SYS::GetNetworkSystemClockContext, "GetNetworkSystemClockContext"}, + {59, &SET_SYS::SetNetworkSystemClockContext, "SetNetworkSystemClockContext"}, + {60, &SET_SYS::IsUserSystemClockAutomaticCorrectionEnabled, "IsUserSystemClockAutomaticCorrectionEnabled"}, + {61, &SET_SYS::SetUserSystemClockAutomaticCorrectionEnabled, "SetUserSystemClockAutomaticCorrectionEnabled"}, {62, nullptr, "GetDebugModeFlag"}, {63, &SET_SYS::GetPrimaryAlbumStorage, "GetPrimaryAlbumStorage"}, {64, nullptr, "SetPrimaryAlbumStorage"}, @@ -633,8 +969,8 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {102, nullptr, "SetExternalRtcResetFlag"}, {103, nullptr, "GetUsbFullKeyEnableFlag"}, {104, nullptr, "SetUsbFullKeyEnableFlag"}, - {105, nullptr, "SetExternalSteadyClockInternalOffset"}, - {106, nullptr, "GetExternalSteadyClockInternalOffset"}, + {105, &SET_SYS::SetExternalSteadyClockInternalOffset, "SetExternalSteadyClockInternalOffset"}, + {106, &SET_SYS::GetExternalSteadyClockInternalOffset, "GetExternalSteadyClockInternalOffset"}, {107, nullptr, "GetBacklightSettingsEx"}, {108, nullptr, "SetBacklightSettingsEx"}, {109, nullptr, "GetHeadphoneVolumeWarningCount"}, @@ -678,10 +1014,10 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {147, nullptr, "GetConsoleSixAxisSensorAngularAcceleration"}, {148, nullptr, "SetConsoleSixAxisSensorAngularAcceleration"}, {149, nullptr, "GetRebootlessSystemUpdateVersion"}, - {150, nullptr, "GetDeviceTimeZoneLocationUpdatedTime"}, - {151, nullptr, "SetDeviceTimeZoneLocationUpdatedTime"}, - {152, nullptr, "GetUserSystemClockAutomaticCorrectionUpdatedTime"}, - {153, nullptr, "SetUserSystemClockAutomaticCorrectionUpdatedTime"}, + {150, &SET_SYS::GetDeviceTimeZoneLocationUpdatedTime, "GetDeviceTimeZoneLocationUpdatedTime"}, + {151, &SET_SYS::SetDeviceTimeZoneLocationUpdatedTime, "SetDeviceTimeZoneLocationUpdatedTime"}, + {152, &SET_SYS::GetUserSystemClockAutomaticCorrectionUpdatedTime, "GetUserSystemClockAutomaticCorrectionUpdatedTime"}, + {153, &SET_SYS::SetUserSystemClockAutomaticCorrectionUpdatedTime, "SetUserSystemClockAutomaticCorrectionUpdatedTime"}, {154, nullptr, "GetAccountOnlineStorageSettings"}, {155, nullptr, "SetAccountOnlineStorageSettings"}, {156, nullptr, "GetPctlReadyFlag"}, @@ -743,8 +1079,184 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { // clang-format on RegisterHandlers(functions); + + SetupSettings(); + m_save_thread = + std::jthread([this](std::stop_token stop_token) { StoreSettingsThreadFunc(stop_token); }); +} + +SET_SYS::~SET_SYS() { + SetSaveNeeded(); + m_save_thread.request_stop(); +} + +void SET_SYS::SetupSettings() { + auto system_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000050"; + if (!LoadSettingsFile(system_dir, []() { return DefaultSystemSettings(); })) { + ASSERT(false); + } + + auto private_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000052"; + if (!LoadSettingsFile(private_dir, []() { return DefaultPrivateSettings(); })) { + ASSERT(false); + } + + auto device_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000053"; + if (!LoadSettingsFile(device_dir, []() { return DefaultDeviceSettings(); })) { + ASSERT(false); + } + + auto appln_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000054"; + if (!LoadSettingsFile(appln_dir, []() { return DefaultApplnSettings(); })) { + ASSERT(false); + } +} + +void SET_SYS::StoreSettings() { + auto system_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000050"; + if (!StoreSettingsFile(system_dir, m_system_settings)) { + LOG_ERROR(HW_GPU, "Failed to store System settings"); + } + + auto private_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000052"; + if (!StoreSettingsFile(private_dir, m_private_settings)) { + LOG_ERROR(HW_GPU, "Failed to store Private settings"); + } + + auto device_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000053"; + if (!StoreSettingsFile(device_dir, m_device_settings)) { + LOG_ERROR(HW_GPU, "Failed to store Device settings"); + } + + auto appln_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000054"; + if (!StoreSettingsFile(appln_dir, m_appln_settings)) { + LOG_ERROR(HW_GPU, "Failed to store ApplLn settings"); + } +} + +void SET_SYS::StoreSettingsThreadFunc(std::stop_token stop_token) { + while (Common::StoppableTimedWait(stop_token, std::chrono::minutes(1))) { + std::scoped_lock l{m_save_needed_mutex}; + if (!std::exchange(m_save_needed, false)) { + continue; + } + StoreSettings(); + } +} + +void SET_SYS::SetSaveNeeded() { + std::scoped_lock l{m_save_needed_mutex}; + m_save_needed = true; +} + +Result SET_SYS::GetSettingsItemValue(std::vector<u8>& out_value, const std::string& category, + const std::string& name) { + auto settings{GetSettings()}; + R_UNLESS(settings.contains(category) && settings[category].contains(name), ResultUnknown); + + out_value = settings[category][name]; + R_SUCCEED(); +} + +Result SET_SYS::GetExternalSteadyClockSourceId(Common::UUID& out_id) { + out_id = m_private_settings.external_clock_source_id; + R_SUCCEED(); +} + +Result SET_SYS::SetExternalSteadyClockSourceId(Common::UUID id) { + m_private_settings.external_clock_source_id = id; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result SET_SYS::GetUserSystemClockContext(Service::Time::Clock::SystemClockContext& out_context) { + out_context = m_system_settings.user_system_clock_context; + R_SUCCEED(); } -SET_SYS::~SET_SYS() = default; +Result SET_SYS::SetUserSystemClockContext(Service::Time::Clock::SystemClockContext& context) { + m_system_settings.user_system_clock_context = context; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result SET_SYS::GetDeviceTimeZoneLocationName(Service::Time::TimeZone::LocationName& out_name) { + out_name = m_system_settings.device_time_zone_location_name; + R_SUCCEED(); +} + +Result SET_SYS::SetDeviceTimeZoneLocationName(Service::Time::TimeZone::LocationName& name) { + m_system_settings.device_time_zone_location_name = name; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result SET_SYS::GetNetworkSystemClockContext( + Service::Time::Clock::SystemClockContext& out_context) { + out_context = m_system_settings.network_system_clock_context; + R_SUCCEED(); +} + +Result SET_SYS::SetNetworkSystemClockContext(Service::Time::Clock::SystemClockContext& context) { + m_system_settings.network_system_clock_context = context; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result SET_SYS::IsUserSystemClockAutomaticCorrectionEnabled(bool& out_enabled) { + out_enabled = m_system_settings.user_system_clock_automatic_correction_enabled; + R_SUCCEED(); +} + +Result SET_SYS::SetUserSystemClockAutomaticCorrectionEnabled(bool enabled) { + m_system_settings.user_system_clock_automatic_correction_enabled = enabled; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result SET_SYS::SetExternalSteadyClockInternalOffset(s64 offset) { + m_private_settings.external_steady_clock_internal_offset = offset; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result SET_SYS::GetExternalSteadyClockInternalOffset(s64& out_offset) { + out_offset = m_private_settings.external_steady_clock_internal_offset; + R_SUCCEED(); +} + +Result SET_SYS::GetDeviceTimeZoneLocationUpdatedTime( + Service::Time::Clock::SteadyClockTimePoint& out_time_point) { + out_time_point = m_system_settings.device_time_zone_location_updated_time; + R_SUCCEED(); +} + +Result SET_SYS::SetDeviceTimeZoneLocationUpdatedTime( + Service::Time::Clock::SteadyClockTimePoint& time_point) { + m_system_settings.device_time_zone_location_updated_time = time_point; + SetSaveNeeded(); + R_SUCCEED(); +} + +Result SET_SYS::GetUserSystemClockAutomaticCorrectionUpdatedTime( + Service::Time::Clock::SteadyClockTimePoint& out_time_point) { + out_time_point = m_system_settings.user_system_clock_automatic_correction_updated_time_point; + R_SUCCEED(); +} + +Result SET_SYS::SetUserSystemClockAutomaticCorrectionUpdatedTime( + Service::Time::Clock::SteadyClockTimePoint out_time_point) { + m_system_settings.user_system_clock_automatic_correction_updated_time_point = out_time_point; + SetSaveNeeded(); + R_SUCCEED(); +} } // namespace Service::Set diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h index 5f770fd32..3785d93d8 100644 --- a/src/core/hle/service/set/set_sys.h +++ b/src/core/hle/service/set/set_sys.h @@ -3,17 +3,27 @@ #pragma once +#include <filesystem> +#include <mutex> +#include <string> +#include <thread> + +#include "common/polyfill_thread.h" #include "common/uuid.h" #include "core/hle/result.h" #include "core/hle/service/service.h" +#include "core/hle/service/set/appln_settings.h" +#include "core/hle/service/set/device_settings.h" +#include "core/hle/service/set/private_settings.h" +#include "core/hle/service/set/system_settings.h" #include "core/hle/service/time/clock_types.h" +#include "core/hle/service/time/time_zone_types.h" namespace Core { class System; } namespace Service::Set { -enum class LanguageCode : u64; enum class GetFirmwareVersionType { Version1, Version2, @@ -42,270 +52,38 @@ public: explicit SET_SYS(Core::System& system_); ~SET_SYS() override; -private: - /// Indicates the current theme set by the system settings - enum class ColorSet : u32 { - BasicWhite = 0, - BasicBlack = 1, - }; - - /// Indicates the current console is a retail or kiosk unit - enum class QuestFlag : u8 { - Retail = 0, - Kiosk = 1, - }; - - /// This is nn::settings::system::TvResolution - enum class TvResolution : u32 { - Auto, - Resolution1080p, - Resolution720p, - Resolution480p, - }; - - /// This is nn::settings::system::HdmiContentType - enum class HdmiContentType : u32 { - None, - Graphics, - Cinema, - Photo, - Game, - }; - - /// This is nn::settings::system::RgbRange - enum class RgbRange : u32 { - Auto, - Full, - Limited, - }; - - /// This is nn::settings::system::CmuMode - enum class CmuMode : u32 { - None, - ColorInvert, - HighContrast, - GrayScale, - }; - - /// This is nn::settings::system::PrimaryAlbumStorage - enum class PrimaryAlbumStorage : u32 { - Nand, - SdCard, - }; - - /// This is nn::settings::system::NotificationVolume - enum class NotificationVolume : u32 { - Mute, - Low, - High, - }; - - /// This is nn::settings::system::ChineseTraditionalInputMethod - enum class ChineseTraditionalInputMethod : u32 { - Unknown0 = 0, - Unknown1 = 1, - Unknown2 = 2, - }; - - /// This is nn::settings::system::ErrorReportSharePermission - enum class ErrorReportSharePermission : u32 { - NotConfirmed, - Granted, - Denied, - }; - - /// This is nn::settings::system::FriendPresenceOverlayPermission - enum class FriendPresenceOverlayPermission : u8 { - NotConfirmed, - NoDisplay, - FavoriteFriends, - Friends, - }; - - /// This is nn::settings::system::HandheldSleepPlan - enum class HandheldSleepPlan : u32 { - Sleep1Min, - Sleep3Min, - Sleep5Min, - Sleep10Min, - Sleep30Min, - Never, - }; - - /// This is nn::settings::system::ConsoleSleepPlan - enum class ConsoleSleepPlan : u32 { - Sleep1Hour, - Sleep2Hour, - Sleep3Hour, - Sleep6Hour, - Sleep12Hour, - Never, - }; - - /// This is nn::settings::system::RegionCode - enum class RegionCode : u32 { - Japan, - Usa, - Europe, - Australia, - HongKongTaiwanKorea, - China, - }; - - /// This is nn::settings::system::EulaVersionClockType - enum class EulaVersionClockType : u32 { - NetworkSystemClock, - SteadyClock, - }; - - /// This is nn::settings::system::SleepFlag - struct SleepFlag { - union { - u32 raw{}; - - BitField<0, 1, u32> SleepsWhilePlayingMedia; - BitField<1, 1, u32> WakesAtPowerStateChange; - }; - }; - static_assert(sizeof(SleepFlag) == 4, "TvFlag is an invalid size"); - - /// This is nn::settings::system::TvFlag - struct TvFlag { - union { - u32 raw{}; - - BitField<0, 1, u32> Allows4k; - BitField<1, 1, u32> Allows3d; - BitField<2, 1, u32> AllowsCec; - BitField<3, 1, u32> PreventsScreenBurnIn; - }; - }; - static_assert(sizeof(TvFlag) == 4, "TvFlag is an invalid size"); - - /// This is nn::settings::system::InitialLaunchFlag - struct InitialLaunchFlag { - union { - u32 raw{}; - - BitField<0, 1, u32> InitialLaunchCompletionFlag; - BitField<8, 1, u32> InitialLaunchUserAdditionFlag; - BitField<16, 1, u32> InitialLaunchTimestampFlag; - }; - }; - static_assert(sizeof(InitialLaunchFlag) == 4, "InitialLaunchFlag is an invalid size"); - - /// This is nn::settings::system::NotificationFlag - struct NotificationFlag { - union { - u32 raw{}; - - BitField<0, 1, u32> RingtoneFlag; - BitField<1, 1, u32> DownloadCompletionFlag; - BitField<8, 1, u32> EnablesNews; - BitField<9, 1, u32> IncomingLampFlag; - }; - }; - static_assert(sizeof(NotificationFlag) == 4, "NotificationFlag is an invalid size"); - - /// This is nn::settings::system::AccountNotificationFlag - struct AccountNotificationFlag { - union { - u32 raw{}; - - BitField<0, 1, u32> FriendOnlineFlag; - BitField<1, 1, u32> FriendRequestFlag; - BitField<8, 1, u32> CoralInvitationFlag; - }; - }; - static_assert(sizeof(AccountNotificationFlag) == 4, - "AccountNotificationFlag is an invalid size"); - - /// This is nn::settings::system::TvSettings - struct TvSettings { - TvFlag flags; - TvResolution tv_resolution; - HdmiContentType hdmi_content_type; - RgbRange rgb_range; - CmuMode cmu_mode; - u32 tv_underscan; - f32 tv_gama; - f32 constrast_ratio; - }; - static_assert(sizeof(TvSettings) == 0x20, "TvSettings is an invalid size"); - - /// This is nn::settings::system::NotificationTime - struct NotificationTime { - u32 hour; - u32 minute; - }; - static_assert(sizeof(NotificationTime) == 0x8, "NotificationTime is an invalid size"); - - /// This is nn::settings::system::NotificationSettings - struct NotificationSettings { - NotificationFlag flags; - NotificationVolume volume; - NotificationTime start_time; - NotificationTime stop_time; - }; - static_assert(sizeof(NotificationSettings) == 0x18, "NotificationSettings is an invalid size"); - - /// This is nn::settings::system::AccountSettings - struct AccountSettings { - u32 flags; - }; - static_assert(sizeof(AccountSettings) == 0x4, "AccountSettings is an invalid size"); - - /// This is nn::settings::system::AccountNotificationSettings - struct AccountNotificationSettings { - Common::UUID uid; - AccountNotificationFlag flags; - FriendPresenceOverlayPermission friend_presence_permission; - FriendPresenceOverlayPermission friend_invitation_permission; - INSERT_PADDING_BYTES(0x2); - }; - static_assert(sizeof(AccountNotificationSettings) == 0x18, - "AccountNotificationSettings is an invalid size"); - - /// This is nn::settings::system::InitialLaunchSettings - struct SleepSettings { - SleepFlag flags; - HandheldSleepPlan handheld_sleep_plan; - ConsoleSleepPlan console_sleep_plan; - }; - static_assert(sizeof(SleepSettings) == 0xc, "SleepSettings is incorrect size"); - - /// This is nn::settings::system::InitialLaunchSettings - struct InitialLaunchSettings { - InitialLaunchFlag flags; - INSERT_PADDING_BYTES(0x4); - Time::Clock::SteadyClockTimePoint timestamp; - }; - static_assert(sizeof(InitialLaunchSettings) == 0x20, "InitialLaunchSettings is incorrect size"); - - /// This is nn::settings::system::InitialLaunchSettings - struct EulaVersion { - u32 version; - RegionCode region_code; - EulaVersionClockType clock_type; - INSERT_PADDING_BYTES(0x4); - s64 posix_time; - Time::Clock::SteadyClockTimePoint timestamp; - }; - static_assert(sizeof(EulaVersion) == 0x30, "EulaVersion is incorrect size"); - - /// This is nn::settings::system::HomeMenuScheme - struct HomeMenuScheme { - u32 main; - u32 back; - u32 sub; - u32 bezel; - u32 extra; - }; - static_assert(sizeof(HomeMenuScheme) == 0x14, "HomeMenuScheme is incorrect size"); + Result GetSettingsItemValue(std::vector<u8>& out_value, const std::string& category, + const std::string& name); + + Result GetExternalSteadyClockSourceId(Common::UUID& out_id); + Result SetExternalSteadyClockSourceId(Common::UUID id); + Result GetUserSystemClockContext(Service::Time::Clock::SystemClockContext& out_context); + Result SetUserSystemClockContext(Service::Time::Clock::SystemClockContext& context); + Result GetDeviceTimeZoneLocationName(Service::Time::TimeZone::LocationName& out_name); + Result SetDeviceTimeZoneLocationName(Service::Time::TimeZone::LocationName& name); + Result GetNetworkSystemClockContext(Service::Time::Clock::SystemClockContext& out_context); + Result SetNetworkSystemClockContext(Service::Time::Clock::SystemClockContext& context); + Result IsUserSystemClockAutomaticCorrectionEnabled(bool& out_enabled); + Result SetUserSystemClockAutomaticCorrectionEnabled(bool enabled); + Result SetExternalSteadyClockInternalOffset(s64 offset); + Result GetExternalSteadyClockInternalOffset(s64& out_offset); + Result GetDeviceTimeZoneLocationUpdatedTime( + Service::Time::Clock::SteadyClockTimePoint& out_time_point); + Result SetDeviceTimeZoneLocationUpdatedTime( + Service::Time::Clock::SteadyClockTimePoint& time_point); + Result GetUserSystemClockAutomaticCorrectionUpdatedTime( + Service::Time::Clock::SteadyClockTimePoint& out_time_point); + Result SetUserSystemClockAutomaticCorrectionUpdatedTime( + Service::Time::Clock::SteadyClockTimePoint time_point); +private: void SetLanguageCode(HLERequestContext& ctx); void GetFirmwareVersion(HLERequestContext& ctx); void GetFirmwareVersion2(HLERequestContext& ctx); + void GetExternalSteadyClockSourceId(HLERequestContext& ctx); + void SetExternalSteadyClockSourceId(HLERequestContext& ctx); + void GetUserSystemClockContext(HLERequestContext& ctx); + void SetUserSystemClockContext(HLERequestContext& ctx); void GetAccountSettings(HLERequestContext& ctx); void SetAccountSettings(HLERequestContext& ctx); void GetEulaVersions(HLERequestContext& ctx); @@ -321,7 +99,13 @@ private: void GetTvSettings(HLERequestContext& ctx); void SetTvSettings(HLERequestContext& ctx); void GetQuestFlag(HLERequestContext& ctx); + void GetDeviceTimeZoneLocationName(HLERequestContext& ctx); + void SetDeviceTimeZoneLocationName(HLERequestContext& ctx); void SetRegionCode(HLERequestContext& ctx); + void GetNetworkSystemClockContext(HLERequestContext& ctx); + void SetNetworkSystemClockContext(HLERequestContext& ctx); + void IsUserSystemClockAutomaticCorrectionEnabled(HLERequestContext& ctx); + void SetUserSystemClockAutomaticCorrectionEnabled(HLERequestContext& ctx); void GetPrimaryAlbumStorage(HLERequestContext& ctx); void GetSleepSettings(HLERequestContext& ctx); void SetSleepSettings(HLERequestContext& ctx); @@ -333,59 +117,36 @@ private: void GetMiiAuthorId(HLERequestContext& ctx); void GetAutoUpdateEnableFlag(HLERequestContext& ctx); void GetBatteryPercentageFlag(HLERequestContext& ctx); + void SetExternalSteadyClockInternalOffset(HLERequestContext& ctx); + void GetExternalSteadyClockInternalOffset(HLERequestContext& ctx); void GetErrorReportSharePermission(HLERequestContext& ctx); void GetAppletLaunchFlags(HLERequestContext& ctx); void SetAppletLaunchFlags(HLERequestContext& ctx); void GetKeyboardLayout(HLERequestContext& ctx); + void GetDeviceTimeZoneLocationUpdatedTime(HLERequestContext& ctx); + void SetDeviceTimeZoneLocationUpdatedTime(HLERequestContext& ctx); + void GetUserSystemClockAutomaticCorrectionUpdatedTime(HLERequestContext& ctx); + void SetUserSystemClockAutomaticCorrectionUpdatedTime(HLERequestContext& ctx); void GetChineseTraditionalInputMethod(HLERequestContext& ctx); - void GetFieldTestingFlag(HLERequestContext& ctx); void GetHomeMenuScheme(HLERequestContext& ctx); void GetHomeMenuSchemeModel(HLERequestContext& ctx); + void GetFieldTestingFlag(HLERequestContext& ctx); - AccountSettings account_settings{ - .flags = {}, - }; - - ColorSet color_set = ColorSet::BasicWhite; - - NotificationSettings notification_settings{ - .flags = {0x300}, - .volume = NotificationVolume::High, - .start_time = {.hour = 9, .minute = 0}, - .stop_time = {.hour = 21, .minute = 0}, - }; - - std::vector<AccountNotificationSettings> account_notifications{}; - - TvSettings tv_settings{ - .flags = {0xc}, - .tv_resolution = TvResolution::Auto, - .hdmi_content_type = HdmiContentType::Game, - .rgb_range = RgbRange::Auto, - .cmu_mode = CmuMode::None, - .tv_underscan = {}, - .tv_gama = 1.0f, - .constrast_ratio = 0.5f, - }; - - InitialLaunchSettings launch_settings{ - .flags = {0x10001}, - .timestamp = {}, - }; - - SleepSettings sleep_settings{ - .flags = {0x3}, - .handheld_sleep_plan = HandheldSleepPlan::Sleep10Min, - .console_sleep_plan = ConsoleSleepPlan::Sleep1Hour, - }; - - u32 applet_launch_flag{}; - - std::vector<EulaVersion> eula_versions{}; - - RegionCode region_code; - - LanguageCode language_code_setting; + bool LoadSettingsFile(std::filesystem::path& path, auto&& default_func); + bool StoreSettingsFile(std::filesystem::path& path, auto& settings); + void SetupSettings(); + void StoreSettings(); + void StoreSettingsThreadFunc(std::stop_token stop_token); + void SetSaveNeeded(); + + Core::System& m_system; + SystemSettings m_system_settings{}; + PrivateSettings m_private_settings{}; + DeviceSettings m_device_settings{}; + ApplnSettings m_appln_settings{}; + std::jthread m_save_thread; + std::mutex m_save_needed_mutex; + bool m_save_needed{false}; }; } // namespace Service::Set diff --git a/src/core/hle/service/set/system_settings.cpp b/src/core/hle/service/set/system_settings.cpp new file mode 100644 index 000000000..2723417ad --- /dev/null +++ b/src/core/hle/service/set/system_settings.cpp @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/set/system_settings.h" + +namespace Service::Set { + +SystemSettings DefaultSystemSettings() { + SystemSettings settings{}; + + settings.version = 0x140000; + settings.flags = 7; + + settings.color_set_id = ColorSet::BasicWhite; + + settings.notification_settings = { + .flags{0x300}, + .volume = NotificationVolume::High, + .start_time = {.hour = 9, .minute = 0}, + .stop_time = {.hour = 21, .minute = 0}, + }; + + settings.tv_settings = { + .flags = {0xC}, + .tv_resolution = TvResolution::Auto, + .hdmi_content_type = HdmiContentType::Game, + .rgb_range = RgbRange::Auto, + .cmu_mode = CmuMode::None, + .tv_underscan = {}, + .tv_gama = 1.0f, + .constrast_ratio = 0.5f, + }; + + settings.initial_launch_settings_packed = { + .flags = {0x10001}, + .timestamp = {}, + }; + + settings.sleep_settings = { + .flags = {0x3}, + .handheld_sleep_plan = HandheldSleepPlan::Sleep10Min, + .console_sleep_plan = ConsoleSleepPlan::Sleep1Hour, + }; + + settings.device_time_zone_location_name = {"UTC"}; + settings.user_system_clock_automatic_correction_enabled = false; + + return settings; +} + +} // namespace Service::Set diff --git a/src/core/hle/service/set/system_settings.h b/src/core/hle/service/set/system_settings.h new file mode 100644 index 000000000..ded2906ad --- /dev/null +++ b/src/core/hle/service/set/system_settings.h @@ -0,0 +1,699 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "core/hle/service/set/private_settings.h" +#include "core/hle/service/time/clock_types.h" + +namespace Service::Set { + +/// This is "nn::settings::LanguageCode", which is a NUL-terminated string stored in a u64. +enum class LanguageCode : u64 { + JA = 0x000000000000616A, + EN_US = 0x00000053552D6E65, + FR = 0x0000000000007266, + DE = 0x0000000000006564, + IT = 0x0000000000007469, + ES = 0x0000000000007365, + ZH_CN = 0x0000004E432D687A, + KO = 0x0000000000006F6B, + NL = 0x0000000000006C6E, + PT = 0x0000000000007470, + RU = 0x0000000000007572, + ZH_TW = 0x00000057542D687A, + EN_GB = 0x00000042472D6E65, + FR_CA = 0x00000041432D7266, + ES_419 = 0x00003931342D7365, + ZH_HANS = 0x00736E61482D687A, + ZH_HANT = 0x00746E61482D687A, + PT_BR = 0x00000052422D7470, +}; + +/// This is nn::settings::system::ErrorReportSharePermission +enum class ErrorReportSharePermission : u32 { + NotConfirmed, + Granted, + Denied, +}; + +/// This is nn::settings::system::ChineseTraditionalInputMethod +enum class ChineseTraditionalInputMethod : u32 { + Unknown0 = 0, + Unknown1 = 1, + Unknown2 = 2, +}; + +/// This is nn::settings::system::HomeMenuScheme +struct HomeMenuScheme { + u32 main; + u32 back; + u32 sub; + u32 bezel; + u32 extra; +}; +static_assert(sizeof(HomeMenuScheme) == 0x14, "HomeMenuScheme is incorrect size"); + +/// Indicates the current theme set by the system settings +enum class ColorSet : u32 { + BasicWhite = 0, + BasicBlack = 1, +}; + +/// Indicates the current console is a retail or kiosk unit +enum class QuestFlag : u8 { + Retail = 0, + Kiosk = 1, +}; + +/// This is nn::settings::system::RegionCode +enum class RegionCode : u32 { + Japan, + Usa, + Europe, + Australia, + HongKongTaiwanKorea, + China, +}; + +/// This is nn::settings::system::AccountSettings +struct AccountSettings { + u32 flags; +}; +static_assert(sizeof(AccountSettings) == 4, "AccountSettings is an invalid size"); + +/// This is nn::settings::system::NotificationVolume +enum class NotificationVolume : u32 { + Mute, + Low, + High, +}; + +/// This is nn::settings::system::NotificationFlag +struct NotificationFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> RingtoneFlag; + BitField<1, 1, u32> DownloadCompletionFlag; + BitField<8, 1, u32> EnablesNews; + BitField<9, 1, u32> IncomingLampFlag; + }; +}; +static_assert(sizeof(NotificationFlag) == 4, "NotificationFlag is an invalid size"); + +/// This is nn::settings::system::NotificationTime +struct NotificationTime { + u32 hour; + u32 minute; +}; +static_assert(sizeof(NotificationTime) == 0x8, "NotificationTime is an invalid size"); + +/// This is nn::settings::system::NotificationSettings +struct NotificationSettings { + NotificationFlag flags; + NotificationVolume volume; + NotificationTime start_time; + NotificationTime stop_time; +}; +static_assert(sizeof(NotificationSettings) == 0x18, "NotificationSettings is an invalid size"); + +/// This is nn::settings::system::AccountNotificationFlag +struct AccountNotificationFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> FriendOnlineFlag; + BitField<1, 1, u32> FriendRequestFlag; + BitField<8, 1, u32> CoralInvitationFlag; + }; +}; +static_assert(sizeof(AccountNotificationFlag) == 4, "AccountNotificationFlag is an invalid size"); + +/// This is nn::settings::system::FriendPresenceOverlayPermission +enum class FriendPresenceOverlayPermission : u8 { + NotConfirmed, + NoDisplay, + FavoriteFriends, + Friends, +}; + +/// This is nn::settings::system::AccountNotificationSettings +struct AccountNotificationSettings { + Common::UUID uid; + AccountNotificationFlag flags; + FriendPresenceOverlayPermission friend_presence_permission; + FriendPresenceOverlayPermission friend_invitation_permission; + INSERT_PADDING_BYTES(0x2); +}; +static_assert(sizeof(AccountNotificationSettings) == 0x18, + "AccountNotificationSettings is an invalid size"); + +/// This is nn::settings::system::TvFlag +struct TvFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> Allows4k; + BitField<1, 1, u32> Allows3d; + BitField<2, 1, u32> AllowsCec; + BitField<3, 1, u32> PreventsScreenBurnIn; + }; +}; +static_assert(sizeof(TvFlag) == 4, "TvFlag is an invalid size"); + +/// This is nn::settings::system::TvResolution +enum class TvResolution : u32 { + Auto, + Resolution1080p, + Resolution720p, + Resolution480p, +}; + +/// This is nn::settings::system::HdmiContentType +enum class HdmiContentType : u32 { + None, + Graphics, + Cinema, + Photo, + Game, +}; + +/// This is nn::settings::system::RgbRange +enum class RgbRange : u32 { + Auto, + Full, + Limited, +}; + +/// This is nn::settings::system::CmuMode +enum class CmuMode : u32 { + None, + ColorInvert, + HighContrast, + GrayScale, +}; + +/// This is nn::settings::system::TvSettings +struct TvSettings { + TvFlag flags; + TvResolution tv_resolution; + HdmiContentType hdmi_content_type; + RgbRange rgb_range; + CmuMode cmu_mode; + u32 tv_underscan; + f32 tv_gama; + f32 constrast_ratio; +}; +static_assert(sizeof(TvSettings) == 0x20, "TvSettings is an invalid size"); + +/// This is nn::settings::system::PrimaryAlbumStorage +enum class PrimaryAlbumStorage : u32 { + Nand, + SdCard, +}; + +/// This is nn::settings::system::HandheldSleepPlan +enum class HandheldSleepPlan : u32 { + Sleep1Min, + Sleep3Min, + Sleep5Min, + Sleep10Min, + Sleep30Min, + Never, +}; + +/// This is nn::settings::system::ConsoleSleepPlan +enum class ConsoleSleepPlan : u32 { + Sleep1Hour, + Sleep2Hour, + Sleep3Hour, + Sleep6Hour, + Sleep12Hour, + Never, +}; + +/// This is nn::settings::system::SleepFlag +struct SleepFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> SleepsWhilePlayingMedia; + BitField<1, 1, u32> WakesAtPowerStateChange; + }; +}; +static_assert(sizeof(SleepFlag) == 4, "TvFlag is an invalid size"); + +/// This is nn::settings::system::SleepSettings +struct SleepSettings { + SleepFlag flags; + HandheldSleepPlan handheld_sleep_plan; + ConsoleSleepPlan console_sleep_plan; +}; +static_assert(sizeof(SleepSettings) == 0xc, "SleepSettings is incorrect size"); + +/// This is nn::settings::system::EulaVersionClockType +enum class EulaVersionClockType : u32 { + NetworkSystemClock, + SteadyClock, +}; + +/// This is nn::settings::system::EulaVersion +struct EulaVersion { + u32 version; + RegionCode region_code; + EulaVersionClockType clock_type; + INSERT_PADDING_BYTES(0x4); + s64 posix_time; + Time::Clock::SteadyClockTimePoint timestamp; +}; +static_assert(sizeof(EulaVersion) == 0x30, "EulaVersion is incorrect size"); + +struct SystemSettings { + // 0/unwritten (1.0.0), 0x20000 (2.0.0), 0x30000 (3.0.0-3.0.1), 0x40001 (4.0.0-4.1.0), 0x50000 + // (5.0.0-5.1.0), 0x60000 (6.0.0-6.2.0), 0x70000 (7.0.0), 0x80000 (8.0.0-8.1.1), 0x90000 + // (9.0.0-10.0.4), 0x100100 (10.1.0+), 0x120000 (12.0.0-12.1.0), 0x130000 (13.0.0-13.2.1), + // 0x140000 (14.0.0+) + u32 version; + // 0/unwritten (1.0.0), 1 (6.0.0-8.1.0), 2 (8.1.1), 7 (9.0.0+). + // if (flags & 2), defaults are written for AnalogStickUserCalibration + u32 flags; + + std::array<u8, 0x8> reserved_00008; + + // nn::settings::LanguageCode + LanguageCode language_code; + + std::array<u8, 0x38> reserved_00018; + + // nn::settings::system::NetworkSettings + u32 network_setting_count; + bool wireless_lan_enable_flag; + std::array<u8, 0x3> pad_00055; + + std::array<u8, 0x8> reserved_00058; + + // nn::settings::system::NetworkSettings + std::array<std::array<u8, 0x400>, 32> network_settings_1B0; + + // nn::settings::system::BluetoothDevicesSettings + std::array<u8, 0x4> bluetooth_device_settings_count; + bool bluetooth_enable_flag; + std::array<u8, 0x3> pad_08065; + bool bluetooth_afh_enable_flag; + std::array<u8, 0x3> pad_08069; + bool bluetooth_boost_enable_flag; + std::array<u8, 0x3> pad_0806D; + std::array<std::array<u8, 0x200>, 10> bluetooth_device_settings_first_10; + + s32 ldn_channel; + + std::array<u8, 0x3C> reserved_09474; + + // nn::util::Uuid MiiAuthorId + std::array<u8, 0x10> mii_author_id; + + std::array<u8, 0x30> reserved_094C0; + + // nn::settings::system::NxControllerSettings + u32 nx_controller_settings_count; + + std::array<u8, 0xC> reserved_094F4; + + // nn::settings::system::NxControllerSettings, + // nn::settings::system::NxControllerLegacySettings on 13.0.0+ + std::array<std::array<u8, 0x40>, 10> nx_controller_legacy_settings; + + std::array<u8, 0x170> reserved_09780; + + bool external_rtc_reset_flag; + std::array<u8, 0x3> pad_098F1; + + std::array<u8, 0x3C> reserved_098F4; + + s32 push_notification_activity_mode_on_sleep; + + std::array<u8, 0x3C> reserved_09934; + + // nn::settings::system::ErrorReportSharePermission + ErrorReportSharePermission error_report_share_permssion; + + std::array<u8, 0x3C> reserved_09974; + + // nn::settings::KeyboardLayout + std::array<u8, 0x4> keyboard_layout; + + std::array<u8, 0x3C> reserved_099B4; + + bool web_inspector_flag; + std::array<u8, 0x3> pad_099F1; + + // nn::settings::system::AllowedSslHost + u32 allowed_ssl_host_count; + + bool memory_usage_rate_flag; + std::array<u8, 0x3> pad_099F9; + + std::array<u8, 0x34> reserved_099FC; + + // nn::settings::system::HostFsMountPoint + std::array<u8, 0x100> host_fs_mount_point; + + // nn::settings::system::AllowedSslHost + std::array<std::array<u8, 0x100>, 8> allowed_ssl_hosts; + + std::array<u8, 0x6C0> reserved_0A330; + + // nn::settings::system::BlePairingSettings + u32 ble_pairing_settings_count; + std::array<u8, 0xC> reserved_0A9F4; + std::array<std::array<u8, 0x80>, 10> ble_pairing_settings; + + // nn::settings::system::AccountOnlineStorageSettings + u32 account_online_storage_settings_count; + std::array<u8, 0xC> reserved_0AF04; + std::array<std::array<u8, 0x40>, 8> account_online_storage_settings; + + bool pctl_ready_flag; + std::array<u8, 0x3> pad_0B111; + + std::array<u8, 0x3C> reserved_0B114; + + // nn::settings::system::ThemeId + std::array<u8, 0x80> theme_id_type0; + std::array<u8, 0x80> theme_id_type1; + + std::array<u8, 0x100> reserved_0B250; + + // nn::settings::ChineseTraditionalInputMethod + ChineseTraditionalInputMethod chinese_traditional_input_method; + + std::array<u8, 0x3C> reserved_0B354; + + bool zoom_flag; + std::array<u8, 0x3> pad_0B391; + + std::array<u8, 0x3C> reserved_0B394; + + // nn::settings::system::ButtonConfigRegisteredSettings + u32 button_config_registered_settings_count; + std::array<u8, 0xC> reserved_0B3D4; + + // nn::settings::system::ButtonConfigSettings + u32 button_config_settings_count; + std::array<u8, 0x4> reserved_0B3E4; + std::array<std::array<u8, 0x5A8>, 5> button_config_settings; + std::array<u8, 0x13B0> reserved_0D030; + u32 button_config_settings_embedded_count; + std::array<u8, 0x4> reserved_0E3E4; + std::array<std::array<u8, 0x5A8>, 5> button_config_settings_embedded; + std::array<u8, 0x13B0> reserved_10030; + u32 button_config_settings_left_count; + std::array<u8, 0x4> reserved_113E4; + std::array<std::array<u8, 0x5A8>, 5> button_config_settings_left; + std::array<u8, 0x13B0> reserved_13030; + u32 button_config_settings_right_count; + std::array<u8, 0x4> reserved_143E4; + std::array<std::array<u8, 0x5A8>, 5> button_config_settings_right; + std::array<u8, 0x73B0> reserved_16030; + // nn::settings::system::ButtonConfigRegisteredSettings + std::array<u8, 0x5C8> button_config_registered_settings_embedded; + std::array<std::array<u8, 0x5C8>, 10> button_config_registered_settings; + + std::array<u8, 0x7FF8> reserved_21378; + + // nn::settings::system::ConsoleSixAxisSensorAccelerationBias + std::array<u8, 0xC> console_six_axis_sensor_acceleration_bias; + // nn::settings::system::ConsoleSixAxisSensorAngularVelocityBias + std::array<u8, 0xC> console_six_axis_sensor_angular_velocity_bias; + // nn::settings::system::ConsoleSixAxisSensorAccelerationGain + std::array<u8, 0x24> console_six_axis_sensor_acceleration_gain; + // nn::settings::system::ConsoleSixAxisSensorAngularVelocityGain + std::array<u8, 0x24> console_six_axis_sensor_angular_velocity_gain; + // nn::settings::system::ConsoleSixAxisSensorAngularVelocityTimeBias + std::array<u8, 0xC> console_six_axis_sensor_angular_velocity_time_bias; + // nn::settings::system::ConsoleSixAxisSensorAngularAcceleration + std::array<u8, 0x24> console_six_axis_sensor_angular_velocity_acceleration; + + std::array<u8, 0x70> reserved_29400; + + bool lock_screen_flag; + std::array<u8, 0x3> pad_29471; + + std::array<u8, 0x4> reserved_249274; + + ColorSet color_set_id; + + QuestFlag quest_flag; + + // nn::settings::system::RegionCode + RegionCode region_code; + + // Different to nn::settings::system::InitialLaunchSettings? + InitialLaunchSettingsPacked initial_launch_settings_packed; + + bool battery_percentage_flag; + std::array<u8, 0x3> pad_294A1; + + // BitFlagSet<32, nn::settings::system::AppletLaunchFlag> + u32 applet_launch_flag; + + // nn::settings::system::ThemeSettings + std::array<u8, 0x8> theme_settings; + // nn::fssystem::ArchiveMacKey + std::array<u8, 0x10> theme_key; + + bool field_testing_flag; + std::array<u8, 0x3> pad_294C1; + + s32 panel_crc_mode; + + std::array<u8, 0x28> reserved_294C8; + + // nn::settings::system::BacklightSettings + std::array<u8, 0x2C> backlight_settings_mixed_up; + + std::array<u8, 0x64> reserved_2951C; + + // nn::time::SystemClockContext + Service::Time::Clock::SystemClockContext user_system_clock_context; + Service::Time::Clock::SystemClockContext network_system_clock_context; + bool user_system_clock_automatic_correction_enabled; + std::array<u8, 0x3> pad_295C1; + std::array<u8, 0x4> reserved_295C4; + // nn::time::SteadyClockTimePoint + Service::Time::Clock::SteadyClockTimePoint + user_system_clock_automatic_correction_updated_time_point; + + std::array<u8, 0x10> reserved_295E0; + + // nn::settings::system::AccountSettings + AccountSettings account_settings; + + std::array<u8, 0xFC> reserved_295F4; + + // nn::settings::system::AudioVolume + std::array<u8, 0x8> audio_volume_type0; + std::array<u8, 0x8> audio_volume_type1; + // nn::settings::system::AudioOutputMode + s32 audio_output_mode_type0; + s32 audio_output_mode_type1; + s32 audio_output_mode_type2; + bool force_mute_on_headphone_removed; + std::array<u8, 0x3> pad_2970D; + s32 headphone_volume_warning_count; + bool heaphone_volume_update_flag; + std::array<u8, 0x3> pad_29715; + // nn::settings::system::AudioVolume + std::array<u8, 0x8> audio_volume_type2; + // nn::settings::system::AudioOutputMode + s32 audio_output_mode_type3; + s32 audio_output_mode_type4; + bool hearing_protection_safeguard_flag; + std::array<u8, 0x3> pad_29729; + std::array<u8, 0x4> reserved_2972C; + s64 hearing_protection_safeguard_remaining_time; + std::array<u8, 0x38> reserved_29738; + + bool console_information_upload_flag; + std::array<u8, 0x3> pad_29771; + + std::array<u8, 0x3C> reserved_29774; + + bool automatic_application_download_flag; + std::array<u8, 0x3> pad_297B1; + + std::array<u8, 0x4> reserved_297B4; + + // nn::settings::system::NotificationSettings + NotificationSettings notification_settings; + + std::array<u8, 0x60> reserved_297D0; + + // nn::settings::system::AccountNotificationSettings + u32 account_notification_settings_count; + std::array<u8, 0xC> reserved_29834; + std::array<AccountNotificationSettings, 8> account_notification_settings; + + std::array<u8, 0x140> reserved_29900; + + f32 vibration_master_volume; + + bool usb_full_key_enable_flag; + std::array<u8, 0x3> pad_29A45; + + // nn::settings::system::AnalogStickUserCalibration + std::array<u8, 0x10> analog_stick_user_calibration_left; + std::array<u8, 0x10> analog_stick_user_calibration_right; + + // nn::settings::system::TouchScreenMode + s32 touch_screen_mode; + + std::array<u8, 0x14> reserved_29A6C; + + // nn::settings::system::TvSettings + TvSettings tv_settings; + + // nn::settings::system::Edid + std::array<u8, 0x100> edid; + + std::array<u8, 0x2E0> reserved_29BA0; + + // nn::settings::system::DataDeletionSettings + std::array<u8, 0x8> data_deletion_settings; + + std::array<u8, 0x38> reserved_29E88; + + // nn::ncm::ProgramId + std::array<u8, 0x8> initial_system_applet_program_id; + std::array<u8, 0x8> overlay_disp_program_id; + + std::array<u8, 0x4> reserved_29ED0; + + bool requires_run_repair_time_reviser; + + std::array<u8, 0x6B> reserved_29ED5; + + // nn::time::LocationName + Service::Time::TimeZone::LocationName device_time_zone_location_name; + std::array<u8, 0x4> reserved_29F64; + // nn::time::SteadyClockTimePoint + Service::Time::Clock::SteadyClockTimePoint device_time_zone_location_updated_time; + + std::array<u8, 0xC0> reserved_29F80; + + // nn::settings::system::PrimaryAlbumStorage + PrimaryAlbumStorage primary_album_storage; + + std::array<u8, 0x3C> reserved_2A044; + + bool usb_30_enable_flag; + std::array<u8, 0x3> pad_2A081; + bool usb_30_host_enable_flag; + std::array<u8, 0x3> pad_2A085; + bool usb_30_device_enable_flag; + std::array<u8, 0x3> pad_2A089; + + std::array<u8, 0x34> reserved_2A08C; + + bool nfc_enable_flag; + std::array<u8, 0x3> pad_2A0C1; + + std::array<u8, 0x3C> reserved_2A0C4; + + // nn::settings::system::SleepSettings + SleepSettings sleep_settings; + + std::array<u8, 0x34> reserved_2A10C; + + // nn::settings::system::EulaVersion + u32 eula_version_count; + std::array<u8, 0xC> reserved_2A144; + std::array<EulaVersion, 32> eula_versions; + + std::array<u8, 0x200> reserved_2A750; + + // nn::settings::system::DeviceNickName + std::array<u8, 0x80> device_nick_name; + + std::array<u8, 0x80> reserved_2A9D0; + + bool auto_update_enable_flag; + std::array<u8, 0x3> pad_2AA51; + + std::array<u8, 0x4C> reserved_2AA54; + + // nn::settings::system::BluetoothDevicesSettings + std::array<std::array<u8, 0x200>, 14> bluetooth_device_settings_last_14; + + std::array<u8, 0x2000> reserved_2C6A0; + + // nn::settings::system::NxControllerSettings + std::array<std::array<u8, 0x800>, 10> nx_controller_settings_data_from_offset_30; +}; + +static_assert(offsetof(SystemSettings, language_code) == 0x10); +static_assert(offsetof(SystemSettings, network_setting_count) == 0x50); +static_assert(offsetof(SystemSettings, network_settings_1B0) == 0x60); +static_assert(offsetof(SystemSettings, bluetooth_device_settings_count) == 0x8060); +static_assert(offsetof(SystemSettings, bluetooth_enable_flag) == 0x8064); +static_assert(offsetof(SystemSettings, bluetooth_device_settings_first_10) == 0x8070); +static_assert(offsetof(SystemSettings, ldn_channel) == 0x9470); +static_assert(offsetof(SystemSettings, mii_author_id) == 0x94B0); +static_assert(offsetof(SystemSettings, nx_controller_settings_count) == 0x94F0); +static_assert(offsetof(SystemSettings, nx_controller_legacy_settings) == 0x9500); +static_assert(offsetof(SystemSettings, external_rtc_reset_flag) == 0x98F0); +static_assert(offsetof(SystemSettings, push_notification_activity_mode_on_sleep) == 0x9930); +static_assert(offsetof(SystemSettings, allowed_ssl_host_count) == 0x99F4); +static_assert(offsetof(SystemSettings, host_fs_mount_point) == 0x9A30); +static_assert(offsetof(SystemSettings, allowed_ssl_hosts) == 0x9B30); +static_assert(offsetof(SystemSettings, ble_pairing_settings_count) == 0xA9F0); +static_assert(offsetof(SystemSettings, ble_pairing_settings) == 0xAA00); +static_assert(offsetof(SystemSettings, account_online_storage_settings_count) == 0xAF00); +static_assert(offsetof(SystemSettings, account_online_storage_settings) == 0xAF10); +static_assert(offsetof(SystemSettings, pctl_ready_flag) == 0xB110); +static_assert(offsetof(SystemSettings, theme_id_type0) == 0xB150); +static_assert(offsetof(SystemSettings, chinese_traditional_input_method) == 0xB350); +static_assert(offsetof(SystemSettings, button_config_registered_settings_count) == 0xB3D0); +static_assert(offsetof(SystemSettings, button_config_settings_count) == 0xB3E0); +static_assert(offsetof(SystemSettings, button_config_settings) == 0xB3E8); +static_assert(offsetof(SystemSettings, button_config_registered_settings_embedded) == 0x1D3E0); +static_assert(offsetof(SystemSettings, console_six_axis_sensor_acceleration_bias) == 0x29370); +static_assert(offsetof(SystemSettings, lock_screen_flag) == 0x29470); +static_assert(offsetof(SystemSettings, battery_percentage_flag) == 0x294A0); +static_assert(offsetof(SystemSettings, field_testing_flag) == 0x294C0); +static_assert(offsetof(SystemSettings, backlight_settings_mixed_up) == 0x294F0); +static_assert(offsetof(SystemSettings, user_system_clock_context) == 0x29580); +static_assert(offsetof(SystemSettings, network_system_clock_context) == 0x295A0); +static_assert(offsetof(SystemSettings, user_system_clock_automatic_correction_enabled) == 0x295C0); +static_assert(offsetof(SystemSettings, user_system_clock_automatic_correction_updated_time_point) == + 0x295C8); +static_assert(offsetof(SystemSettings, account_settings) == 0x295F0); +static_assert(offsetof(SystemSettings, audio_volume_type0) == 0x296F0); +static_assert(offsetof(SystemSettings, hearing_protection_safeguard_remaining_time) == 0x29730); +static_assert(offsetof(SystemSettings, automatic_application_download_flag) == 0x297B0); +static_assert(offsetof(SystemSettings, notification_settings) == 0x297B8); +static_assert(offsetof(SystemSettings, account_notification_settings) == 0x29840); +static_assert(offsetof(SystemSettings, vibration_master_volume) == 0x29A40); +static_assert(offsetof(SystemSettings, analog_stick_user_calibration_left) == 0x29A48); +static_assert(offsetof(SystemSettings, touch_screen_mode) == 0x29A68); +static_assert(offsetof(SystemSettings, edid) == 0x29AA0); +static_assert(offsetof(SystemSettings, data_deletion_settings) == 0x29E80); +static_assert(offsetof(SystemSettings, requires_run_repair_time_reviser) == 0x29ED4); +static_assert(offsetof(SystemSettings, device_time_zone_location_name) == 0x29F40); +static_assert(offsetof(SystemSettings, nfc_enable_flag) == 0x2A0C0); +static_assert(offsetof(SystemSettings, eula_version_count) == 0x2A140); +static_assert(offsetof(SystemSettings, device_nick_name) == 0x2A950); +static_assert(offsetof(SystemSettings, bluetooth_device_settings_last_14) == 0x2AAA0); +static_assert(offsetof(SystemSettings, nx_controller_settings_data_from_offset_30) == 0x2E6A0); + +static_assert(sizeof(SystemSettings) == 0x336A0, "SystemSettings has the wrong size!"); + +SystemSettings DefaultSystemSettings(); + +} // namespace Service::Set diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 6c8427b0d..0fbb43057 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -240,7 +240,7 @@ private: return ret; } - Result ReadImpl(std::vector<u8>* out_data, size_t size) { + Result ReadImpl(std::vector<u8>* out_data) { ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); size_t actual_size{}; Result res = backend->Read(&actual_size, *out_data); @@ -326,8 +326,8 @@ private: } void Read(HLERequestContext& ctx) { - std::vector<u8> output_bytes; - const Result res = ReadImpl(&output_bytes, ctx.GetWriteBufferSize()); + std::vector<u8> output_bytes(ctx.GetWriteBufferSize()); + const Result res = ReadImpl(&output_bytes); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(res); if (res == ResultSuccess) { diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp index d30f49877..71ce9be50 100644 --- a/src/core/hle/service/vi/display/vi_display.cpp +++ b/src/core/hle/service/vi/display/vi_display.cpp @@ -51,11 +51,24 @@ Display::~Display() { } Layer& Display::GetLayer(std::size_t index) { - return *layers.at(index); + size_t i = 0; + for (auto& layer : layers) { + if (!layer->IsOpen()) { + continue; + } + + if (i == index) { + return *layer; + } + + i++; + } + + UNREACHABLE(); } -const Layer& Display::GetLayer(std::size_t index) const { - return *layers.at(index); +size_t Display::GetNumLayers() const { + return std::ranges::count_if(layers, [](auto& l) { return l->IsOpen(); }); } Result Display::GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event) { @@ -92,7 +105,11 @@ void Display::CreateLayer(u64 layer_id, u32 binder_id, hos_binder_driver_server.RegisterProducer(std::move(producer)); } -void Display::CloseLayer(u64 layer_id) { +void Display::DestroyLayer(u64 layer_id) { + if (auto* layer = this->FindLayer(layer_id); layer != nullptr) { + layer->GetConsumer().Abandon(); + } + std::erase_if(layers, [layer_id](const auto& layer) { return layer->GetLayerId() == layer_id; }); } diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index 101cbce20..1d9360b96 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h @@ -66,18 +66,13 @@ public: /// Whether or not this display has any layers added to it. bool HasLayers() const { - return !layers.empty(); + return GetNumLayers() > 0; } /// Gets a layer for this display based off an index. Layer& GetLayer(std::size_t index); - /// Gets a layer for this display based off an index. - const Layer& GetLayer(std::size_t index) const; - - std::size_t GetNumLayers() const { - return layers.size(); - } + std::size_t GetNumLayers() const; /** * Gets the internal vsync event. @@ -100,11 +95,11 @@ public: /// void CreateLayer(u64 layer_id, u32 binder_id, Service::Nvidia::NvCore::Container& core); - /// Closes and removes a layer from this display with the given ID. + /// Removes a layer from this display with the given ID. /// - /// @param layer_id The ID assigned to the layer to close. + /// @param layer_id The ID assigned to the layer to destroy. /// - void CloseLayer(u64 layer_id); + void DestroyLayer(u64 layer_id); /// Resets the display for a new connection. void Reset() { diff --git a/src/core/hle/service/vi/layer/vi_layer.cpp b/src/core/hle/service/vi/layer/vi_layer.cpp index 9ae2e0e44..04e52a23b 100644 --- a/src/core/hle/service/vi/layer/vi_layer.cpp +++ b/src/core/hle/service/vi/layer/vi_layer.cpp @@ -8,8 +8,8 @@ namespace Service::VI { Layer::Layer(u64 layer_id_, u32 binder_id_, android::BufferQueueCore& core_, android::BufferQueueProducer& binder_, std::shared_ptr<android::BufferItemConsumer>&& consumer_) - : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, consumer{std::move( - consumer_)} {} + : layer_id{layer_id_}, binder_id{binder_id_}, core{core_}, binder{binder_}, + consumer{std::move(consumer_)}, open{false} {} Layer::~Layer() = default; diff --git a/src/core/hle/service/vi/layer/vi_layer.h b/src/core/hle/service/vi/layer/vi_layer.h index 8cf1b5275..295005e23 100644 --- a/src/core/hle/service/vi/layer/vi_layer.h +++ b/src/core/hle/service/vi/layer/vi_layer.h @@ -71,12 +71,25 @@ public: return core; } + bool IsOpen() const { + return open; + } + + void Close() { + open = false; + } + + void Open() { + open = true; + } + private: const u64 layer_id; const u32 binder_id; android::BufferQueueCore& core; android::BufferQueueProducer& binder; std::shared_ptr<android::BufferItemConsumer> consumer; + bool open; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index b1bfb9898..9ab8788e3 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -719,6 +719,8 @@ private: return; } + nv_flinger.OpenLayer(layer_id); + android::OutputParcel parcel; parcel.WriteInterface(NativeWindow{*buffer_queue_id}); @@ -783,6 +785,7 @@ private: const u64 layer_id = rp.Pop<u64>(); LOG_WARNING(Service_VI, "(STUBBED) called. layer_id=0x{:016X}", layer_id); + nv_flinger.DestroyLayer(layer_id); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index db30ba598..3fc4024dc 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp @@ -62,7 +62,7 @@ u64 StandardVmCallbacks::HidKeysDown() { } const auto applet_resource = hid->GetResourceManager(); - if (applet_resource == nullptr) { + if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) { LOG_WARNING(CheatEngine, "Attempted to read input state, but applet resource is not initialized!"); return 0; diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp index 1a0491c2c..d9f99148b 100644 --- a/src/frontend_common/config.cpp +++ b/src/frontend_common/config.cpp @@ -214,6 +214,7 @@ void Config::ReadControlValues() { } void Config::ReadMotionTouchValues() { + Settings::values.touch_from_button_maps.clear(); int num_touch_from_button_maps = BeginArray(std::string("touch_from_button_maps")); if (num_touch_from_button_maps > 0) { diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp index 618793668..2dbff21af 100644 --- a/src/tests/video_core/memory_tracker.cpp +++ b/src/tests/video_core/memory_tracker.cpp @@ -23,13 +23,13 @@ constexpr VAddr c = 16 * HIGH_PAGE_SIZE; class RasterizerInterface { public: - void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { + void UpdatePagesCachedCount(VAddr addr, u64 size, bool cache) { const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS}; const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >> Core::Memory::YUZU_PAGEBITS}; for (u64 page = page_start; page < page_end; ++page) { int& value = page_table[page]; - value += delta; + value += (cache ? 1 : -1); if (value < 0) { throw std::logic_error{"negative page"}; } @@ -546,4 +546,4 @@ TEST_CASE("MemoryTracker: Cached write downloads") { REQUIRE(!memory_track->IsRegionGpuModified(c + PAGE, PAGE)); memory_track->MarkRegionAsCpuModified(c, WORD); REQUIRE(rasterizer.Count() == 0); -}
\ No newline at end of file +} diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h index a336bde41..95b752055 100644 --- a/src/video_core/buffer_cache/word_manager.h +++ b/src/video_core/buffer_cache/word_manager.h @@ -473,7 +473,7 @@ private: VAddr addr = cpu_addr + word_index * BYTES_PER_WORD; IteratePages(changed_bits, [&](size_t offset, size_t size) { rasterizer->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, - size * BYTES_PER_PAGE, add_to_rasterizer ? 1 : -1); + size * BYTES_PER_PAGE, add_to_rasterizer); }); } diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp index f200a650f..3c9477f6e 100644 --- a/src/video_core/rasterizer_accelerated.cpp +++ b/src/video_core/rasterizer_accelerated.cpp @@ -3,6 +3,7 @@ #include <atomic> +#include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" #include "common/div_ceil.h" @@ -11,61 +12,65 @@ namespace VideoCore { +static constexpr u16 IdentityValue = 1; + using namespace Core::Memory; -RasterizerAccelerated::RasterizerAccelerated(Memory& cpu_memory_) - : cached_pages(std::make_unique<CachedPages>()), cpu_memory{cpu_memory_} {} +RasterizerAccelerated::RasterizerAccelerated(Memory& cpu_memory_) : map{}, cpu_memory{cpu_memory_} { + // We are tracking CPU memory, which cannot map more than 39 bits. + const VAddr start_address = 0; + const VAddr end_address = (1ULL << 39); + const IntervalType address_space_interval(start_address, end_address); + const auto value = std::make_pair(address_space_interval, IdentityValue); + + map.add(value); +} RasterizerAccelerated::~RasterizerAccelerated() = default; -void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { - u64 uncache_begin = 0; - u64 cache_begin = 0; - u64 uncache_bytes = 0; - u64 cache_bytes = 0; - - std::atomic_thread_fence(std::memory_order_acquire); - const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE); - for (u64 page = addr >> YUZU_PAGEBITS; page != page_end; ++page) { - std::atomic_uint16_t& count = cached_pages->at(page >> 2).Count(page); - - if (delta > 0) { - ASSERT_MSG(count.load(std::memory_order::relaxed) < UINT16_MAX, "Count may overflow!"); - } else if (delta < 0) { - ASSERT_MSG(count.load(std::memory_order::relaxed) > 0, "Count may underflow!"); - } else { - ASSERT_MSG(false, "Delta must be non-zero!"); - } +void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, bool cache) { + std::scoped_lock lk{map_lock}; - // Adds or subtracts 1, as count is a unsigned 8-bit value - count.fetch_add(static_cast<u16>(delta), std::memory_order_release); - - // Assume delta is either -1 or 1 - if (count.load(std::memory_order::relaxed) == 0) { - if (uncache_bytes == 0) { - uncache_begin = page; - } - uncache_bytes += YUZU_PAGESIZE; - } else if (uncache_bytes > 0) { - cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes, - false); - uncache_bytes = 0; - } - if (count.load(std::memory_order::relaxed) == 1 && delta > 0) { - if (cache_bytes == 0) { - cache_begin = page; - } - cache_bytes += YUZU_PAGESIZE; - } else if (cache_bytes > 0) { - cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true); - cache_bytes = 0; + // Align sizes. + addr = Common::AlignDown(addr, YUZU_PAGESIZE); + size = Common::AlignUp(size, YUZU_PAGESIZE); + + // Declare the overall interval we are going to operate on. + const VAddr start_address = addr; + const VAddr end_address = addr + size; + const IntervalType modification_range(start_address, end_address); + + // Find the boundaries of where to iterate. + const auto lower = map.lower_bound(modification_range); + const auto upper = map.upper_bound(modification_range); + + // Iterate over the contained intervals. + for (auto it = lower; it != upper; it++) { + // Intersect interval range with modification range. + const auto current_range = modification_range & it->first; + + // Calculate the address and size to operate over. + const auto current_addr = current_range.lower(); + const auto current_size = current_range.upper() - current_addr; + + // Get the current value of the range. + const auto value = it->second; + + if (cache && value == IdentityValue) { + // If we are going to cache, and the value is not yet referenced, then cache this range. + cpu_memory.RasterizerMarkRegionCached(current_addr, current_size, true); + } else if (!cache && value == IdentityValue + 1) { + // If we are going to uncache, and this is the last reference, then uncache this range. + cpu_memory.RasterizerMarkRegionCached(current_addr, current_size, false); } } - if (uncache_bytes > 0) { - cpu_memory.RasterizerMarkRegionCached(uncache_begin << YUZU_PAGEBITS, uncache_bytes, false); - } - if (cache_bytes > 0) { - cpu_memory.RasterizerMarkRegionCached(cache_begin << YUZU_PAGEBITS, cache_bytes, true); + + // Update the set. + const auto value = std::make_pair(modification_range, IdentityValue); + if (cache) { + map.add(value); + } else { + map.subtract(value); } } diff --git a/src/video_core/rasterizer_accelerated.h b/src/video_core/rasterizer_accelerated.h index e6c0ea87a..f1968f186 100644 --- a/src/video_core/rasterizer_accelerated.h +++ b/src/video_core/rasterizer_accelerated.h @@ -3,8 +3,8 @@ #pragma once -#include <array> -#include <atomic> +#include <mutex> +#include <boost/icl/interval_map.hpp> #include "common/common_types.h" #include "video_core/rasterizer_interface.h" @@ -21,28 +21,17 @@ public: explicit RasterizerAccelerated(Core::Memory::Memory& cpu_memory_); ~RasterizerAccelerated() override; - void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) override; + void UpdatePagesCachedCount(VAddr addr, u64 size, bool cache) override; private: - class CacheEntry final { - public: - CacheEntry() = default; + using PageIndex = VAddr; + using PageReferenceCount = u16; - std::atomic_uint16_t& Count(std::size_t page) { - return values[page & 3]; - } + using IntervalMap = boost::icl::interval_map<PageIndex, PageReferenceCount>; + using IntervalType = IntervalMap::interval_type; - const std::atomic_uint16_t& Count(std::size_t page) const { - return values[page & 3]; - } - - private: - std::array<std::atomic_uint16_t, 4> values{}; - }; - static_assert(sizeof(CacheEntry) == 8, "CacheEntry should be 8 bytes!"); - - using CachedPages = std::array<CacheEntry, 0x2000000>; - std::unique_ptr<CachedPages> cached_pages; + IntervalMap map; + std::mutex map_lock; Core::Memory::Memory& cpu_memory; }; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index af1469147..fd42d26b5 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -162,7 +162,7 @@ public: } /// Increase/decrease the number of object in pages touching the specified region - virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {} + virtual void UpdatePagesCachedCount(VAddr addr, u64 size, bool cache) {} /// Initialize disk cached resources for the game being emulated virtual void LoadDiskResources(u64 title_id, std::stop_token stop_loading, diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp index e6c70fb34..a71866b75 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -333,7 +333,7 @@ void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings< glBindBuffersRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast<GLsizei>(bindings.buffers.size()), buffer_handles.data(), reinterpret_cast<const GLintptr*>(bindings.offsets.data()), - reinterpret_cast<const GLsizeiptr*>(bindings.strides.data())); + reinterpret_cast<const GLsizeiptr*>(bindings.sizes.data())); } void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index 8e4c74b5c..a59e2d2d1 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -102,8 +102,8 @@ PresentManager::PresentManager(const vk::Instance& instance_, memory_allocator{memory_allocator_}, scheduler{scheduler_}, swapchain{swapchain_}, surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())}, - use_present_thread{Settings::values.async_presentation.GetValue()}, - image_count{swapchain.GetImageCount()} { + use_present_thread{Settings::values.async_presentation.GetValue()} { + SetImageCount(); auto& dld = device.GetLogical(); cmdpool = dld.CreateCommandPool({ @@ -289,7 +289,14 @@ void PresentManager::PresentThread(std::stop_token token) { void PresentManager::RecreateSwapchain(Frame* frame) { swapchain.Create(*surface, frame->width, frame->height); - image_count = swapchain.GetImageCount(); + SetImageCount(); +} + +void PresentManager::SetImageCount() { + // We cannot have more than 5 images in flight at any given time. + // FRAMES_IN_FLIGHT is 7, and the cache TICKS_TO_DESTROY is 6. + // Mali drivers will give us 6. + image_count = std::min<size_t>(swapchain.GetImageCount(), 5); } void PresentManager::CopyToSwapchain(Frame* frame) { diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h index 337171a09..23ee61c8c 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.h +++ b/src/video_core/renderer_vulkan/vk_present_manager.h @@ -62,6 +62,8 @@ private: void RecreateSwapchain(Frame* frame); + void SetImageCount(); + private: const vk::Instance& instance; Core::Frontend::EmuWindow& render_window; diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp index e81cd031b..a109f9cbe 100644 --- a/src/video_core/shader_cache.cpp +++ b/src/video_core/shader_cache.cpp @@ -132,7 +132,7 @@ void ShaderCache::Register(std::unique_ptr<ShaderInfo> data, VAddr addr, size_t storage.push_back(std::move(data)); - rasterizer.UpdatePagesCachedCount(addr, size, 1); + rasterizer.UpdatePagesCachedCount(addr, size, true); } void ShaderCache::InvalidatePagesInRegion(VAddr addr, size_t size) { @@ -209,7 +209,7 @@ void ShaderCache::UnmarkMemory(Entry* entry) { const VAddr addr = entry->addr_start; const size_t size = entry->addr_end - addr; - rasterizer.UpdatePagesCachedCount(addr, size, -1); + rasterizer.UpdatePagesCachedCount(addr, size, false); } void ShaderCache::RemoveShadersFromStorage(std::span<ShaderInfo*> removed_shaders) { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 0d5a1709f..d7941f6a4 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -2080,7 +2080,7 @@ void TextureCache<P>::TrackImage(ImageBase& image, ImageId image_id) { ASSERT(False(image.flags & ImageFlagBits::Tracked)); image.flags |= ImageFlagBits::Tracked; if (False(image.flags & ImageFlagBits::Sparse)) { - rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, 1); + rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, true); return; } if (True(image.flags & ImageFlagBits::Registered)) { @@ -2091,13 +2091,13 @@ void TextureCache<P>::TrackImage(ImageBase& image, ImageId image_id) { const auto& map = slot_map_views[map_view_id]; const VAddr cpu_addr = map.cpu_addr; const std::size_t size = map.size; - rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1); + rasterizer.UpdatePagesCachedCount(cpu_addr, size, true); } return; } ForEachSparseSegment(image, [this]([[maybe_unused]] GPUVAddr gpu_addr, VAddr cpu_addr, size_t size) { - rasterizer.UpdatePagesCachedCount(cpu_addr, size, 1); + rasterizer.UpdatePagesCachedCount(cpu_addr, size, true); }); } @@ -2106,7 +2106,7 @@ void TextureCache<P>::UntrackImage(ImageBase& image, ImageId image_id) { ASSERT(True(image.flags & ImageFlagBits::Tracked)); image.flags &= ~ImageFlagBits::Tracked; if (False(image.flags & ImageFlagBits::Sparse)) { - rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, -1); + rasterizer.UpdatePagesCachedCount(image.cpu_addr, image.guest_size_bytes, false); return; } ASSERT(True(image.flags & ImageFlagBits::Registered)); @@ -2117,7 +2117,7 @@ void TextureCache<P>::UntrackImage(ImageBase& image, ImageId image_id) { const auto& map = slot_map_views[map_view_id]; const VAddr cpu_addr = map.cpu_addr; const std::size_t size = map.size; - rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1); + rasterizer.UpdatePagesCachedCount(cpu_addr, size, false); } } diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index dd43f0a0e..c8e871151 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp @@ -193,8 +193,8 @@ void ConfigureUi::RequestGameListUpdate() { void ConfigureUi::SetConfiguration() { ui->theme_combobox->setCurrentIndex( ui->theme_combobox->findData(QString::fromStdString(UISettings::values.theme))); - ui->language_combobox->setCurrentIndex( - ui->language_combobox->findData(QString::fromStdString(UISettings::values.language))); + ui->language_combobox->setCurrentIndex(ui->language_combobox->findData( + QString::fromStdString(UISettings::values.language.GetValue()))); ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue()); ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); ui->show_size->setChecked(UISettings::values.show_size.GetValue()); diff --git a/src/yuzu/configuration/qt_config.cpp b/src/yuzu/configuration/qt_config.cpp index 636c5e640..417a43ec5 100644 --- a/src/yuzu/configuration/qt_config.cpp +++ b/src/yuzu/configuration/qt_config.cpp @@ -187,7 +187,6 @@ void QtConfig::ReadPathValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); UISettings::values.roms_path = ReadStringSetting(std::string("romsPath")); - UISettings::values.symbols_path = ReadStringSetting(std::string("symbolsPath")); UISettings::values.game_dir_deprecated = ReadStringSetting(std::string("gameListRootDir"), std::string(".")); UISettings::values.game_dir_deprecated_deepscan = @@ -225,8 +224,6 @@ void QtConfig::ReadPathValues() { UISettings::values.recent_files = QString::fromStdString(ReadStringSetting(std::string("recentFiles"))) .split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive); - UISettings::values.language = - ReadStringSetting(std::string("language"), std::make_optional(std::string(""))); EndGroup(); } @@ -409,7 +406,6 @@ void QtConfig::SavePathValues() { BeginGroup(Settings::TranslateCategory(Settings::Category::Paths)); WriteSetting(std::string("romsPath"), UISettings::values.roms_path); - WriteSetting(std::string("symbolsPath"), UISettings::values.symbols_path); BeginArray(std::string("gamedirs")); for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) { SetArrayIndex(i); @@ -422,7 +418,6 @@ void QtConfig::SavePathValues() { WriteSetting(std::string("recentFiles"), UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString()); - WriteSetting(std::string("language"), UISettings::values.language); EndGroup(); } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index f31ed7ebb..059fcf041 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -5147,12 +5147,12 @@ void GMainWindow::UpdateUITheme() { void GMainWindow::LoadTranslation() { bool loaded; - if (UISettings::values.language.empty()) { + if (UISettings::values.language.GetValue().empty()) { // If the selected language is empty, use system locale loaded = translator.load(QLocale(), {}, {}, QStringLiteral(":/languages/")); } else { // Otherwise load from the specified file - loaded = translator.load(QString::fromStdString(UISettings::values.language), + loaded = translator.load(QString::fromStdString(UISettings::values.language.GetValue()), QStringLiteral(":/languages/")); } @@ -5164,7 +5164,7 @@ void GMainWindow::LoadTranslation() { } void GMainWindow::OnLanguageChanged(const QString& locale) { - if (UISettings::values.language != std::string("en")) { + if (UISettings::values.language.GetValue() != std::string("en")) { qApp->removeTranslator(&translator); } diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 549a39e1b..f9906be33 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -154,12 +154,11 @@ struct Values { Setting<u32> screenshot_height{linkage, 0, "screenshot_height", Category::Screenshots}; std::string roms_path; - std::string symbols_path; std::string game_dir_deprecated; bool game_dir_deprecated_deepscan; QVector<GameDir> game_dirs; QStringList recent_files; - std::string language; + Setting<std::string> language{linkage, {}, "language", Category::Paths}; std::string theme; |