summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt76
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt24
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt30
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt72
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt128
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsDialogFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt13
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt61
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt19
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt33
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt21
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt39
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NativeConfig.kt20
-rw-r--r--src/android/app/src/main/jni/android_config.cpp50
-rw-r--r--src/android/app/src/main/jni/android_config.h8
-rw-r--r--src/android/app/src/main/jni/android_settings.h8
-rw-r--r--src/android/app/src/main/jni/id_cache.cpp16
-rw-r--r--src/android/app/src/main/jni/id_cache.h2
-rw-r--r--src/android/app/src/main/jni/native.cpp11
-rw-r--r--src/android/app/src/main/jni/native_config.cpp52
-rw-r--r--src/android/app/src/main/res/layout/card_folder.xml70
-rw-r--r--src/android/app/src/main/res/layout/dialog_add_folder.xml45
-rw-r--r--src/android/app/src/main/res/layout/dialog_folder_properties.xml30
-rw-r--r--src/android/app/src/main/res/layout/fragment_folders.xml48
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml7
-rw-r--r--src/android/app/src/main/res/values/arrays.xml18
-rw-r--r--src/android/app/src/main/res/values/dimens.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml12
-rw-r--r--src/common/CMakeLists.txt19
-rw-r--r--src/common/free_region_manager.h55
-rw-r--r--src/common/host_memory.cpp219
-rw-r--r--src/common/host_memory.h15
-rw-r--r--src/common/linux/gamemode.cpp40
-rw-r--r--src/common/linux/gamemode.h24
-rw-r--r--src/common/settings.cpp19
-rw-r--r--src/common/settings.h14
-rw-r--r--src/common/settings_common.h1
-rw-r--r--src/common/settings_enums.h2
-rw-r--r--src/common/signal_chain.cpp42
-rw-r--r--src/common/signal_chain.h19
-rw-r--r--src/common/wall_clock.cpp4
-rw-r--r--src/core/CMakeLists.txt16
-rw-r--r--src/core/arm/arm_interface.cpp2
-rw-r--r--src/core/arm/arm_interface.h3
-rw-r--r--src/core/arm/nce/arm_nce.cpp400
-rw-r--r--src/core/arm/nce/arm_nce.h108
-rw-r--r--src/core/arm/nce/arm_nce.s222
-rw-r--r--src/core/arm/nce/arm_nce_asm_definitions.h29
-rw-r--r--src/core/arm/nce/guest_context.h50
-rw-r--r--src/core/arm/nce/instructions.h147
-rw-r--r--src/core/arm/nce/patcher.cpp474
-rw-r--r--src/core/arm/nce/patcher.h98
-rw-r--r--src/core/cpu_manager.cpp2
-rw-r--r--src/core/device_memory.cpp3
-rw-r--r--src/core/file_sys/patch_manager.cpp4
-rw-r--r--src/core/file_sys/romfs.cpp19
-rw-r--r--src/core/file_sys/romfs_factory.cpp2
-rw-r--r--src/core/frontend/emu_window.h10
-rw-r--r--src/core/hle/kernel/code_set.h14
-rw-r--r--src/core/hle/kernel/k_address_space_info.cpp4
-rw-r--r--src/core/hle/kernel/k_page_table_base.cpp33
-rw-r--r--src/core/hle/kernel/k_page_table_base.h3
-rw-r--r--src/core/hle/kernel/k_process.cpp23
-rw-r--r--src/core/hle/kernel/k_process.h14
-rw-r--r--src/core/hle/kernel/k_process_page_table.h9
-rw-r--r--src/core/hle/kernel/k_thread.h16
-rw-r--r--src/core/hle/kernel/physical_core.cpp14
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp12
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h4
-rw-r--r--src/core/hle/service/hid/hid_server.cpp16
-rw-r--r--src/core/hle/service/hid/hid_server.h1
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp2
-rw-r--r--src/core/hle/service/nfc/common/device.cpp70
-rw-r--r--src/core/hle/service/nfc/common/device.h1
-rw-r--r--src/core/hle/service/set/set_sys.cpp4
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp63
-rw-r--r--src/core/loader/kip.cpp3
-rw-r--r--src/core/loader/nca.cpp6
-rw-r--r--src/core/loader/nro.cpp63
-rw-r--r--src/core/loader/nro.h2
-rw-r--r--src/core/loader/nso.cpp67
-rw-r--r--src/core/loader/nso.h7
-rw-r--r--src/core/memory.cpp71
-rw-r--r--src/core/memory.h18
-rw-r--r--src/frontend_common/config.cpp2
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp9
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.cpp5
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp4
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate_program.cpp2
-rw-r--r--src/shader_recompiler/host_translate_info.h1
-rw-r--r--src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp13
-rw-r--r--src/shader_recompiler/ir_opt/passes.h2
-rw-r--r--src/shader_recompiler/profile.h2
-rw-r--r--src/tests/common/host_memory.cpp71
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h20
-rw-r--r--src/video_core/buffer_cache/usage_tracker.h4
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp8
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h10
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp18
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_staging_buffer_pool.cpp66
-rw-r--r--src/video_core/renderer_opengl/gl_staging_buffer_pool.h22
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp8
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h8
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp7
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h4
-rw-r--r--src/video_core/texture_cache/texture_cache.h2
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp71
-rw-r--r--src/yuzu/bootmanager.cpp56
-rw-r--r--src/yuzu/bootmanager.h6
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp12
-rw-r--r--src/yuzu/configuration/configure_cpu.h1
-rw-r--r--src/yuzu/configuration/configure_cpu.ui30
-rw-r--r--src/yuzu/configuration/configure_general.cpp43
-rw-r--r--src/yuzu/configuration/configure_general.ui27
-rw-r--r--src/yuzu/configuration/configure_system.ui2
-rw-r--r--src/yuzu/configuration/shared_translation.cpp9
-rw-r--r--src/yuzu/main.cpp79
-rw-r--r--src/yuzu/main.h4
-rw-r--r--src/yuzu_cmd/yuzu.cpp12
136 files changed, 3785 insertions, 499 deletions
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 f2ba2504c..e0f01127c 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
@@ -300,6 +300,11 @@ object NativeLibrary {
external fun getPerfStats(): DoubleArray
/**
+ * Returns the current CPU backend.
+ */
+ external fun getCpuBackend(): String
+
+ /**
* Notifies the core emulation that the orientation has changed.
*/
external fun notifyOrientationChange(layout_option: Int, rotation: Int)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
new file mode 100644
index 000000000..ab657a7b9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/FolderAdapter.kt
@@ -0,0 +1,76 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import android.net.Uri
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.fragment.app.FragmentActivity
+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.CardFolderBinding
+import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
+import org.yuzu.yuzu_emu.model.GameDir
+import org.yuzu.yuzu_emu.model.GamesViewModel
+
+class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesViewModel) :
+ ListAdapter<GameDir, FolderAdapter.FolderViewHolder>(
+ AsyncDifferConfig.Builder(DiffCallback()).build()
+ ) {
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): FolderAdapter.FolderViewHolder {
+ CardFolderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ .also { return FolderViewHolder(it) }
+ }
+
+ override fun onBindViewHolder(holder: FolderAdapter.FolderViewHolder, position: Int) =
+ holder.bind(currentList[position])
+
+ inner class FolderViewHolder(val binding: CardFolderBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ private lateinit var gameDir: GameDir
+
+ fun bind(gameDir: GameDir) {
+ this.gameDir = gameDir
+
+ binding.apply {
+ path.text = Uri.parse(gameDir.uriString).path
+ path.postDelayed(
+ {
+ path.isSelected = true
+ path.ellipsize = TextUtils.TruncateAt.MARQUEE
+ },
+ 3000
+ )
+
+ buttonEdit.setOnClickListener {
+ GameFolderPropertiesDialogFragment.newInstance(this@FolderViewHolder.gameDir)
+ .show(
+ activity.supportFragmentManager,
+ GameFolderPropertiesDialogFragment.TAG
+ )
+ }
+
+ buttonDelete.setOnClickListener {
+ gamesViewModel.removeFolder(this@FolderViewHolder.gameDir)
+ }
+ }
+ }
+ }
+
+ private class DiffCallback : DiffUtil.ItemCallback<GameDir>() {
+ override fun areItemsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun areContentsTheSame(oldItem: GameDir, newItem: GameDir): Boolean {
+ return oldItem == newItem
+ }
+ }
+}
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 151362124..ef10b209f 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
@@ -10,6 +10,7 @@ enum class IntSetting(
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),
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 d005c656e..e3cd66185 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
@@ -3,33 +3,9 @@
package org.yuzu.yuzu_emu.features.settings.model
-import android.text.TextUtils
-import android.widget.Toast
import org.yuzu.yuzu_emu.R
-import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.utils.NativeConfig
object Settings {
- private val context get() = YuzuApplication.appContext
-
- fun saveSettings(gameId: String = "") {
- if (TextUtils.isEmpty(gameId)) {
- Toast.makeText(
- context,
- context.getString(R.string.ini_saved),
- Toast.LENGTH_SHORT
- ).show()
- NativeConfig.saveSettings()
- } else {
- // TODO: Save custom game settings
- Toast.makeText(
- context,
- context.getString(R.string.gameid_saved, gameId),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
-
enum class Category {
Android,
Audio,
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 6aba69dbe..e198b18a0 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
@@ -79,6 +79,15 @@ abstract class SettingsItem(
)
put(
SingleChoiceSetting(
+ IntSetting.CPU_BACKEND,
+ R.string.cpu_backend,
+ 0,
+ R.array.cpuBackendArm64Names,
+ R.array.cpuBackendArm64Values
+ )
+ )
+ put(
+ SingleChoiceSetting(
IntSetting.CPU_ACCURACY,
R.string.cpu_accuracy,
0,
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 48bdbdd75..64bfc6dd0 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,12 +19,13 @@ 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 java.io.IOException
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
-import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.ResetSettingsDialogFragment
import org.yuzu.yuzu_emu.model.SettingsViewModel
@@ -53,10 +54,6 @@ class SettingsActivity : AppCompatActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
- if (savedInstanceState != null) {
- settingsViewModel.shouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE)
- }
-
if (InsetsHelper.getSystemGestureType(applicationContext) !=
InsetsHelper.GESTURE_NAVIGATION
) {
@@ -127,12 +124,6 @@ class SettingsActivity : AppCompatActivity() {
}
}
- override fun onSaveInstanceState(outState: Bundle) {
- // Critical: If super method is not called, rotations will be busted.
- super.onSaveInstanceState(outState)
- outState.putBoolean(KEY_SHOULD_SAVE, settingsViewModel.shouldSave)
- }
-
override fun onStart() {
super.onStart()
// TODO: Load custom settings contextually
@@ -141,16 +132,10 @@ class SettingsActivity : AppCompatActivity() {
}
}
- /**
- * If this is called, the user has left the settings screen (potentially through the
- * home button) and will expect their changes to be persisted. So we kick off an
- * IntentService which will do so on a background thread.
- */
override fun onStop() {
super.onStop()
- if (isFinishing && settingsViewModel.shouldSave) {
- Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
- Settings.saveSettings()
+ CoroutineScope(Dispatchers.IO).launch {
+ NativeConfig.saveSettings()
}
}
@@ -160,9 +145,6 @@ class SettingsActivity : AppCompatActivity() {
}
fun onSettingsReset() {
- // Prevents saving to a non-existent settings file
- settingsViewModel.shouldSave = false
-
// 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)
@@ -194,8 +176,4 @@ class SettingsActivity : AppCompatActivity() {
windowInsets
}
}
-
- companion object {
- private const val KEY_SHOULD_SAVE = "should_save"
- }
}
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 a7a029fc1..af2c1e582 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
@@ -105,7 +105,6 @@ class SettingsAdapter(
fun onBooleanClick(item: SwitchSetting, checked: Boolean) {
item.checked = checked
settingsViewModel.setShouldReloadSettingsList(true)
- settingsViewModel.shouldSave = true
}
fun onSingleChoiceClick(item: SingleChoiceSetting, position: Int) {
@@ -161,7 +160,6 @@ class SettingsAdapter(
epochTime += timePicker.hour.toLong() * 60 * 60
epochTime += timePicker.minute.toLong() * 60
if (item.value != epochTime) {
- settingsViewModel.shouldSave = true
notifyItemChanged(position)
item.value = epochTime
}
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 8b71e32f3..7425728c6 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
@@ -269,6 +269,7 @@ class SettingsFragmentPresenter(
add(BooleanSetting.RENDERER_DEBUG.key)
add(HeaderSetting(R.string.cpu))
+ add(IntSetting.CPU_BACKEND.key)
add(IntSetting.CPU_ACCURACY.key)
add(BooleanSetting.CPU_DEBUG_MODE.key)
add(SettingsItem.FASTMEM_COMBINED)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt
new file mode 100644
index 000000000..dec2b7cf1
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddGameFolderDialogFragment.kt
@@ -0,0 +1,53 @@
+// 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.net.Uri
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogAddFolderBinding
+import org.yuzu.yuzu_emu.model.GameDir
+import org.yuzu.yuzu_emu.model.GamesViewModel
+
+class AddGameFolderDialogFragment : DialogFragment() {
+ private val gamesViewModel: GamesViewModel by activityViewModels()
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val binding = DialogAddFolderBinding.inflate(layoutInflater)
+ val folderUriString = requireArguments().getString(FOLDER_URI_STRING)
+ if (folderUriString == null) {
+ dismiss()
+ }
+ binding.path.text = Uri.parse(folderUriString).path
+
+ return MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.add_game_folder)
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+ val newGameDir = GameDir(folderUriString!!, binding.deepScanSwitch.isChecked)
+ gamesViewModel.addFolder(newGameDir)
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .setView(binding.root)
+ .show()
+ }
+
+ companion object {
+ const val TAG = "AddGameFolderDialogFragment"
+
+ private const val FOLDER_URI_STRING = "FolderUriString"
+
+ fun newInstance(folderUriString: String): AddGameFolderDialogFragment {
+ val args = Bundle()
+ args.putString(FOLDER_URI_STRING, folderUriString)
+ val fragment = AddGameFolderDialogFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
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 c32fa0d7e..734c1d5ca 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
@@ -414,8 +414,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
perfStatsUpdater = {
if (emulationViewModel.emulationStarted.value) {
val perfStats = NativeLibrary.getPerfStats()
+ val cpuBackend = NativeLibrary.getCpuBackend()
if (_binding != null) {
- binding.showFpsText.text = String.format("FPS: %.1f", perfStats[FPS])
+ binding.showFpsText.text =
+ String.format("FPS: %.1f\n%s", perfStats[FPS], cpuBackend)
}
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 800)
}
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
new file mode 100644
index 000000000..b6c2e4635
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFolderPropertiesDialogFragment.kt
@@ -0,0 +1,72 @@
+// 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 com.google.android.material.dialog.MaterialAlertDialogBuilder
+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.SerializableHelper.parcelable
+
+class GameFolderPropertiesDialogFragment : DialogFragment() {
+ private val gamesViewModel: GamesViewModel by activityViewModels()
+
+ private var deepScan = false
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val binding = DialogFolderPropertiesBinding.inflate(layoutInflater)
+ val gameDir = requireArguments().parcelable<GameDir>(GAME_DIR)!!
+
+ // Restore checkbox state
+ binding.deepScanSwitch.isChecked =
+ savedInstanceState?.getBoolean(DEEP_SCAN) ?: gameDir.deepScan
+
+ // Ensure that we can get the checkbox state even if the view is destroyed
+ deepScan = binding.deepScanSwitch.isChecked
+ binding.deepScanSwitch.setOnClickListener {
+ deepScan = binding.deepScanSwitch.isChecked
+ }
+
+ return MaterialAlertDialogBuilder(requireContext())
+ .setView(binding.root)
+ .setTitle(R.string.game_folder_properties)
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
+ val folderIndex = gamesViewModel.folders.value.indexOf(gameDir)
+ if (folderIndex != -1) {
+ gamesViewModel.folders.value[folderIndex].deepScan =
+ binding.deepScanSwitch.isChecked
+ gamesViewModel.updateGameDirs()
+ }
+ }
+ .setNegativeButton(android.R.string.cancel, null)
+ .show()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putBoolean(DEEP_SCAN, deepScan)
+ }
+
+ companion object {
+ const val TAG = "GameFolderPropertiesDialogFragment"
+
+ private const val GAME_DIR = "GameDir"
+
+ private const val DEEP_SCAN = "DeepScan"
+
+ fun newInstance(gameDir: GameDir): GameFolderPropertiesDialogFragment {
+ val args = Bundle()
+ args.putParcelable(GAME_DIR, gameDir)
+ val fragment = GameFolderPropertiesDialogFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
new file mode 100644
index 000000000..341a37fdb
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GameFoldersFragment.kt
@@ -0,0 +1,128 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+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.recyclerview.widget.GridLayoutManager
+import com.google.android.material.transition.MaterialSharedAxis
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.adapters.FolderAdapter
+import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding
+import org.yuzu.yuzu_emu.model.GamesViewModel
+import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.ui.main.MainActivity
+
+class GameFoldersFragment : Fragment() {
+ private var _binding: FragmentFoldersBinding? = null
+ private val binding get() = _binding!!
+
+ private val homeViewModel: HomeViewModel by activityViewModels()
+ private val gamesViewModel: GamesViewModel by activityViewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+
+ gamesViewModel.onOpenGameFoldersFragment()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentFoldersBinding.inflate(inflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ homeViewModel.setNavigationVisibility(visible = false, animated = true)
+ homeViewModel.setStatusBarShadeVisibility(visible = false)
+
+ binding.toolbarFolders.setNavigationOnClickListener {
+ binding.root.findNavController().popBackStack()
+ }
+
+ binding.listFolders.apply {
+ layoutManager = GridLayoutManager(
+ requireContext(),
+ resources.getInteger(R.integer.grid_columns)
+ )
+ adapter = FolderAdapter(requireActivity(), gamesViewModel)
+ }
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ gamesViewModel.folders.collect {
+ (binding.listFolders.adapter as FolderAdapter).submitList(it)
+ }
+ }
+ }
+
+ val mainActivity = requireActivity() as MainActivity
+ binding.buttonAdd.setOnClickListener {
+ mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
+ }
+
+ setInsets()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ gamesViewModel.onCloseGameFoldersFragment()
+ }
+
+ 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.toolbarFolders.layoutParams as ViewGroup.MarginLayoutParams
+ mlpToolbar.leftMargin = leftInsets
+ mlpToolbar.rightMargin = rightInsets
+ binding.toolbarFolders.layoutParams = mlpToolbar
+
+ val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
+ val mlpFab =
+ binding.buttonAdd.layoutParams as ViewGroup.MarginLayoutParams
+ mlpFab.leftMargin = leftInsets + fabSpacing
+ mlpFab.rightMargin = rightInsets + fabSpacing
+ mlpFab.bottomMargin = barInsets.bottom + fabSpacing
+ binding.buttonAdd.layoutParams = mlpFab
+
+ val mlpListFolders = binding.listFolders.layoutParams as ViewGroup.MarginLayoutParams
+ mlpListFolders.leftMargin = leftInsets
+ mlpListFolders.rightMargin = rightInsets
+ binding.listFolders.layoutParams = mlpListFolders
+
+ binding.listFolders.updatePadding(
+ bottom = barInsets.bottom +
+ resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
+ )
+
+ windowInsets
+ }
+}
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 4720daec4..3addc2e63 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
@@ -127,18 +127,13 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
- R.string.select_games_folder,
+ R.string.manage_game_folders,
R.string.select_games_folder_description,
R.drawable.ic_add,
{
- mainActivity.getGamesDirectory.launch(
- Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
- )
- },
- { true },
- 0,
- 0,
- homeViewModel.gamesDir
+ binding.root.findNavController()
+ .navigate(R.id.action_homeSettingsFragment_to_gameFoldersFragment)
+ }
)
)
add(
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 d18ec6974..b88d2c038 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
@@ -52,7 +52,6 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
settingsViewModel.clickedItem!!.setting.reset()
settingsViewModel.setAdapterItemChanged(position)
- settingsViewModel.shouldSave = true
}
.setNegativeButton(android.R.string.cancel, null)
.create()
@@ -137,24 +136,17 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
is SingleChoiceSetting -> {
val scSetting = settingsViewModel.clickedItem as SingleChoiceSetting
val value = getValueForSingleChoiceSelection(scSetting, which)
- if (scSetting.selectedValue != value) {
- settingsViewModel.shouldSave = true
- }
scSetting.selectedValue = value
}
is StringSingleChoiceSetting -> {
val scSetting = settingsViewModel.clickedItem as StringSingleChoiceSetting
val value = scSetting.getValueAt(which)
- if (scSetting.selectedValue != value) settingsViewModel.shouldSave = true
scSetting.selectedValue = value
}
is SliderSetting -> {
val sliderSetting = settingsViewModel.clickedItem as SliderSetting
- if (sliderSetting.selectedValue != settingsViewModel.sliderProgress.value) {
- settingsViewModel.shouldSave = true
- }
sliderSetting.selectedValue = settingsViewModel.sliderProgress.value
}
}
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 c66bb635a..c4277735d 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
@@ -42,7 +42,7 @@ import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
-import org.yuzu.yuzu_emu.utils.GameHelper
+import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupFragment : Fragment() {
@@ -184,11 +184,7 @@ class SetupFragment : Fragment() {
R.string.add_games_warning_description,
R.string.add_games_warning_help,
{
- val preferences =
- PreferenceManager.getDefaultSharedPreferences(
- YuzuApplication.appContext
- )
- if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
+ if (NativeConfig.getGameDirs().isNotEmpty()) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt
new file mode 100644
index 000000000..274bc1c7b
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDir.kt
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class GameDir(
+ val uriString: String,
+ var deepScan: Boolean
+) : Parcelable
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 8512ed17c..752d98c10 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
@@ -12,6 +12,7 @@ import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
@@ -20,6 +21,7 @@ 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
class GamesViewModel : ViewModel() {
val games: StateFlow<List<Game>> get() = _games
@@ -40,6 +42,9 @@ class GamesViewModel : ViewModel() {
val searchFocused: StateFlow<Boolean> get() = _searchFocused
private val _searchFocused = MutableStateFlow(false)
+ private val _folders = MutableStateFlow(mutableListOf<GameDir>())
+ val folders = _folders.asStateFlow()
+
init {
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
@@ -50,6 +55,7 @@ class GamesViewModel : ViewModel() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
+ getGameDirs()
if (storedGames!!.isNotEmpty()) {
val deserializedGames = mutableSetOf<Game>()
storedGames.forEach {
@@ -104,7 +110,7 @@ class GamesViewModel : ViewModel() {
_searchFocused.value = searchFocused
}
- fun reloadGames(directoryChanged: Boolean) {
+ fun reloadGames(directoriesChanged: Boolean) {
if (isReloading.value) {
return
}
@@ -116,10 +122,61 @@ class GamesViewModel : ViewModel() {
setGames(GameHelper.getGames())
_isReloading.value = false
- if (directoryChanged) {
+ if (directoriesChanged) {
setShouldSwapData(true)
}
}
}
}
+
+ fun addFolder(gameDir: GameDir) =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ NativeConfig.addGameDir(gameDir)
+ getGameDirs()
+ }
+ }
+
+ fun removeFolder(gameDir: GameDir) =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ val gameDirs = _folders.value.toMutableList()
+ val removedDirIndex = gameDirs.indexOf(gameDir)
+ if (removedDirIndex != -1) {
+ gameDirs.removeAt(removedDirIndex)
+ NativeConfig.setGameDirs(gameDirs.toTypedArray())
+ getGameDirs()
+ }
+ }
+ }
+
+ fun updateGameDirs() =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ NativeConfig.setGameDirs(_folders.value.toTypedArray())
+ getGameDirs()
+ }
+ }
+
+ fun onOpenGameFoldersFragment() =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ getGameDirs()
+ }
+ }
+
+ fun onCloseGameFoldersFragment() =
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ getGameDirs(true)
+ }
+ }
+
+ private fun getGameDirs(reloadList: Boolean = false) {
+ val gameDirs = NativeConfig.getGameDirs()
+ _folders.value = gameDirs.toMutableList()
+ if (reloadList) {
+ reloadGames(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 756f76721..251b5a667 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,15 +3,9 @@
package org.yuzu.yuzu_emu.model
-import android.net.Uri
-import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import androidx.preference.PreferenceManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import org.yuzu.yuzu_emu.YuzuApplication
-import org.yuzu.yuzu_emu.utils.GameHelper
class HomeViewModel : ViewModel() {
val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
@@ -23,14 +17,6 @@ class HomeViewModel : ViewModel() {
val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
private val _shouldPageForward = MutableStateFlow(false)
- val gamesDir: StateFlow<String> get() = _gamesDir
- private val _gamesDir = MutableStateFlow(
- Uri.parse(
- PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
- .getString(GameHelper.KEY_GAME_PATH, "")
- ).path ?: ""
- )
-
var navigatedToSetup = false
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
@@ -50,9 +36,4 @@ class HomeViewModel : ViewModel() {
fun setShouldPageForward(pageForward: Boolean) {
_shouldPageForward.value = pageForward
}
-
- fun setGamesDir(activity: FragmentActivity, dir: String) {
- ViewModelProvider(activity)[GamesViewModel::class.java].reloadGames(true)
- _gamesDir.value = dir
- }
}
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 6f947674e..ccc981e95 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
@@ -13,8 +13,6 @@ import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
class SettingsViewModel : ViewModel() {
var game: Game? = null
- var shouldSave = false
-
var clickedItem: SettingsItem? = null
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
@@ -73,6 +71,5 @@ class SettingsViewModel : ViewModel() {
fun clear() {
game = null
- shouldSave = false
}
}
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 bd2f4cd25..16323a316 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
@@ -40,6 +40,7 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
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
@@ -252,6 +253,13 @@ 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()
@@ -293,20 +301,19 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
- // When a new directory is picked, we currently will reset the existing games
- // database. This effectively means that only one game directory is supported.
- PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
- .putString(GameHelper.KEY_GAME_PATH, result.toString())
- .apply()
-
- Toast.makeText(
- applicationContext,
- R.string.games_dir_selected,
- Toast.LENGTH_LONG
- ).show()
+ val uriString = result.toString()
+ val folder = gamesViewModel.folders.value.firstOrNull { it.uriString == uriString }
+ if (folder != null) {
+ Toast.makeText(
+ applicationContext,
+ R.string.folder_already_added,
+ Toast.LENGTH_SHORT
+ ).show()
+ return
+ }
- gamesViewModel.reloadGames(true)
- homeViewModel.setGamesDir(this, result.path!!)
+ AddGameFolderDialogFragment.newInstance(uriString)
+ .show(supportFragmentManager, AddGameFolderDialogFragment.TAG)
}
val getProdKey =
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 8c3268e9c..bbe7bfa92 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
@@ -364,6 +364,27 @@ object FileUtil {
.lowercase()
}
+ fun isTreeUriValid(uri: Uri): Boolean {
+ val resolver = context.contentResolver
+ val columns = arrayOf(
+ DocumentsContract.Document.COLUMN_DOCUMENT_ID,
+ DocumentsContract.Document.COLUMN_DISPLAY_NAME,
+ DocumentsContract.Document.COLUMN_MIME_TYPE
+ )
+ return try {
+ val docId: String = if (isRootTreeUri(uri)) {
+ DocumentsContract.getTreeDocumentId(uri)
+ } else {
+ DocumentsContract.getDocumentId(uri)
+ }
+ val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, docId)
+ resolver.query(childrenUri, columns, null, null, null)
+ true
+ } catch (_: Exception) {
+ false
+ }
+ }
+
@Throws(IOException::class)
fun getStringFromFile(file: File): String =
String(file.readBytes(), StandardCharsets.UTF_8)
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 e6aca6b44..55010dc59 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
@@ -11,10 +11,11 @@ import kotlinx.serialization.json.Json
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.Game
+import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
object GameHelper {
- const val KEY_GAME_PATH = "game_path"
+ private const val KEY_OLD_GAME_PATH = "game_path"
const val KEY_GAMES = "Games"
private lateinit var preferences: SharedPreferences
@@ -22,15 +23,43 @@ object GameHelper {
fun getGames(): List<Game> {
val games = mutableListOf<Game>()
val context = YuzuApplication.appContext
- val gamesDir =
- PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_GAME_PATH, "")
- val gamesUri = Uri.parse(gamesDir)
preferences = PreferenceManager.getDefaultSharedPreferences(context)
+ val gameDirs = mutableListOf<GameDir>()
+ val oldGamesDir = preferences.getString(KEY_OLD_GAME_PATH, "") ?: ""
+ if (oldGamesDir.isNotEmpty()) {
+ gameDirs.add(GameDir(oldGamesDir, true))
+ preferences.edit().remove(KEY_OLD_GAME_PATH).apply()
+ }
+ gameDirs.addAll(NativeConfig.getGameDirs())
+
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
- addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
+ val badDirs = mutableListOf<Int>()
+ gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
+ val gameDirUri = Uri.parse(gameDir.uriString)
+ val isValid = FileUtil.isTreeUriValid(gameDirUri)
+ if (isValid) {
+ addGamesRecursive(
+ games,
+ FileUtil.listFiles(gameDirUri),
+ if (gameDir.deepScan) 3 else 1
+ )
+ } else {
+ badDirs.add(index)
+ }
+ }
+
+ // Remove all game dirs with insufficient permissions from config
+ if (badDirs.isNotEmpty()) {
+ var offset = 0
+ badDirs.forEach {
+ gameDirs.removeAt(it - offset)
+ offset++
+ }
+ }
+ NativeConfig.setGameDirs(gameDirs.toTypedArray())
// Cache list of games found on disk
val serializedGames = mutableSetOf<String>()
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 87e579fa7..f4e1bb13f 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
@@ -3,6 +3,8 @@
package org.yuzu.yuzu_emu.utils
+import org.yuzu.yuzu_emu.model.GameDir
+
object NativeConfig {
/**
* Creates a Config object and opens the emulation config.
@@ -54,4 +56,22 @@ object NativeConfig {
external fun getConfigHeader(category: Int): String
external fun getPairedSettingKey(key: String): String
+
+ /**
+ * Gets every [GameDir] in AndroidSettings::values.game_dirs
+ */
+ @Synchronized
+ external fun getGameDirs(): Array<GameDir>
+
+ /**
+ * Clears the AndroidSettings::values.game_dirs array and replaces them with the provided array
+ */
+ @Synchronized
+ external fun setGameDirs(dirs: Array<GameDir>)
+
+ /**
+ * Adds a single [GameDir] to the AndroidSettings::values.game_dirs array
+ */
+ @Synchronized
+ external fun addGameDir(dir: GameDir)
}
diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp
index 3041c25c9..767d8ea83 100644
--- a/src/android/app/src/main/jni/android_config.cpp
+++ b/src/android/app/src/main/jni/android_config.cpp
@@ -34,6 +34,7 @@ void AndroidConfig::SaveAllValues() {
void AndroidConfig::ReadAndroidValues() {
if (global) {
ReadAndroidUIValues();
+ ReadUIValues();
}
}
@@ -45,9 +46,35 @@ void AndroidConfig::ReadAndroidUIValues() {
EndGroup();
}
+void AndroidConfig::ReadUIValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
+
+ ReadPathValues();
+
+ EndGroup();
+}
+
+void AndroidConfig::ReadPathValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
+
+ const int gamedirs_size = BeginArray(std::string("gamedirs"));
+ for (int i = 0; i < gamedirs_size; ++i) {
+ SetArrayIndex(i);
+ AndroidSettings::GameDir game_dir;
+ game_dir.path = ReadStringSetting(std::string("path"));
+ game_dir.deep_scan =
+ ReadBooleanSetting(std::string("deep_scan"), std::make_optional(false));
+ AndroidSettings::values.game_dirs.push_back(game_dir);
+ }
+ EndArray();
+
+ EndGroup();
+}
+
void AndroidConfig::SaveAndroidValues() {
if (global) {
SaveAndroidUIValues();
+ SaveUIValues();
}
WriteToIni();
@@ -61,6 +88,29 @@ void AndroidConfig::SaveAndroidUIValues() {
EndGroup();
}
+void AndroidConfig::SaveUIValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Ui));
+
+ SavePathValues();
+
+ EndGroup();
+}
+
+void AndroidConfig::SavePathValues() {
+ BeginGroup(Settings::TranslateCategory(Settings::Category::Paths));
+
+ BeginArray(std::string("gamedirs"));
+ for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
+ SetArrayIndex(i);
+ const auto& game_dir = AndroidSettings::values.game_dirs[i];
+ WriteSetting(std::string("path"), game_dir.path);
+ WriteSetting(std::string("deep_scan"), game_dir.deep_scan, std::make_optional(false));
+ }
+ EndArray();
+
+ 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 e679392fd..f490be016 100644
--- a/src/android/app/src/main/jni/android_config.h
+++ b/src/android/app/src/main/jni/android_config.h
@@ -19,9 +19,9 @@ protected:
void ReadAndroidUIValues();
void ReadHidbusValues() override {}
void ReadDebugControlValues() override {}
- void ReadPathValues() override {}
+ void ReadPathValues() override;
void ReadShortcutValues() override {}
- void ReadUIValues() override {}
+ void ReadUIValues() override;
void ReadUIGamelistValues() override {}
void ReadUILayoutValues() override {}
void ReadMultiplayerValues() override {}
@@ -30,9 +30,9 @@ protected:
void SaveAndroidUIValues();
void SaveHidbusValues() override {}
void SaveDebugControlValues() override {}
- void SavePathValues() override {}
+ void SavePathValues() override;
void SaveShortcutValues() override {}
- void SaveUIValues() override {}
+ void SaveUIValues() override;
void SaveUIGamelistValues() override {}
void SaveUILayoutValues() override {}
void SaveMultiplayerValues() override {}
diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h
index 37bc33918..fc0523206 100644
--- a/src/android/app/src/main/jni/android_settings.h
+++ b/src/android/app/src/main/jni/android_settings.h
@@ -9,9 +9,17 @@
namespace AndroidSettings {
+struct GameDir {
+ std::string path;
+ bool deep_scan = false;
+};
+
struct Values {
Settings::Linkage linkage;
+ // Path settings
+ std::vector<GameDir> game_dirs;
+
// Android
Settings::Setting<bool> picture_in_picture{linkage, false, "picture_in_picture",
Settings::Category::Android};
diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp
index 960abf95a..a56ed5662 100644
--- a/src/android/app/src/main/jni/id_cache.cpp
+++ b/src/android/app/src/main/jni/id_cache.cpp
@@ -13,6 +13,8 @@ static JavaVM* s_java_vm;
static jclass s_native_library_class;
static jclass s_disk_cache_progress_class;
static jclass s_load_callback_stage_class;
+static jclass s_game_dir_class;
+static jmethodID s_game_dir_constructor;
static jmethodID s_exit_emulation_activity;
static jmethodID s_disk_cache_load_progress;
static jmethodID s_on_emulation_started;
@@ -53,6 +55,14 @@ jclass GetDiskCacheLoadCallbackStageClass() {
return s_load_callback_stage_class;
}
+jclass GetGameDirClass() {
+ return s_game_dir_class;
+}
+
+jmethodID GetGameDirConstructor() {
+ return s_game_dir_constructor;
+}
+
jmethodID GetExitEmulationActivity() {
return s_exit_emulation_activity;
}
@@ -90,6 +100,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_load_callback_stage_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass(
"org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage")));
+ const jclass game_dir_class = env->FindClass("org/yuzu/yuzu_emu/model/GameDir");
+ s_game_dir_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_dir_class));
+ s_game_dir_constructor = env->GetMethodID(game_dir_class, "<init>", "(Ljava/lang/String;Z)V");
+ env->DeleteLocalRef(game_dir_class);
+
// Initialize methods
s_exit_emulation_activity =
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
@@ -120,6 +135,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
env->DeleteGlobalRef(s_native_library_class);
env->DeleteGlobalRef(s_disk_cache_progress_class);
env->DeleteGlobalRef(s_load_callback_stage_class);
+ env->DeleteGlobalRef(s_game_dir_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 b76158928..855649efa 100644
--- a/src/android/app/src/main/jni/id_cache.h
+++ b/src/android/app/src/main/jni/id_cache.h
@@ -13,6 +13,8 @@ JNIEnv* GetEnvForThread();
jclass GetNativeLibraryClass();
jclass GetDiskCacheProgressClass();
jclass GetDiskCacheLoadCallbackStageClass();
+jclass GetGameDirClass();
+jmethodID GetGameDirConstructor();
jmethodID GetExitEmulationActivity();
jmethodID GetDiskCacheLoadProgress();
jmethodID GetOnEmulationStarted();
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 617288ae4..3d795b57f 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -123,9 +123,6 @@ int EmulationSession::InstallFileToNand(std::string filename, std::string file_e
ErrorFilenameExtension = 4,
};
- m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
- m_system.GetFileSystemController().CreateFactories(*m_vfs);
-
[[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
if (file_extension == "nsp") {
nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
@@ -694,6 +691,14 @@ jdoubleArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPerfStats(JNIEnv* env, jcl
return j_stats;
}
+jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCpuBackend(JNIEnv* env, jclass clazz) {
+ if (Settings::IsNceEnabled()) {
+ return ToJString(env, "NCE");
+ }
+
+ return ToJString(env, "JIT");
+}
+
void Java_org_yuzu_yuzu_1emu_utils_DirectoryInitialization_setSysDirectory(JNIEnv* env,
jclass clazz,
jstring j_path) {}
diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp
index 8e81816e5..763b2164c 100644
--- a/src/android/app/src/main/jni/native_config.cpp
+++ b/src/android/app/src/main/jni/native_config.cpp
@@ -11,6 +11,7 @@
#include "common/settings.h"
#include "frontend_common/config.h"
#include "jni/android_common/android_common.h"
+#include "jni/id_cache.h"
std::unique_ptr<AndroidConfig> config;
@@ -253,4 +254,55 @@ jstring Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getPairedSettingKey(JNIEnv* e
return ToJString(env, setting->PairedSetting()->GetLabel());
}
+jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getGameDirs(JNIEnv* env, jobject obj) {
+ jclass gameDirClass = IDCache::GetGameDirClass();
+ jmethodID gameDirConstructor = IDCache::GetGameDirConstructor();
+ jobjectArray jgameDirArray =
+ env->NewObjectArray(AndroidSettings::values.game_dirs.size(), gameDirClass, nullptr);
+ for (size_t i = 0; i < AndroidSettings::values.game_dirs.size(); ++i) {
+ jobject jgameDir =
+ env->NewObject(gameDirClass, gameDirConstructor,
+ ToJString(env, AndroidSettings::values.game_dirs[i].path),
+ static_cast<jboolean>(AndroidSettings::values.game_dirs[i].deep_scan));
+ env->SetObjectArrayElement(jgameDirArray, i, jgameDir);
+ }
+ return jgameDirArray;
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setGameDirs(JNIEnv* env, jobject obj,
+ jobjectArray gameDirs) {
+ AndroidSettings::values.game_dirs.clear();
+ int size = env->GetArrayLength(gameDirs);
+
+ if (size == 0) {
+ return;
+ }
+
+ jobject dir = env->GetObjectArrayElement(gameDirs, 0);
+ jclass gameDirClass = IDCache::GetGameDirClass();
+ jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;");
+ jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z");
+ for (int i = 0; i < size; ++i) {
+ dir = env->GetObjectArrayElement(gameDirs, i);
+ jstring juriString = static_cast<jstring>(env->GetObjectField(dir, uriStringField));
+ jboolean jdeepScanBoolean = env->GetBooleanField(dir, deepScanBooleanField);
+ std::string uriString = GetJString(env, juriString);
+ AndroidSettings::values.game_dirs.push_back(
+ AndroidSettings::GameDir{uriString, static_cast<bool>(jdeepScanBoolean)});
+ }
+}
+
+void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject obj,
+ jobject gameDir) {
+ jclass gameDirClass = IDCache::GetGameDirClass();
+ jfieldID uriStringField = env->GetFieldID(gameDirClass, "uriString", "Ljava/lang/String;");
+ jfieldID deepScanBooleanField = env->GetFieldID(gameDirClass, "deepScan", "Z");
+
+ jstring juriString = static_cast<jstring>(env->GetObjectField(gameDir, uriStringField));
+ jboolean jdeepScanBoolean = env->GetBooleanField(gameDir, deepScanBooleanField);
+ std::string uriString = GetJString(env, juriString);
+ AndroidSettings::values.game_dirs.push_back(
+ AndroidSettings::GameDir{uriString, static_cast<bool>(jdeepScanBoolean)});
+}
+
} // extern "C"
diff --git a/src/android/app/src/main/res/layout/card_folder.xml b/src/android/app/src/main/res/layout/card_folder.xml
new file mode 100644
index 000000000..4e0c04b6b
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_folder.xml
@@ -0,0 +1,70 @@
+<?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"
+ android:focusable="true">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="16dp"
+ android:layout_gravity="center_vertical"
+ android:animateLayoutChanges="true">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/path"
+ style="@style/TextAppearance.Material3.BodyLarge"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@+id/button_layout"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="@string/select_gpu_driver_default" />
+
+ <LinearLayout
+ android:id="@+id/button_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ <Button
+ android:id="@+id/button_edit"
+ style="@style/Widget.Material3.Button.IconButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/delete"
+ android:tooltipText="@string/edit"
+ app:icon="@drawable/ic_edit"
+ app:iconTint="?attr/colorControlNormal" />
+
+ <Button
+ android:id="@+id/button_delete"
+ style="@style/Widget.Material3.Button.IconButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/delete"
+ android:tooltipText="@string/delete"
+ app:icon="@drawable/ic_delete"
+ app:iconTint="?attr/colorControlNormal" />
+
+ </LinearLayout>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/dialog_add_folder.xml b/src/android/app/src/main/res/layout/dialog_add_folder.xml
new file mode 100644
index 000000000..01f95e868
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_add_folder.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ android:orientation="vertical">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/path"
+ style="@style/TextAppearance.Material3.BodyLarge"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_gravity="center_vertical|start"
+ android:layout_weight="1"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="folder/folder/folder/folder" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="8dp">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:layout_weight="1"
+ android:text="@string/deep_scan"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.checkbox.MaterialCheckBox
+ android:id="@+id/deep_scan_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/dialog_folder_properties.xml b/src/android/app/src/main/res/layout/dialog_folder_properties.xml
new file mode 100644
index 000000000..248d048cb
--- /dev/null
+++ b/src/android/app/src/main/res/layout/dialog_folder_properties.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/deep_scan_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <com.google.android.material.textview.MaterialTextView
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start"
+ android:layout_weight="1"
+ android:text="@string/deep_scan"
+ android:textAlignment="viewStart" />
+
+ <com.google.android.material.checkbox.MaterialCheckBox
+ android:id="@+id/deep_scan_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_folders.xml b/src/android/app/src/main/res/layout/fragment_folders.xml
new file mode 100644
index 000000000..74f2f3754
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_folders.xml
@@ -0,0 +1,48 @@
+<?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_folders"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/colorSurface">
+
+ <androidx.coordinatorlayout.widget.CoordinatorLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar_folders"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ app:liftOnScrollTargetViewId="@id/list_folders">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_folders"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:navigationIcon="@drawable/ic_back"
+ app:title="@string/game_folders" />
+
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/list_folders"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+ </androidx.coordinatorlayout.widget.CoordinatorLayout>
+
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
+ android:id="@+id/button_add"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:contentDescription="@string/add_games"
+ app:srcCompat="@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/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 6d4c1f86d..cf70b4bc4 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -28,6 +28,9 @@
<action
android:id="@+id/action_homeSettingsFragment_to_appletLauncherFragment"
app:destination="@id/appletLauncherFragment" />
+ <action
+ android:id="@+id/action_homeSettingsFragment_to_gameFoldersFragment"
+ app:destination="@id/gameFoldersFragment" />
</fragment>
<fragment
@@ -117,5 +120,9 @@
android:id="@+id/cabinetLauncherDialogFragment"
android:name="org.yuzu.yuzu_emu.fragments.CabinetLauncherDialogFragment"
android:label="CabinetLauncherDialogFragment" />
+ <fragment
+ android:id="@+id/gameFoldersFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.GameFoldersFragment"
+ android:label="GameFoldersFragment" />
</navigation>
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 51bcc49a3..ab435dce9 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -175,6 +175,24 @@
<item>2</item>
</integer-array>
+ <string-array name="cpuBackendArm64Names">
+ <item>@string/cpu_backend_dynarmic</item>
+ <item>@string/cpu_backend_nce</item>
+ </string-array>
+
+ <integer-array name="cpuBackendArm64Values">
+ <item>0</item>
+ <item>1</item>
+ </integer-array>
+
+ <string-array name="cpuBackendX86Names">
+ <item>@string/cpu_backend_dynarmic</item>
+ </string-array>
+
+ <integer-array name="cpuBackendX86Values">
+ <item>0</item>
+ </integer-array>
+
<string-array name="cpuAccuracyNames">
<item>@string/auto</item>
<item>@string/cpu_accuracy_accurate</item>
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index ef855ea6f..380d14213 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">72dp</dimen>
+ <dimen name="spacing_bottom_list_fab">76dp</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 471af8795..a6ccef8a1 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -38,6 +38,7 @@
<string name="empty_gamelist">No files were found or no game directory has been selected yet.</string>
<string name="search_and_filter_games">Search and filter games</string>
<string name="select_games_folder">Select games folder</string>
+ <string name="manage_game_folders">Manage game folders</string>
<string name="select_games_folder_description">Allows yuzu to populate the games list</string>
<string name="add_games_warning">Skip selecting games folder?</string>
<string name="add_games_warning_description">Games won\'t be displayed in the Games list if a folder isn\'t selected.</string>
@@ -124,6 +125,11 @@
<string name="manage_yuzu_data_description">Import/export firmware, keys, user data, and more!</string>
<string name="share_save_file">Share save file</string>
<string name="export_save_failed">Failed to export save</string>
+ <string name="game_folders">Game folders</string>
+ <string name="deep_scan">Deep scan</string>
+ <string name="add_game_folder">Add game folder</string>
+ <string name="folder_already_added">This folder was already added!</string>
+ <string name="game_folder_properties">Game folder properties</string>
<!-- Applet launcher strings -->
<string name="applets">Applet launcher</string>
@@ -185,6 +191,7 @@
<string name="frame_limit_enable_description">Limits emulation speed to a specified percentage of normal speed.</string>
<string name="frame_limit_slider">Limit speed percent</string>
<string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. 100% is the normal speed. Values higher or lower will increase or decrease the speed limit.</string>
+ <string name="cpu_backend">CPU backend</string>
<string name="cpu_accuracy">CPU accuracy</string>
<string name="value_with_units">%1$s%2$s</string>
@@ -257,6 +264,7 @@
<string name="cancelling">Cancelling</string>
<string name="install">Install</string>
<string name="delete">Delete</string>
+ <string name="edit">Edit</string>
<string name="export_success">Exported successfully</string>
<!-- GPU driver installation -->
@@ -416,6 +424,10 @@
<string name="ratio_force_sixteen_ten">Force 16:10</string>
<string name="ratio_stretch">Stretch to window</string>
+ <!-- CPU Backend -->
+ <string name="cpu_backend_dynarmic">Dynarmic (Slow)</string>
+ <string name="cpu_backend_nce">Native code execution (NCE)</string>
+
<!-- CPU Accuracy -->
<string name="cpu_accuracy_accurate">Accurate</string>
<string name="cpu_accuracy_unsafe">Unsafe</string>
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index e216eb3de..b58a7073f 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -52,6 +52,7 @@ add_library(common STATIC
fiber.cpp
fiber.h
fixed_point.h
+ free_region_manager.h
fs/file.cpp
fs/file.h
fs/fs.cpp
@@ -166,6 +167,13 @@ if (WIN32)
target_link_libraries(common PRIVATE ntdll)
endif()
+if (NOT WIN32)
+ target_sources(common PRIVATE
+ signal_chain.cpp
+ signal_chain.h
+ )
+endif()
+
if(ANDROID)
target_sources(common
PRIVATE
@@ -174,6 +182,15 @@ if(ANDROID)
)
endif()
+if (UNIX AND NOT APPLE)
+ target_sources(common PRIVATE
+ linux/gamemode.cpp
+ linux/gamemode.h
+ )
+
+ target_link_libraries(common PRIVATE gamemode::headers)
+endif()
+
if(ARCHITECTURE_x86_64)
target_sources(common
PRIVATE
@@ -191,7 +208,7 @@ if(ARCHITECTURE_x86_64)
target_link_libraries(common PRIVATE xbyak::xbyak)
endif()
-if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
+if (HAS_NCE)
target_sources(common
PRIVATE
arm64/native_clock.cpp
diff --git a/src/common/free_region_manager.h b/src/common/free_region_manager.h
new file mode 100644
index 000000000..2e590d609
--- /dev/null
+++ b/src/common/free_region_manager.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+#include <boost/icl/interval_set.hpp>
+
+namespace Common {
+
+class FreeRegionManager {
+public:
+ explicit FreeRegionManager() = default;
+ ~FreeRegionManager() = default;
+
+ void SetAddressSpace(void* start, size_t size) {
+ this->FreeBlock(start, size);
+ }
+
+ std::pair<void*, size_t> FreeBlock(void* block_ptr, size_t size) {
+ std::scoped_lock lk(m_mutex);
+
+ // Check to see if we are adjacent to any regions.
+ auto start_address = reinterpret_cast<uintptr_t>(block_ptr);
+ auto end_address = start_address + size;
+ auto it = m_free_regions.find({start_address - 1, end_address + 1});
+
+ // If we are, join with them, ensuring we stay in bounds.
+ if (it != m_free_regions.end()) {
+ start_address = std::min(start_address, it->lower());
+ end_address = std::max(end_address, it->upper());
+ }
+
+ // Free the relevant region.
+ m_free_regions.insert({start_address, end_address});
+
+ // Return the adjusted pointers.
+ block_ptr = reinterpret_cast<void*>(start_address);
+ size = end_address - start_address;
+ return {block_ptr, size};
+ }
+
+ void AllocateBlock(void* block_ptr, size_t size) {
+ std::scoped_lock lk(m_mutex);
+
+ auto address = reinterpret_cast<uintptr_t>(block_ptr);
+ m_free_regions.subtract({address, address + size});
+ }
+
+private:
+ std::mutex m_mutex;
+ boost::icl::interval_set<uintptr_t> m_free_regions;
+};
+
+} // namespace Common
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index ba22595e0..4bfc64f2d 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -21,15 +21,22 @@
#include <boost/icl/interval_set.hpp>
#include <fcntl.h>
#include <sys/mman.h>
+#include <sys/random.h>
#include <unistd.h>
#include "common/scope_exit.h"
+#ifndef MAP_NORESERVE
+#define MAP_NORESERVE 0
+#endif
+
#endif // ^^^ Linux ^^^
#include <mutex>
+#include <random>
#include "common/alignment.h"
#include "common/assert.h"
+#include "common/free_region_manager.h"
#include "common/host_memory.h"
#include "common/logging/log.h"
@@ -141,7 +148,7 @@ public:
Release();
}
- void Map(size_t virtual_offset, size_t host_offset, size_t length) {
+ void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
std::unique_lock lock{placeholder_mutex};
if (!IsNiechePlaceholder(virtual_offset, length)) {
Split(virtual_offset, length);
@@ -160,7 +167,7 @@ public:
}
}
- void Protect(size_t virtual_offset, size_t length, bool read, bool write) {
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {
DWORD new_flags{};
if (read && write) {
new_flags = PAGE_READWRITE;
@@ -186,6 +193,11 @@ public:
}
}
+ void EnableDirectMappedAddress() {
+ // TODO
+ UNREACHABLE();
+ }
+
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
@@ -353,6 +365,65 @@ private:
#elif defined(__linux__) || defined(__FreeBSD__) // ^^^ Windows ^^^ vvv Linux vvv
+#ifdef ARCHITECTURE_arm64
+
+static void* ChooseVirtualBase(size_t virtual_size) {
+ constexpr uintptr_t Map39BitSize = (1ULL << 39);
+ constexpr uintptr_t Map36BitSize = (1ULL << 36);
+
+ // This is not a cryptographic application, we just want something random.
+ std::mt19937_64 rng;
+
+ // We want to ensure we are allocating at an address aligned to the L2 block size.
+ // For Qualcomm devices, we must also allocate memory above 36 bits.
+ const size_t lower = Map36BitSize / HugePageSize;
+ const size_t upper = (Map39BitSize - virtual_size) / HugePageSize;
+ const size_t range = upper - lower;
+
+ // Try up to 64 times to allocate memory at random addresses in the range.
+ for (int i = 0; i < 64; i++) {
+ // Calculate a possible location.
+ uintptr_t hint_address = ((rng() % range) + lower) * HugePageSize;
+
+ // Try to map.
+ // Note: we may be able to take advantage of MAP_FIXED_NOREPLACE here.
+ void* map_pointer =
+ mmap(reinterpret_cast<void*>(hint_address), virtual_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+
+ // If we successfully mapped, we're done.
+ if (reinterpret_cast<uintptr_t>(map_pointer) == hint_address) {
+ return map_pointer;
+ }
+
+ // Unmap if necessary, and try again.
+ if (map_pointer != MAP_FAILED) {
+ munmap(map_pointer, virtual_size);
+ }
+ }
+
+ return MAP_FAILED;
+}
+
+#else
+
+static void* ChooseVirtualBase(size_t virtual_size) {
+#if defined(__FreeBSD__)
+ void* virtual_base =
+ mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0);
+
+ if (virtual_base != MAP_FAILED) {
+ return virtual_base;
+ }
+#endif
+
+ return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+}
+
+#endif
+
class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_)
@@ -402,29 +473,16 @@ public:
}
// Virtual memory initialization
-#if defined(__FreeBSD__)
- virtual_base =
- static_cast<u8*>(mmap(nullptr, virtual_size, PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED_SUPER, -1, 0));
- if (virtual_base == MAP_FAILED) {
- virtual_base = static_cast<u8*>(
- mmap(nullptr, virtual_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
- if (virtual_base == MAP_FAILED) {
- LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
- throw std::bad_alloc{};
- }
- }
-#else
- virtual_base = static_cast<u8*>(mmap(nullptr, virtual_size, PROT_NONE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
+ virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
if (virtual_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
throw std::bad_alloc{};
}
+#if defined(__linux__)
madvise(virtual_base, virtual_size, MADV_HUGEPAGE);
#endif
- placeholders.add({0, virtual_size});
+ free_manager.SetAddressSpace(virtual_base, virtual_size);
good = true;
}
@@ -432,14 +490,29 @@ public:
Release();
}
- void Map(size_t virtual_offset, size_t host_offset, size_t length) {
- {
- std::scoped_lock lock{placeholder_mutex};
- placeholders.subtract({virtual_offset, virtual_offset + length});
+ void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms) {
+ // Intersect the range with our address space.
+ AdjustMap(&virtual_offset, &length);
+
+ // We are removing a placeholder.
+ free_manager.AllocateBlock(virtual_base + virtual_offset, length);
+
+ // Deduce mapping protection flags.
+ int flags = PROT_NONE;
+ if (True(perms & MemoryPermission::Read)) {
+ flags |= PROT_READ;
+ }
+ if (True(perms & MemoryPermission::Write)) {
+ flags |= PROT_WRITE;
+ }
+#ifdef ARCHITECTURE_arm64
+ if (True(perms & MemoryPermission::Execute)) {
+ flags |= PROT_EXEC;
}
+#endif
- void* ret = mmap(virtual_base + virtual_offset, length, PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_FIXED, fd, host_offset);
+ void* ret = mmap(virtual_base + virtual_offset, length, flags, MAP_SHARED | MAP_FIXED, fd,
+ host_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
}
@@ -447,47 +520,54 @@ public:
// The method name is wrong. We're still talking about the virtual range.
// We don't want to unmap, we want to reserve this memory.
- {
- std::scoped_lock lock{placeholder_mutex};
- auto it = placeholders.find({virtual_offset - 1, virtual_offset + length + 1});
-
- if (it != placeholders.end()) {
- size_t prev_upper = virtual_offset + length;
- virtual_offset = std::min(virtual_offset, it->lower());
- length = std::max(it->upper(), prev_upper) - virtual_offset;
- }
+ // Intersect the range with our address space.
+ AdjustMap(&virtual_offset, &length);
- placeholders.add({virtual_offset, virtual_offset + length});
- }
+ // Merge with any adjacent placeholder mappings.
+ auto [merged_pointer, merged_size] =
+ free_manager.FreeBlock(virtual_base + virtual_offset, length);
- void* ret = mmap(virtual_base + virtual_offset, length, PROT_NONE,
+ void* ret = mmap(merged_pointer, merged_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
}
- void Protect(size_t virtual_offset, size_t length, bool read, bool write) {
- int flags = 0;
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {
+ // Intersect the range with our address space.
+ AdjustMap(&virtual_offset, &length);
+
+ int flags = PROT_NONE;
if (read) {
flags |= PROT_READ;
}
if (write) {
flags |= PROT_WRITE;
}
+#ifdef HAS_NCE
+ if (execute) {
+ flags |= PROT_EXEC;
+ }
+#endif
int ret = mprotect(virtual_base + virtual_offset, length, flags);
ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
}
+ void EnableDirectMappedAddress() {
+ virtual_base = nullptr;
+ }
+
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
+ u8* virtual_map_base{reinterpret_cast<u8*>(MAP_FAILED)};
private:
/// Release all resources in the object
void Release() {
- if (virtual_base != MAP_FAILED) {
- int ret = munmap(virtual_base, virtual_size);
+ if (virtual_map_base != MAP_FAILED) {
+ int ret = munmap(virtual_map_base, virtual_size);
ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno));
}
@@ -502,10 +582,29 @@ private:
}
}
- int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
+ void AdjustMap(size_t* virtual_offset, size_t* length) {
+ if (virtual_base != nullptr) {
+ return;
+ }
+
+ // If we are direct mapped, we want to make sure we are operating on a region
+ // that is in range of our virtual mapping.
+ size_t intended_start = *virtual_offset;
+ size_t intended_end = intended_start + *length;
+ size_t address_space_start = reinterpret_cast<size_t>(virtual_map_base);
+ size_t address_space_end = address_space_start + virtual_size;
- boost::icl::interval_set<size_t> placeholders; ///< Mapped placeholders
- std::mutex placeholder_mutex; ///< Mutex for placeholders
+ if (address_space_start > intended_end || intended_start > address_space_end) {
+ *virtual_offset = 0;
+ *length = 0;
+ } else {
+ *virtual_offset = std::max(intended_start, address_space_start);
+ *length = std::min(intended_end, address_space_end) - *virtual_offset;
+ }
+ }
+
+ int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
+ FreeRegionManager free_manager{};
};
#else // ^^^ Linux ^^^ vvv Generic vvv
@@ -518,11 +617,13 @@ public:
throw std::bad_alloc{};
}
- void Map(size_t virtual_offset, size_t host_offset, size_t length) {}
+ void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perm) {}
void Unmap(size_t virtual_offset, size_t length) {}
- void Protect(size_t virtual_offset, size_t length, bool read, bool write) {}
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute) {}
+
+ void EnableDirectMappedAddress() {}
u8* backing_base{nullptr};
u8* virtual_base{nullptr};
@@ -535,15 +636,16 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
try {
// Try to allocate a fastmem arena.
// The implementation will fail with std::bad_alloc on errors.
- impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment),
- AlignUp(virtual_size, PageAlignment) +
- 3 * HugePageSize);
+ impl =
+ std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment),
+ AlignUp(virtual_size, PageAlignment) + HugePageSize);
backing_base = impl->backing_base;
virtual_base = impl->virtual_base;
if (virtual_base) {
- virtual_base += 2 * HugePageSize - 1;
- virtual_base -= reinterpret_cast<size_t>(virtual_base) & (HugePageSize - 1);
+ // Ensure the virtual base is aligned to the L2 block size.
+ virtual_base = reinterpret_cast<u8*>(
+ Common::AlignUp(reinterpret_cast<uintptr_t>(virtual_base), HugePageSize));
virtual_base_offset = virtual_base - impl->virtual_base;
}
@@ -562,7 +664,8 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default;
HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
-void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) {
+void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length,
+ MemoryPermission perms) {
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(host_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
@@ -571,7 +674,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) {
if (length == 0 || !virtual_base || !impl) {
return;
}
- impl->Map(virtual_offset + virtual_base_offset, host_offset, length);
+ impl->Map(virtual_offset + virtual_base_offset, host_offset, length, perms);
}
void HostMemory::Unmap(size_t virtual_offset, size_t length) {
@@ -584,14 +687,22 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length) {
impl->Unmap(virtual_offset + virtual_base_offset, length);
}
-void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write) {
+void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write,
+ bool execute) {
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
ASSERT(virtual_offset + length <= virtual_size);
if (length == 0 || !virtual_base || !impl) {
return;
}
- impl->Protect(virtual_offset + virtual_base_offset, length, read, write);
+ impl->Protect(virtual_offset + virtual_base_offset, length, read, write, execute);
+}
+
+void HostMemory::EnableDirectMappedAddress() {
+ if (impl) {
+ impl->EnableDirectMappedAddress();
+ virtual_size += reinterpret_cast<uintptr_t>(virtual_base);
+ }
}
} // namespace Common
diff --git a/src/common/host_memory.h b/src/common/host_memory.h
index 447975ded..cebfacab2 100644
--- a/src/common/host_memory.h
+++ b/src/common/host_memory.h
@@ -4,11 +4,20 @@
#pragma once
#include <memory>
+#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/virtual_buffer.h"
namespace Common {
+enum class MemoryPermission : u32 {
+ Read = 1 << 0,
+ Write = 1 << 1,
+ ReadWrite = Read | Write,
+ Execute = 1 << 2,
+};
+DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
+
/**
* A low level linear memory buffer, which supports multiple mappings
* Its purpose is to rebuild a given sparse memory layout, including mirrors.
@@ -31,11 +40,13 @@ public:
HostMemory(HostMemory&& other) noexcept;
HostMemory& operator=(HostMemory&& other) noexcept;
- void Map(size_t virtual_offset, size_t host_offset, size_t length);
+ void Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms);
void Unmap(size_t virtual_offset, size_t length);
- void Protect(size_t virtual_offset, size_t length, bool read, bool write);
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write, bool execute = false);
+
+ void EnableDirectMappedAddress();
[[nodiscard]] u8* BackingBasePointer() noexcept {
return backing_base;
diff --git a/src/common/linux/gamemode.cpp b/src/common/linux/gamemode.cpp
new file mode 100644
index 000000000..8d3e2934a
--- /dev/null
+++ b/src/common/linux/gamemode.cpp
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <gamemode_client.h>
+
+#include "common/linux/gamemode.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+
+namespace Common::Linux {
+
+void StartGamemode() {
+ if (Settings::values.enable_gamemode) {
+ if (gamemode_request_start() < 0) {
+ LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string());
+ } else {
+ LOG_INFO(Frontend, "Started gamemode");
+ }
+ }
+}
+
+void StopGamemode() {
+ if (Settings::values.enable_gamemode) {
+ if (gamemode_request_end() < 0) {
+ LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string());
+ } else {
+ LOG_INFO(Frontend, "Stopped gamemode");
+ }
+ }
+}
+
+void SetGamemodeState(bool state) {
+ if (state) {
+ StartGamemode();
+ } else {
+ StopGamemode();
+ }
+}
+
+} // namespace Common::Linux
diff --git a/src/common/linux/gamemode.h b/src/common/linux/gamemode.h
new file mode 100644
index 000000000..b80646ae2
--- /dev/null
+++ b/src/common/linux/gamemode.h
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+namespace Common::Linux {
+
+/**
+ * Start the (Feral Interactive) Linux gamemode if it is installed and it is activated
+ */
+void StartGamemode();
+
+/**
+ * Stop the (Feral Interactive) Linux gamemode if it is installed and it is activated
+ */
+void StopGamemode();
+
+/**
+ * Start or stop the (Feral Interactive) Linux gamemode if it is installed and it is activated
+ * @param state The new state the gamemode should have
+ */
+void SetGamemodeState(bool state);
+
+} // namespace Common::Linux
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index a10131eb2..4666bd0a0 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -41,6 +41,7 @@ SWITCHABLE(AspectRatio, true);
SWITCHABLE(AstcDecodeMode, true);
SWITCHABLE(AstcRecompression, true);
SWITCHABLE(AudioMode, true);
+SWITCHABLE(CpuBackend, true);
SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
@@ -155,6 +156,22 @@ bool IsFastmemEnabled() {
return true;
}
+static bool is_nce_enabled = false;
+
+void SetNceEnabled(bool is_39bit) {
+ const bool is_nce_selected = values.cpu_backend.GetValue() == CpuBackend::Nce;
+ is_nce_enabled = IsFastmemEnabled() && is_nce_selected && is_39bit;
+ if (is_nce_selected && !is_nce_enabled) {
+ LOG_WARNING(
+ Common,
+ "Program does not utilize 39-bit address space, unable to natively execute code");
+ }
+}
+
+bool IsNceEnabled() {
+ return is_nce_enabled;
+}
+
bool IsDockedMode() {
return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
}
@@ -219,6 +236,8 @@ const char* TranslateCategory(Category category) {
return "Services";
case Category::Paths:
return "Paths";
+ case Category::Linux:
+ return "Linux";
case Category::MaxEnum:
break;
}
diff --git a/src/common/settings.h b/src/common/settings.h
index b929fd957..98341ad96 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -63,6 +63,7 @@ SWITCHABLE(AspectRatio, true);
SWITCHABLE(AstcDecodeMode, true);
SWITCHABLE(AstcRecompression, true);
SWITCHABLE(AudioMode, true);
+SWITCHABLE(CpuBackend, true);
SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
@@ -179,6 +180,14 @@ struct Values {
&use_speed_limit};
// Cpu
+ SwitchableSetting<CpuBackend, true> cpu_backend{
+ linkage, CpuBackend::Dynarmic, CpuBackend::Dynarmic,
+#ifdef HAS_NCE
+ CpuBackend::Nce,
+#else
+ CpuBackend::Dynarmic,
+#endif
+ "cpu_backend", Category::Cpu};
SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage, CpuAccuracy::Auto,
CpuAccuracy::Auto, CpuAccuracy::Paranoid,
"cpu_accuracy", Category::Cpu};
@@ -429,6 +438,9 @@ struct Values {
true,
true};
+ // Linux
+ SwitchableSetting<bool> enable_gamemode{linkage, true, "enable_gamemode", Category::Linux};
+
// Controls
InputSetting<std::array<PlayerInput, 10>> players;
@@ -566,6 +578,8 @@ bool IsGPULevelExtreme();
bool IsGPULevelHigh();
bool IsFastmemEnabled();
+void SetNceEnabled(bool is_64bit);
+bool IsNceEnabled();
bool IsDockedMode();
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 7943223eb..344c04439 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -41,6 +41,7 @@ enum class Category : u32 {
Multiplayer,
Services,
Paths,
+ Linux,
MaxEnum,
};
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index 11429d7a8..d6351e57e 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -129,6 +129,8 @@ ENUM(ShaderBackend, Glsl, Glasm, SpirV);
ENUM(GpuAccuracy, Normal, High, Extreme);
+ENUM(CpuBackend, Dynarmic, Nce);
+
ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
diff --git a/src/common/signal_chain.cpp b/src/common/signal_chain.cpp
new file mode 100644
index 000000000..2e4fecc48
--- /dev/null
+++ b/src/common/signal_chain.cpp
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <dlfcn.h>
+
+#include "common/assert.h"
+#include "common/dynamic_library.h"
+#include "common/scope_exit.h"
+#include "common/signal_chain.h"
+
+namespace Common {
+
+template <typename T>
+T* LookupLibcSymbol(const char* name) {
+#if defined(__BIONIC__)
+ Common::DynamicLibrary provider("libc.so");
+ if (!provider.IsOpen()) {
+ UNREACHABLE_MSG("Failed to open libc!");
+ }
+#else
+ // For other operating environments, we assume the symbol is not overridden.
+ const char* base = nullptr;
+ Common::DynamicLibrary provider(base);
+#endif
+
+ void* sym = provider.GetSymbolAddress(name);
+ if (sym == nullptr) {
+ sym = dlsym(RTLD_DEFAULT, name);
+ }
+ if (sym == nullptr) {
+ UNREACHABLE_MSG("Unable to find symbol {}!", name);
+ }
+
+ return reinterpret_cast<T*>(sym);
+}
+
+int SigAction(int signum, const struct sigaction* act, struct sigaction* oldact) {
+ static auto libc_sigaction = LookupLibcSymbol<decltype(sigaction)>("sigaction");
+ return libc_sigaction(signum, act, oldact);
+}
+
+} // namespace Common
diff --git a/src/common/signal_chain.h b/src/common/signal_chain.h
new file mode 100644
index 000000000..8d06a1bd1
--- /dev/null
+++ b/src/common/signal_chain.h
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#ifndef _WIN32
+
+#include <signal.h>
+
+namespace Common {
+
+// Android's ART overrides sigaction with its own wrapper. This is problematic for SIGSEGV
+// in particular, because ART's handler accesses tpidr_el0, which conflicts with NCE.
+// This extracts the libc symbol and calls it directly.
+int SigAction(int signum, const struct sigaction* act, struct sigaction* oldact);
+
+} // namespace Common
+
+#endif
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
index caca9a123..012fdc1e0 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -10,7 +10,7 @@
#include "common/x64/rdtsc.h"
#endif
-#if defined(ARCHITECTURE_arm64) && defined(__linux__)
+#ifdef HAS_NCE
#include "common/arm64/native_clock.h"
#endif
@@ -68,7 +68,7 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
// - Is not more precise than 1 GHz (1ns resolution)
return std::make_unique<StandardWallClock>();
}
-#elif defined(ARCHITECTURE_arm64) && defined(__linux__)
+#elif defined(HAS_NCE)
return std::make_unique<Arm64::NativeClock>();
#else
return std::make_unique<StandardWallClock>();
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 66c10fc3f..85583941c 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -926,6 +926,22 @@ if (ENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
endif()
+if (HAS_NCE)
+ enable_language(C ASM)
+ set(CMAKE_ASM_FLAGS "${CFLAGS} -x assembler-with-cpp")
+
+ target_sources(core PRIVATE
+ arm/nce/arm_nce.cpp
+ arm/nce/arm_nce.h
+ arm/nce/arm_nce.s
+ arm/nce/guest_context.h
+ arm/nce/patcher.cpp
+ arm/nce/patcher.h
+ arm/nce/instructions.h
+ )
+ target_link_libraries(core PRIVATE merry::oaknut)
+endif()
+
if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_sources(core PRIVATE
arm/dynarmic/arm_dynarmic.h
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index 558fba5bd..d231bf89c 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -201,6 +201,8 @@ void ARM_Interface::Run() {
if (True(hr & HaltReason::DataAbort)) {
if (system.DebuggerEnabled()) {
system.GetDebugger().NotifyThreadWatchpoint(current_thread, *HaltedWatchpoint());
+ } else {
+ LogBacktrace();
}
current_thread->RequestSuspend(SuspendType::Debug);
break;
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 3d866ff6f..a9d9ac09d 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -81,6 +81,9 @@ public:
// thread context to be 800 bytes in size.
static_assert(sizeof(ThreadContext64) == 0x320);
+ /// Perform any backend-specific initialization.
+ virtual void Initialize() {}
+
/// Runs the CPU until an event happens
void Run();
diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp
new file mode 100644
index 000000000..f7bdafd39
--- /dev/null
+++ b/src/core/arm/nce/arm_nce.cpp
@@ -0,0 +1,400 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <cinttypes>
+#include <memory>
+
+#include "common/signal_chain.h"
+#include "core/arm/nce/arm_nce.h"
+#include "core/arm/nce/patcher.h"
+#include "core/core.h"
+#include "core/memory.h"
+
+#include "core/hle/kernel/k_process.h"
+
+#include <signal.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+namespace Core {
+
+namespace {
+
+struct sigaction g_orig_action;
+
+// Verify assembly offsets.
+using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
+static_assert(offsetof(NativeExecutionParameters, native_context) == TpidrEl0NativeContext);
+static_assert(offsetof(NativeExecutionParameters, lock) == TpidrEl0Lock);
+static_assert(offsetof(NativeExecutionParameters, magic) == TpidrEl0TlsMagic);
+
+fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) {
+ _aarch64_ctx* header = reinterpret_cast<_aarch64_ctx*>(&host_ctx.__reserved);
+ while (header->magic != FPSIMD_MAGIC) {
+ header = reinterpret_cast<_aarch64_ctx*>(reinterpret_cast<char*>(header) + header->size);
+ }
+ return reinterpret_cast<fpsimd_context*>(header);
+}
+
+} // namespace
+
+void* ARM_NCE::RestoreGuestContext(void* raw_context) {
+ // Retrieve the host context.
+ auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
+
+ // Thread-local parameters will be located in x9.
+ auto* tpidr = reinterpret_cast<NativeExecutionParameters*>(host_ctx.regs[9]);
+ auto* guest_ctx = static_cast<GuestContext*>(tpidr->native_context);
+
+ // Retrieve the host floating point state.
+ auto* fpctx = GetFloatingPointState(host_ctx);
+
+ // Save host callee-saved registers.
+ std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &fpctx->vregs[8],
+ sizeof(guest_ctx->host_ctx.host_saved_vregs));
+ std::memcpy(guest_ctx->host_ctx.host_saved_regs.data(), &host_ctx.regs[19],
+ sizeof(guest_ctx->host_ctx.host_saved_regs));
+
+ // Save stack pointer.
+ guest_ctx->host_ctx.host_sp = host_ctx.sp;
+
+ // Restore all guest state except tpidr_el0.
+ host_ctx.sp = guest_ctx->sp;
+ host_ctx.pc = guest_ctx->pc;
+ host_ctx.pstate = guest_ctx->pstate;
+ fpctx->fpcr = guest_ctx->fpcr;
+ fpctx->fpsr = guest_ctx->fpsr;
+ std::memcpy(host_ctx.regs, guest_ctx->cpu_registers.data(), sizeof(host_ctx.regs));
+ std::memcpy(fpctx->vregs, guest_ctx->vector_registers.data(), sizeof(fpctx->vregs));
+
+ // Return the new thread-local storage pointer.
+ return tpidr;
+}
+
+void ARM_NCE::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
+ // Retrieve the host context.
+ auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
+
+ // Retrieve the host floating point state.
+ auto* fpctx = GetFloatingPointState(host_ctx);
+
+ // Save all guest registers except tpidr_el0.
+ std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs, sizeof(host_ctx.regs));
+ std::memcpy(guest_ctx->vector_registers.data(), fpctx->vregs, sizeof(fpctx->vregs));
+ guest_ctx->fpsr = fpctx->fpsr;
+ guest_ctx->fpcr = fpctx->fpcr;
+ guest_ctx->pstate = static_cast<u32>(host_ctx.pstate);
+ guest_ctx->pc = host_ctx.pc;
+ guest_ctx->sp = host_ctx.sp;
+
+ // Restore stack pointer.
+ host_ctx.sp = guest_ctx->host_ctx.host_sp;
+
+ // Restore host callee-saved registers.
+ std::memcpy(&host_ctx.regs[19], guest_ctx->host_ctx.host_saved_regs.data(),
+ sizeof(guest_ctx->host_ctx.host_saved_regs));
+ std::memcpy(&fpctx->vregs[8], guest_ctx->host_ctx.host_saved_vregs.data(),
+ sizeof(guest_ctx->host_ctx.host_saved_vregs));
+
+ // Return from the call on exit by setting pc to x30.
+ host_ctx.pc = guest_ctx->host_ctx.host_saved_regs[11];
+
+ // Clear esr_el1 and return it.
+ host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
+}
+
+bool ARM_NCE::HandleGuestFault(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);
+
+ // For data aborts, skip the instruction and return to guest code.
+ // This will allow games to continue in many scenarios where they would otherwise crash.
+ if (!is_prefetch_abort) {
+ host_ctx.pc += 4;
+ return true;
+ }
+
+ // This is a prefetch abort.
+ guest_ctx->esr_el1.fetch_or(static_cast<u64>(HaltReason::PrefetchAbort));
+
+ // Forcibly mark the context as locked. We are still running.
+ // We may race with SignalInterrupt here:
+ // - If we lose the race, then SignalInterrupt will send us a signal we are masking,
+ // and it will do nothing when it is unmasked, as we have already left guest code.
+ // - If we win the race, then SignalInterrupt will wait for us to unlock first.
+ auto& thread_params = guest_ctx->parent->running_thread->GetNativeExecutionParameters();
+ thread_params.lock.store(SpinLockLocked);
+
+ // Return to host.
+ SaveGuestContext(guest_ctx, raw_context);
+ return false;
+}
+
+void ARM_NCE::HandleHostFault(int sig, void* raw_info, void* raw_context) {
+ return g_orig_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
+}
+
+HaltReason ARM_NCE::RunJit() {
+ // Get the thread parameters.
+ // TODO: pass the current thread down from ::Run
+ auto* thread = Kernel::GetCurrentThreadPointer(system.Kernel());
+ auto* thread_params = &thread->GetNativeExecutionParameters();
+
+ {
+ // Lock our core context.
+ std::scoped_lock lk{lock};
+
+ // We should not be running.
+ ASSERT(running_thread == nullptr);
+
+ // Check if we need to run. If we have already been halted, we are done.
+ u64 halt = guest_ctx.esr_el1.exchange(0);
+ if (halt != 0) {
+ return static_cast<HaltReason>(halt);
+ }
+
+ // Mark that we are running.
+ running_thread = thread;
+
+ // Acquire the lock on the thread parameters.
+ // This allows us to force synchronization with SignalInterrupt.
+ LockThreadParameters(thread_params);
+ }
+
+ // Assign current members.
+ guest_ctx.parent = this;
+ thread_params->native_context = &guest_ctx;
+ thread_params->tpidr_el0 = guest_ctx.tpidr_el0;
+ thread_params->tpidrro_el0 = guest_ctx.tpidrro_el0;
+ thread_params->is_running = true;
+
+ HaltReason halt{};
+
+ // TODO: finding and creating the post handler needs to be locked
+ // to deal with dynamic loading of NROs.
+ const auto& post_handlers = system.ApplicationProcess()->GetPostHandlers();
+ if (auto it = post_handlers.find(guest_ctx.pc); it != post_handlers.end()) {
+ halt = ReturnToRunCodeByTrampoline(thread_params, &guest_ctx, it->second);
+ } else {
+ halt = ReturnToRunCodeByExceptionLevelChange(thread_id, thread_params);
+ }
+
+ // Unload members.
+ // The thread does not change, so we can persist the old reference.
+ guest_ctx.tpidr_el0 = thread_params->tpidr_el0;
+ thread_params->native_context = nullptr;
+ thread_params->is_running = false;
+
+ // Unlock the thread parameters.
+ UnlockThreadParameters(thread_params);
+
+ {
+ // Lock the core context.
+ std::scoped_lock lk{lock};
+
+ // On exit, we no longer have an active thread.
+ running_thread = nullptr;
+ }
+
+ // Return the halt reason.
+ return halt;
+}
+
+HaltReason ARM_NCE::StepJit() {
+ return HaltReason::StepThread;
+}
+
+u32 ARM_NCE::GetSvcNumber() const {
+ return guest_ctx.svc_swi;
+}
+
+ARM_NCE::ARM_NCE(System& system_, bool uses_wall_clock_, std::size_t core_index_)
+ : ARM_Interface{system_, uses_wall_clock_}, core_index{core_index_} {
+ guest_ctx.system = &system_;
+}
+
+ARM_NCE::~ARM_NCE() = default;
+
+void ARM_NCE::Initialize() {
+ thread_id = gettid();
+
+ // Setup our 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);
+
+ struct sigaction return_to_run_code_action {};
+ return_to_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ return_to_run_code_action.sa_sigaction = reinterpret_cast<HandlerType>(
+ &ARM_NCE::ReturnToRunCodeByExceptionLevelChangeSignalHandler);
+ return_to_run_code_action.sa_mask = signal_mask;
+ Common::SigAction(ReturnToRunCodeByExceptionLevelChangeSignal, &return_to_run_code_action,
+ nullptr);
+
+ struct sigaction break_from_run_code_action {};
+ break_from_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
+ break_from_run_code_action.sa_sigaction =
+ reinterpret_cast<HandlerType>(&ARM_NCE::BreakFromRunCodeSignalHandler);
+ 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>(&ARM_NCE::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);
+ }
+ });
+}
+
+void ARM_NCE::SetPC(u64 pc) {
+ guest_ctx.pc = pc;
+}
+
+u64 ARM_NCE::GetPC() const {
+ return guest_ctx.pc;
+}
+
+u64 ARM_NCE::GetSP() const {
+ return guest_ctx.sp;
+}
+
+u64 ARM_NCE::GetReg(int index) const {
+ return guest_ctx.cpu_registers[index];
+}
+
+void ARM_NCE::SetReg(int index, u64 value) {
+ guest_ctx.cpu_registers[index] = value;
+}
+
+u128 ARM_NCE::GetVectorReg(int index) const {
+ return guest_ctx.vector_registers[index];
+}
+
+void ARM_NCE::SetVectorReg(int index, u128 value) {
+ guest_ctx.vector_registers[index] = value;
+}
+
+u32 ARM_NCE::GetPSTATE() const {
+ return guest_ctx.pstate;
+}
+
+void ARM_NCE::SetPSTATE(u32 pstate) {
+ guest_ctx.pstate = pstate;
+}
+
+u64 ARM_NCE::GetTlsAddress() const {
+ return guest_ctx.tpidrro_el0;
+}
+
+void ARM_NCE::SetTlsAddress(u64 address) {
+ guest_ctx.tpidrro_el0 = address;
+}
+
+u64 ARM_NCE::GetTPIDR_EL0() const {
+ return guest_ctx.tpidr_el0;
+}
+
+void ARM_NCE::SetTPIDR_EL0(u64 value) {
+ guest_ctx.tpidr_el0 = value;
+}
+
+void ARM_NCE::SaveContext(ThreadContext64& ctx) const {
+ ctx.cpu_registers = guest_ctx.cpu_registers;
+ ctx.sp = guest_ctx.sp;
+ ctx.pc = guest_ctx.pc;
+ ctx.pstate = guest_ctx.pstate;
+ ctx.vector_registers = guest_ctx.vector_registers;
+ ctx.fpcr = guest_ctx.fpcr;
+ ctx.fpsr = guest_ctx.fpsr;
+ ctx.tpidr = guest_ctx.tpidr_el0;
+}
+
+void ARM_NCE::LoadContext(const ThreadContext64& ctx) {
+ guest_ctx.cpu_registers = ctx.cpu_registers;
+ guest_ctx.sp = ctx.sp;
+ guest_ctx.pc = ctx.pc;
+ guest_ctx.pstate = ctx.pstate;
+ guest_ctx.vector_registers = ctx.vector_registers;
+ guest_ctx.fpcr = ctx.fpcr;
+ guest_ctx.fpsr = ctx.fpsr;
+ guest_ctx.tpidr_el0 = ctx.tpidr;
+}
+
+void ARM_NCE::SignalInterrupt() {
+ // Lock core context.
+ std::scoped_lock lk{lock};
+
+ // Add break loop condition.
+ guest_ctx.esr_el1.fetch_or(static_cast<u64>(HaltReason::BreakLoop));
+
+ // If there is no thread running, we are done.
+ if (running_thread == nullptr) {
+ return;
+ }
+
+ // Lock the thread context.
+ auto* params = &running_thread->GetNativeExecutionParameters();
+ LockThreadParameters(params);
+
+ if (params->is_running) {
+ // We should signal to the running thread.
+ // The running thread will unlock the thread context.
+ syscall(SYS_tkill, thread_id, BreakFromRunCodeSignal);
+ } else {
+ // If the thread is no longer running, we have nothing to do.
+ UnlockThreadParameters(params);
+ }
+}
+
+void ARM_NCE::ClearInterrupt() {
+ guest_ctx.esr_el1 = {};
+}
+
+void ARM_NCE::ClearInstructionCache() {
+ // TODO: This is not possible to implement correctly on Linux because
+ // we do not have any access to ic iallu.
+
+ // Require accesses to complete.
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+}
+
+void ARM_NCE::InvalidateCacheRange(u64 addr, std::size_t size) {
+ this->ClearInstructionCache();
+}
+
+void ARM_NCE::ClearExclusiveState() {
+ // No-op.
+}
+
+void ARM_NCE::PageTableChanged(Common::PageTable& page_table,
+ std::size_t new_address_space_size_in_bits) {
+ // No-op. Page table is never used.
+}
+
+} // namespace Core
diff --git a/src/core/arm/nce/arm_nce.h b/src/core/arm/nce/arm_nce.h
new file mode 100644
index 000000000..5fbd6dbf3
--- /dev/null
+++ b/src/core/arm/nce/arm_nce.h
@@ -0,0 +1,108 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <span>
+#include <unordered_map>
+#include <vector>
+
+#include "core/arm/arm_interface.h"
+#include "core/arm/nce/guest_context.h"
+
+namespace Core::Memory {
+class Memory;
+}
+
+namespace Core {
+
+class System;
+
+class ARM_NCE final : public ARM_Interface {
+public:
+ ARM_NCE(System& system_, bool uses_wall_clock_, std::size_t core_index_);
+
+ ~ARM_NCE() override;
+
+ void Initialize() override;
+ void SetPC(u64 pc) override;
+ u64 GetPC() const override;
+ u64 GetSP() const override;
+ u64 GetReg(int index) const override;
+ void SetReg(int index, u64 value) override;
+ u128 GetVectorReg(int index) const override;
+ void SetVectorReg(int index, u128 value) override;
+
+ u32 GetPSTATE() const override;
+ void SetPSTATE(u32 pstate) override;
+ u64 GetTlsAddress() const override;
+ void SetTlsAddress(u64 address) override;
+ void SetTPIDR_EL0(u64 value) override;
+ u64 GetTPIDR_EL0() const override;
+
+ Architecture GetArchitecture() const override {
+ return Architecture::Aarch64;
+ }
+
+ void SaveContext(ThreadContext32& ctx) const override {}
+ void SaveContext(ThreadContext64& ctx) const override;
+ void LoadContext(const ThreadContext32& ctx) override {}
+ void LoadContext(const ThreadContext64& ctx) override;
+
+ void SignalInterrupt() override;
+ void ClearInterrupt() override;
+ void ClearExclusiveState() override;
+ void ClearInstructionCache() override;
+ void InvalidateCacheRange(u64 addr, std::size_t size) override;
+ void PageTableChanged(Common::PageTable& new_page_table,
+ std::size_t new_address_space_size_in_bits) override;
+
+protected:
+ HaltReason RunJit() override;
+ HaltReason StepJit() override;
+
+ u32 GetSvcNumber() const override;
+
+ const Kernel::DebugWatchpoint* HaltedWatchpoint() const override {
+ return nullptr;
+ }
+
+ void RewindBreakpointInstruction() override {}
+
+private:
+ // Assembly definitions.
+ static HaltReason ReturnToRunCodeByTrampoline(void* tpidr, GuestContext* ctx,
+ u64 trampoline_addr);
+ static HaltReason ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr);
+
+ 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 LockThreadParameters(void* tpidr);
+ static void UnlockThreadParameters(void* tpidr);
+
+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);
+
+public:
+ // Members set on initialization.
+ std::size_t core_index{};
+ pid_t thread_id{-1};
+
+ // Core context.
+ GuestContext guest_ctx;
+
+ // Thread and invalidation info.
+ std::mutex lock;
+ Kernel::KThread* running_thread{};
+};
+
+} // namespace Core
diff --git a/src/core/arm/nce/arm_nce.s b/src/core/arm/nce/arm_nce.s
new file mode 100644
index 000000000..b98e09f31
--- /dev/null
+++ b/src/core/arm/nce/arm_nce.s
@@ -0,0 +1,222 @@
+/* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "core/arm/nce/arm_nce_asm_definitions.h"
+
+#define LOAD_IMMEDIATE_32(reg, val) \
+ mov reg, #(((val) >> 0x00) & 0xFFFF); \
+ movk reg, #(((val) >> 0x10) & 0xFFFF), lsl #16
+
+
+/* static HaltReason Core::ARM_NCE::ReturnToRunCodeByTrampoline(void* tpidr, Core::GuestContext* ctx, u64 trampoline_addr) */
+.section .text._ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, "ax", %progbits
+.global _ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm
+.type _ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, %function
+_ZN4Core7ARM_NCE27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm:
+ /* Back up host sp to x3. */
+ /* Back up host tpidr_el0 to x4. */
+ mov x3, sp
+ mrs x4, tpidr_el0
+
+ /* Load guest sp. x5 is used as a scratch register. */
+ ldr x5, [x1, #(GuestContextSp)]
+ mov sp, x5
+
+ /* Offset GuestContext pointer to the host member. */
+ add x5, x1, #(GuestContextHostContext)
+
+ /* Save original host sp and tpidr_el0 (x3, x4) to host context. */
+ stp x3, x4, [x5, #(HostContextSpTpidrEl0)]
+
+ /* Save all callee-saved host GPRs. */
+ stp x19, x20, [x5, #(HostContextRegs+0x0)]
+ stp x21, x22, [x5, #(HostContextRegs+0x10)]
+ stp x23, x24, [x5, #(HostContextRegs+0x20)]
+ stp x25, x26, [x5, #(HostContextRegs+0x30)]
+ stp x27, x28, [x5, #(HostContextRegs+0x40)]
+ stp x29, x30, [x5, #(HostContextRegs+0x50)]
+
+ /* Save all callee-saved host FPRs. */
+ stp q8, q9, [x5, #(HostContextVregs+0x0)]
+ stp q10, q11, [x5, #(HostContextVregs+0x20)]
+ stp q12, q13, [x5, #(HostContextVregs+0x40)]
+ stp q14, q15, [x5, #(HostContextVregs+0x60)]
+
+ /* Load guest tpidr_el0 from argument. */
+ msr tpidr_el0, x0
+
+ /* Tail call the trampoline to restore guest state. */
+ br x2
+
+
+/* static HaltReason Core::ARM_NCE::ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr) */
+.section .text._ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv, "ax", %progbits
+.global _ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv
+.type _ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv, %function
+_ZN4Core7ARM_NCE37ReturnToRunCodeByExceptionLevelChangeEiPv:
+ /* This jumps to the signal handler, which will restore the entire context. */
+ /* On entry, x0 = thread id, which is already in the right place. */
+
+ /* Move tpidr to x9 so it is not trampled. */
+ mov x9, x1
+
+ /* Set up arguments. */
+ mov x8, #(__NR_tkill)
+ mov x1, #(ReturnToRunCodeByExceptionLevelChangeSignal)
+
+ /* Tail call the signal handler. */
+ svc #0
+
+ /* Block execution from flowing here. */
+ brk #1000
+
+
+/* static void Core::ARM_NCE::ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info, void* raw_context) */
+.section .text._ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_, "ax", %progbits
+.global _ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_
+.type _ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_, %function
+_ZN4Core7ARM_NCE50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_:
+ stp x29, x30, [sp, #-0x10]!
+ mov x29, sp
+
+ /* Call the context restorer with the raw context. */
+ mov x0, x2
+ bl _ZN4Core7ARM_NCE19RestoreGuestContextEPv
+
+ /* Save the old value of tpidr_el0. */
+ mrs x8, tpidr_el0
+ ldr x9, [x0, #(TpidrEl0NativeContext)]
+ str x8, [x9, #(GuestContextHostContext + HostContextTpidrEl0)]
+
+ /* Set our new tpidr_el0. */
+ msr tpidr_el0, x0
+
+ /* Unlock the context. */
+ bl _ZN4Core7ARM_NCE22UnlockThreadParametersEPv
+
+ /* Returning from here will enter the guest. */
+ ldp x29, x30, [sp], #0x10
+ ret
+
+
+/* static void Core::ARM_NCE::BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context) */
+.section .text._ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_, "ax", %progbits
+.global _ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_
+.type _ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_, %function
+_ZN4Core7ARM_NCE29BreakFromRunCodeSignalHandlerEiPvS1_:
+ /* 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.ne 1f
+
+ /* Correct TLS magic, so this is a guest interrupt. */
+ /* Restore host tpidr_el0. */
+ ldr x0, [x8, #(TpidrEl0NativeContext)]
+ ldr x3, [x0, #(GuestContextHostContext + HostContextTpidrEl0)]
+ msr tpidr_el0, x3
+
+ /* Tail call the restorer. */
+ mov x1, x2
+ b _ZN4Core7ARM_NCE16SaveGuestContextEPNS_12GuestContextEPv
+
+ /* Returning from here will enter host code. */
+
+1:
+ /* Incorrect TLS magic, so this is a spurious signal. */
+ ret
+
+
+/* static void Core::ARM_NCE::GuestFaultSignalHandler(int sig, void* info, void* raw_context) */
+.section .text._ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_, "ax", %progbits
+.global _ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_
+.type _ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_, %function
+_ZN4Core7ARM_NCE23GuestFaultSignalHandlerEiPvS1_:
+ /* 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 _ZN4Core7ARM_NCE15HandleHostFaultEiPvS1_
+
+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 _ZN4Core7ARM_NCE16HandleGuestFaultEPNS_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::ARM_NCE::LockThreadParameters(void* tpidr) */
+.section .text._ZN4Core7ARM_NCE20LockThreadParametersEPv, "ax", %progbits
+.global _ZN4Core7ARM_NCE20LockThreadParametersEPv
+.type _ZN4Core7ARM_NCE20LockThreadParametersEPv, %function
+_ZN4Core7ARM_NCE20LockThreadParametersEPv:
+ /* Offset to lock member. */
+ add x0, x0, #(TpidrEl0Lock)
+
+1:
+ /* Clear the monitor. */
+ clrex
+
+2:
+ /* Load-linked with acquire ordering. */
+ ldaxr w1, [x0]
+
+ /* If the value was SpinLockLocked, clear monitor and retry. */
+ cbz w1, 1b
+
+ /* Store-conditional SpinLockLocked with relaxed ordering. */
+ stxr w1, wzr, [x0]
+
+ /* If we failed to store, retry. */
+ cbnz w1, 2b
+
+ ret
+
+
+/* static void Core::ARM_NCE::UnlockThreadParameters(void* tpidr) */
+.section .text._ZN4Core7ARM_NCE22UnlockThreadParametersEPv, "ax", %progbits
+.global _ZN4Core7ARM_NCE22UnlockThreadParametersEPv
+.type _ZN4Core7ARM_NCE22UnlockThreadParametersEPv, %function
+_ZN4Core7ARM_NCE22UnlockThreadParametersEPv:
+ /* Offset to lock member. */
+ add x0, x0, #(TpidrEl0Lock)
+
+ /* Load SpinLockUnlocked. */
+ mov w1, #(SpinLockUnlocked)
+
+ /* Store value with release ordering. */
+ stlr w1, [x0]
+
+ ret
diff --git a/src/core/arm/nce/arm_nce_asm_definitions.h b/src/core/arm/nce/arm_nce_asm_definitions.h
new file mode 100644
index 000000000..8a9b285b5
--- /dev/null
+++ b/src/core/arm/nce/arm_nce_asm_definitions.h
@@ -0,0 +1,29 @@
+/* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+#define __ASSEMBLY__
+
+#include <asm-generic/signal.h>
+#include <asm-generic/unistd.h>
+
+#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
+#define BreakFromRunCodeSignal SIGURG
+#define GuestFaultSignal SIGSEGV
+
+#define GuestContextSp 0xF8
+#define GuestContextHostContext 0x320
+
+#define HostContextSpTpidrEl0 0xE0
+#define HostContextTpidrEl0 0xE8
+#define HostContextRegs 0x0
+#define HostContextVregs 0x60
+
+#define TpidrEl0NativeContext 0x10
+#define TpidrEl0Lock 0x18
+#define TpidrEl0TlsMagic 0x20
+#define TlsMagic 0x555a5559
+
+#define SpinLockLocked 0
+#define SpinLockUnlocked 1
diff --git a/src/core/arm/nce/guest_context.h b/src/core/arm/nce/guest_context.h
new file mode 100644
index 000000000..0767a0337
--- /dev/null
+++ b/src/core/arm/nce/guest_context.h
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/arm/arm_interface.h"
+#include "core/arm/nce/arm_nce_asm_definitions.h"
+
+namespace Core {
+
+class ARM_NCE;
+class System;
+
+struct HostContext {
+ alignas(16) std::array<u64, 12> host_saved_regs{};
+ alignas(16) std::array<u128, 8> host_saved_vregs{};
+ u64 host_sp{};
+ void* host_tpidr_el0{};
+};
+
+struct GuestContext {
+ std::array<u64, 31> cpu_registers{};
+ u64 sp{};
+ u64 pc{};
+ u32 fpcr{};
+ u32 fpsr{};
+ std::array<u128, 32> vector_registers{};
+ u32 pstate{};
+ alignas(16) HostContext host_ctx{};
+ u64 tpidrro_el0{};
+ u64 tpidr_el0{};
+ std::atomic<u64> esr_el1{};
+ u32 nzcv{};
+ u32 svc_swi{};
+ System* system{};
+ ARM_NCE* parent{};
+};
+
+// Verify assembly offsets.
+static_assert(offsetof(GuestContext, sp) == GuestContextSp);
+static_assert(offsetof(GuestContext, host_ctx) == GuestContextHostContext);
+static_assert(offsetof(HostContext, host_sp) == HostContextSpTpidrEl0);
+static_assert(offsetof(HostContext, host_tpidr_el0) - 8 == HostContextSpTpidrEl0);
+static_assert(offsetof(HostContext, host_tpidr_el0) == HostContextTpidrEl0);
+static_assert(offsetof(HostContext, host_saved_regs) == HostContextRegs);
+static_assert(offsetof(HostContext, host_saved_vregs) == HostContextVregs);
+
+} // namespace Core
diff --git a/src/core/arm/nce/instructions.h b/src/core/arm/nce/instructions.h
new file mode 100644
index 000000000..5b56ff857
--- /dev/null
+++ b/src/core/arm/nce/instructions.h
@@ -0,0 +1,147 @@
+// SPDX-FileCopyrightText: Copyright © 2020 Skyline Team and Contributors
+// SPDX-License-Identifier: MPL-2.0
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Core::NCE {
+
+enum SystemRegister : u32 {
+ TpidrEl0 = 0x5E82,
+ TpidrroEl0 = 0x5E83,
+ CntfrqEl0 = 0x5F00,
+ CntpctEl0 = 0x5F01,
+};
+
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/SVC--Supervisor-Call-
+union SVC {
+ constexpr explicit SVC(u32 raw_) : raw{raw_} {}
+
+ constexpr bool Verify() {
+ return (this->GetSig0() == 0x1 && this->GetSig1() == 0x6A0);
+ }
+
+ constexpr u32 GetSig0() {
+ return decltype(sig0)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetValue() {
+ return decltype(value)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSig1() {
+ return decltype(sig1)::ExtractValue(raw);
+ }
+
+ u32 raw;
+
+private:
+ BitField<0, 5, u32> sig0; // 0x1
+ BitField<5, 16, u32> value; // 16-bit immediate
+ BitField<21, 11, u32> sig1; // 0x6A0
+};
+static_assert(sizeof(SVC) == sizeof(u32));
+static_assert(SVC(0xD40000C1).Verify());
+static_assert(SVC(0xD40000C1).GetValue() == 0x6);
+
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/MRS--Move-System-Register-
+union MRS {
+ constexpr explicit MRS(u32 raw_) : raw{raw_} {}
+
+ constexpr bool Verify() {
+ return (this->GetSig() == 0xD53);
+ }
+
+ constexpr u32 GetRt() {
+ return decltype(rt)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSystemReg() {
+ return decltype(system_reg)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSig() {
+ return decltype(sig)::ExtractValue(raw);
+ }
+
+ u32 raw;
+
+private:
+ BitField<0, 5, u32> rt; // destination register
+ BitField<5, 15, u32> system_reg; // source system register
+ BitField<20, 12, u32> sig; // 0xD53
+};
+static_assert(sizeof(MRS) == sizeof(u32));
+static_assert(MRS(0xD53BE020).Verify());
+static_assert(MRS(0xD53BE020).GetSystemReg() == CntpctEl0);
+static_assert(MRS(0xD53BE020).GetRt() == 0x0);
+
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/MSR--register---Move-general-purpose-register-to-System-Register-
+union MSR {
+ constexpr explicit MSR(u32 raw_) : raw{raw_} {}
+
+ constexpr bool Verify() {
+ return this->GetSig() == 0xD51;
+ }
+
+ constexpr u32 GetRt() {
+ return decltype(rt)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSystemReg() {
+ return decltype(system_reg)::ExtractValue(raw);
+ }
+
+ constexpr u32 GetSig() {
+ return decltype(sig)::ExtractValue(raw);
+ }
+
+ u32 raw;
+
+private:
+ BitField<0, 5, u32> rt; // source register
+ BitField<5, 15, u32> system_reg; // destination system register
+ BitField<20, 12, u32> sig; // 0xD51
+};
+static_assert(sizeof(MSR) == sizeof(u32));
+static_assert(MSR(0xD51BD040).Verify());
+static_assert(MSR(0xD51BD040).GetSystemReg() == TpidrEl0);
+static_assert(MSR(0xD51BD040).GetRt() == 0x0);
+
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDXR--Load-Exclusive-Register-
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDXP--Load-Exclusive-Pair-of-Registers-
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/STXR--Store-Exclusive-Register-
+// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/STXP--Store-Exclusive-Pair-of-registers-
+union Exclusive {
+ constexpr explicit Exclusive(u32 raw_) : raw{raw_} {}
+
+ constexpr bool Verify() {
+ return this->GetSig() == 0x10;
+ }
+
+ constexpr u32 GetSig() {
+ return decltype(sig)::ExtractValue(raw);
+ }
+
+ constexpr u32 AsOrdered() {
+ return raw | decltype(o0)::FormatValue(1);
+ }
+
+ u32 raw;
+
+private:
+ BitField<0, 5, u32> rt; // memory operand
+ BitField<5, 5, u32> rn; // register operand 1
+ BitField<10, 5, u32> rt2; // register operand 2
+ BitField<15, 1, u32> o0; // ordered
+ BitField<16, 5, u32> rs; // status register
+ BitField<21, 2, u32> l; // operation type
+ BitField<23, 7, u32> sig; // 0x10
+ BitField<30, 2, u32> size; // size
+};
+static_assert(Exclusive(0xC85FFC00).Verify());
+static_assert(Exclusive(0xC85FFC00).AsOrdered() == 0xC85FFC00);
+static_assert(Exclusive(0xC85F7C00).AsOrdered() == 0xC85FFC00);
+static_assert(Exclusive(0xC8200440).AsOrdered() == 0xC8208440);
+
+} // namespace Core::NCE
diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp
new file mode 100644
index 000000000..ec8527224
--- /dev/null
+++ b/src/core/arm/nce/patcher.cpp
@@ -0,0 +1,474 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/arm64/native_clock.h"
+#include "common/bit_cast.h"
+#include "common/literals.h"
+#include "core/arm/nce/arm_nce.h"
+#include "core/arm/nce/guest_context.h"
+#include "core/arm/nce/instructions.h"
+#include "core/arm/nce/patcher.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/svc.h"
+
+namespace Core::NCE {
+
+using namespace Common::Literals;
+using namespace oaknut::util;
+
+using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
+
+constexpr size_t MaxRelativeBranch = 128_MiB;
+constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
+
+Patcher::Patcher() : c(m_patch_instructions) {}
+
+Patcher::~Patcher() = default;
+
+void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
+ const Kernel::CodeSet::Segment& code) {
+
+ // Write save context helper function.
+ c.l(m_save_context);
+ WriteSaveContext();
+
+ // Write load context helper function.
+ c.l(m_load_context);
+ WriteLoadContext();
+
+ // Retrieve text segment data.
+ const auto text = std::span{program_image}.subspan(code.offset, code.size);
+ const auto text_words =
+ std::span<const u32>{reinterpret_cast<const u32*>(text.data()), text.size() / sizeof(u32)};
+
+ // Loop through instructions, patching as needed.
+ for (u32 i = ModuleCodeIndex; i < static_cast<u32>(text_words.size()); i++) {
+ const u32 inst = text_words[i];
+
+ const auto AddRelocations = [&] {
+ const uintptr_t this_offset = i * sizeof(u32);
+ const uintptr_t next_offset = this_offset + sizeof(u32);
+
+ // Relocate from here to patch.
+ this->BranchToPatch(this_offset);
+
+ // Relocate from patch to next instruction.
+ return next_offset;
+ };
+
+ // SVC
+ if (auto svc = SVC{inst}; svc.Verify()) {
+ WriteSvcTrampoline(AddRelocations(), svc.GetValue());
+ continue;
+ }
+
+ // MRS Xn, TPIDR_EL0
+ // MRS Xn, TPIDRRO_EL0
+ if (auto mrs = MRS{inst};
+ mrs.Verify() && (mrs.GetSystemReg() == TpidrroEl0 || mrs.GetSystemReg() == TpidrEl0)) {
+ const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0
+ : oaknut::SystemReg::TPIDR_EL0;
+ const auto dest_reg = oaknut::XReg{static_cast<int>(mrs.GetRt())};
+ WriteMrsHandler(AddRelocations(), dest_reg, src_reg);
+ continue;
+ }
+
+ // MRS Xn, CNTPCT_EL0
+ if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntpctEl0) {
+ WriteCntpctHandler(AddRelocations(), oaknut::XReg{static_cast<int>(mrs.GetRt())});
+ continue;
+ }
+
+ // MRS Xn, CNTFRQ_EL0
+ if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntfrqEl0) {
+ UNREACHABLE();
+ }
+
+ // MSR TPIDR_EL0, Xn
+ if (auto msr = MSR{inst}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) {
+ WriteMsrHandler(AddRelocations(), oaknut::XReg{static_cast<int>(msr.GetRt())});
+ continue;
+ }
+
+ if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
+ m_exclusives.push_back(i);
+ }
+ }
+
+ // Determine patching mode for the final relocation step
+ const size_t image_size = program_image.size();
+ this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
+}
+
+void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
+ const Kernel::CodeSet::Segment& code,
+ Kernel::PhysicalMemory& program_image,
+ EntryTrampolines* out_trampolines) {
+ const size_t patch_size = GetSectionSize();
+ const size_t image_size = program_image.size();
+
+ // Retrieve text segment data.
+ const auto text = std::span{program_image}.subspan(code.offset, code.size);
+ const auto text_words =
+ std::span<u32>{reinterpret_cast<u32*>(text.data()), text.size() / sizeof(u32)};
+
+ const auto ApplyBranchToPatchRelocation = [&](u32* target, const Relocation& rel) {
+ oaknut::CodeGenerator rc{target};
+ if (mode == PatchMode::PreText) {
+ rc.B(rel.patch_offset - patch_size - rel.module_offset);
+ } else {
+ rc.B(image_size - rel.module_offset + rel.patch_offset);
+ }
+ };
+
+ const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) {
+ oaknut::CodeGenerator rc{target};
+ if (mode == PatchMode::PreText) {
+ rc.B(patch_size - rel.patch_offset + rel.module_offset);
+ } else {
+ rc.B(rel.module_offset - image_size - rel.patch_offset);
+ }
+ };
+
+ const auto RebasePatch = [&](ptrdiff_t patch_offset) {
+ if (mode == PatchMode::PreText) {
+ return GetInteger(load_base) + patch_offset;
+ } else {
+ return GetInteger(load_base) + image_size + patch_offset;
+ }
+ };
+
+ const auto RebasePc = [&](uintptr_t module_offset) {
+ if (mode == PatchMode::PreText) {
+ return GetInteger(load_base) + patch_size + module_offset;
+ } else {
+ return GetInteger(load_base) + module_offset;
+ }
+ };
+
+ // We are now ready to relocate!
+ for (const Relocation& rel : m_branch_to_patch_relocations) {
+ ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
+ }
+ for (const Relocation& rel : m_branch_to_module_relocations) {
+ ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
+ rel);
+ }
+
+ // Rewrite PC constants and record post trampolines
+ for (const Relocation& rel : m_write_module_pc_relocations) {
+ oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
+ rc.dx(RebasePc(rel.module_offset));
+ }
+ for (const Trampoline& rel : m_trampolines) {
+ out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)});
+ }
+
+ // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not.
+ // Convert to ordered to preserve this assumption.
+ for (const ModuleTextAddress i : m_exclusives) {
+ auto exclusive = Exclusive{text_words[i]};
+ text_words[i] = exclusive.AsOrdered();
+ }
+
+ // Copy to program image
+ if (this->mode == PatchMode::PreText) {
+ std::memcpy(program_image.data(), m_patch_instructions.data(),
+ m_patch_instructions.size() * sizeof(u32));
+ } else {
+ program_image.resize(image_size + patch_size);
+ std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
+ m_patch_instructions.size() * sizeof(u32));
+ }
+}
+
+size_t Patcher::GetSectionSize() const noexcept {
+ return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE);
+}
+
+void Patcher::WriteLoadContext() {
+ // This function was called, which modifies X30, so use that as a scratch register.
+ // SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes
+ // of stack.
+ c.STR(X30, SP, 8);
+ c.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
+
+ // Load system registers.
+ c.LDR(W0, X30, offsetof(GuestContext, fpsr));
+ c.MSR(oaknut::SystemReg::FPSR, X0);
+ c.LDR(W0, X30, offsetof(GuestContext, fpcr));
+ c.MSR(oaknut::SystemReg::FPCR, X0);
+ c.LDR(W0, X30, offsetof(GuestContext, nzcv));
+ c.MSR(oaknut::SystemReg::NZCV, X0);
+
+ // Load all vector registers.
+ static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
+ for (int i = 0; i <= 30; i += 2) {
+ c.LDP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i);
+ }
+
+ // Load all general-purpose registers except X30.
+ for (int i = 0; i <= 28; i += 2) {
+ c.LDP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i);
+ }
+
+ // Reload our return X30 from the stack and return.
+ // The patch code will reload the guest X30 for us.
+ c.LDR(X30, SP, 8);
+ c.RET();
+}
+
+void Patcher::WriteSaveContext() {
+ // This function was called, which modifies X30, so use that as a scratch register.
+ // SP contains the guest X30, so save our X30 to SP + 8, since we have allocated 16 bytes of
+ // stack.
+ c.STR(X30, SP, 8);
+ c.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
+
+ // Store all general-purpose registers except X30.
+ for (int i = 0; i <= 28; i += 2) {
+ c.STP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i);
+ }
+
+ // Store all vector registers.
+ static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
+ for (int i = 0; i <= 30; i += 2) {
+ c.STP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i);
+ }
+
+ // Store guest system registers, X30 and SP, using X0 as a scratch register.
+ c.STR(X0, SP, PRE_INDEXED, -16);
+ c.LDR(X0, SP, 16);
+ c.STR(X0, X30, 8 * 30);
+ c.ADD(X0, SP, 32);
+ c.STR(X0, X30, offsetof(GuestContext, sp));
+ c.MRS(X0, oaknut::SystemReg::FPSR);
+ c.STR(W0, X30, offsetof(GuestContext, fpsr));
+ c.MRS(X0, oaknut::SystemReg::FPCR);
+ c.STR(W0, X30, offsetof(GuestContext, fpcr));
+ c.MRS(X0, oaknut::SystemReg::NZCV);
+ c.STR(W0, X30, offsetof(GuestContext, nzcv));
+ c.LDR(X0, SP, POST_INDEXED, 16);
+
+ // Reload our return X30 from the stack, and return.
+ c.LDR(X30, SP, 8);
+ c.RET();
+}
+
+void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
+ // We are about to start saving state, so we need to lock the context.
+ this->LockContext();
+
+ // Store guest X30 to the stack. Then, save the context and restore the stack.
+ // This will save all registers except PC, but we know PC at patch time.
+ c.STR(X30, SP, PRE_INDEXED, -16);
+ c.BL(m_save_context);
+ c.LDR(X30, SP, POST_INDEXED, 16);
+
+ // Now that we've saved all registers, we can use any registers as scratch.
+ // Store PC + 4 to arm interface, since we know the instruction offset from the entry point.
+ oaknut::Label pc_after_svc;
+ c.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
+ c.LDR(X2, pc_after_svc);
+ c.STR(X2, X1, offsetof(GuestContext, pc));
+
+ // Store SVC number to execute when we return
+ c.MOV(X2, svc_id);
+ c.STR(W2, X1, offsetof(GuestContext, svc_swi));
+
+ // We are calling a SVC. Clear esr_el1 and return it.
+ static_assert(std::is_same_v<std::underlying_type_t<HaltReason>, u64>);
+ oaknut::Label retry;
+ c.ADD(X2, X1, offsetof(GuestContext, esr_el1));
+ c.l(retry);
+ c.LDAXR(X0, X2);
+ c.STLXR(W3, XZR, X2);
+ c.CBNZ(W3, retry);
+
+ // Add "calling SVC" flag. Since this is X0, this is now our return value.
+ c.ORR(X0, X0, static_cast<u64>(HaltReason::SupervisorCall));
+
+ // Offset the GuestContext pointer to the HostContext member.
+ // STP has limited range of [-512, 504] which we can't reach otherwise
+ // NB: Due to this all offsets below are from the start of HostContext.
+ c.ADD(X1, X1, offsetof(GuestContext, host_ctx));
+
+ // Reload host TPIDR_EL0 and SP.
+ static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0));
+ c.LDP(X2, X3, X1, offsetof(HostContext, host_sp));
+ c.MOV(SP, X2);
+ c.MSR(oaknut::SystemReg::TPIDR_EL0, X3);
+
+ // Load callee-saved host registers and return to host.
+ static constexpr size_t HOST_REGS_OFF = offsetof(HostContext, host_saved_regs);
+ static constexpr size_t HOST_VREGS_OFF = offsetof(HostContext, host_saved_vregs);
+ c.LDP(X19, X20, X1, HOST_REGS_OFF);
+ c.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64));
+ c.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64));
+ c.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64));
+ c.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64));
+ c.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64));
+ c.LDP(Q8, Q9, X1, HOST_VREGS_OFF);
+ c.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128));
+ c.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128));
+ c.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128));
+ c.RET();
+
+ // Write the post-SVC trampoline address, which will jump back to the guest after restoring its
+ // state.
+ m_trampolines.push_back({c.offset(), module_dest});
+
+ // Host called this location. Save the return address so we can
+ // unwind the stack properly when jumping back.
+ c.MRS(X2, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context));
+ c.ADD(X0, X2, offsetof(GuestContext, host_ctx));
+ c.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64));
+
+ // Reload all guest registers except X30 and PC.
+ // The function also expects 16 bytes of stack already allocated.
+ c.STR(X30, SP, PRE_INDEXED, -16);
+ c.BL(m_load_context);
+ c.LDR(X30, SP, POST_INDEXED, 16);
+
+ // Use X1 as a scratch register to restore X30.
+ c.STR(X1, SP, PRE_INDEXED, -16);
+ c.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
+ c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
+ c.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30);
+ c.LDR(X1, SP, POST_INDEXED, 16);
+
+ // Unlock the context.
+ this->UnlockContext();
+
+ // Jump back to the instruction after the emulated SVC.
+ this->BranchToModule(module_dest);
+
+ // Store PC after call.
+ c.l(pc_after_svc);
+ this->WriteModulePc(module_dest);
+}
+
+void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
+ oaknut::SystemReg src_reg) {
+ // Retrieve emulated TLS register from GuestContext.
+ c.MRS(dest_reg, oaknut::SystemReg::TPIDR_EL0);
+ if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) {
+ c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0));
+ } else {
+ c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0));
+ }
+
+ // Jump back to the instruction after the emulated MRS.
+ this->BranchToModule(module_dest);
+}
+
+void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) {
+ const auto scratch_reg = src_reg.index() == 0 ? X1 : X0;
+ c.STR(scratch_reg, SP, PRE_INDEXED, -16);
+
+ // Save guest value to NativeExecutionParameters::tpidr_el0.
+ c.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0);
+ c.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0));
+
+ // Restore scratch register.
+ c.LDR(scratch_reg, SP, POST_INDEXED, 16);
+
+ // Jump back to the instruction after the emulated MSR.
+ this->BranchToModule(module_dest);
+}
+
+void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) {
+ static Common::Arm64::NativeClock clock{};
+ const auto factor = clock.GetGuestCNTFRQFactor();
+ const auto raw_factor = Common::BitCast<std::array<u64, 2>>(factor);
+
+ const auto use_x2_x3 = dest_reg.index() == 0 || dest_reg.index() == 1;
+ oaknut::XReg scratch0 = use_x2_x3 ? X2 : X0;
+ oaknut::XReg scratch1 = use_x2_x3 ? X3 : X1;
+
+ oaknut::Label factorlo;
+ oaknut::Label factorhi;
+
+ // Save scratches.
+ c.STP(scratch0, scratch1, SP, PRE_INDEXED, -16);
+
+ // Load counter value.
+ c.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0);
+
+ // Load scaling factor.
+ c.LDR(scratch0, factorlo);
+ c.LDR(scratch1, factorhi);
+
+ // Multiply low bits and get result.
+ c.UMULH(scratch0, dest_reg, scratch0);
+
+ // Multiply high bits and add low bit result.
+ c.MADD(dest_reg, dest_reg, scratch1, scratch0);
+
+ // Reload scratches.
+ c.LDP(scratch0, scratch1, SP, POST_INDEXED, 16);
+
+ // Jump back to the instruction after the emulated MRS.
+ this->BranchToModule(module_dest);
+
+ // Scaling factor constant values.
+ c.l(factorlo);
+ c.dx(raw_factor[0]);
+ c.l(factorhi);
+ c.dx(raw_factor[1]);
+}
+
+void Patcher::LockContext() {
+ oaknut::Label retry;
+
+ // Save scratches.
+ c.STP(X0, X1, SP, PRE_INDEXED, -16);
+
+ // Reload lock pointer.
+ c.l(retry);
+ c.CLREX();
+ c.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
+ c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
+
+ static_assert(SpinLockLocked == 0);
+
+ // Load-linked with acquire ordering.
+ c.LDAXR(W1, X0);
+
+ // If the value was SpinLockLocked, clear monitor and retry.
+ c.CBZ(W1, retry);
+
+ // Store-conditional SpinLockLocked with relaxed ordering.
+ c.STXR(W1, WZR, X0);
+
+ // If we failed to store, retry.
+ c.CBNZ(W1, retry);
+
+ // We succeeded! Reload scratches.
+ c.LDP(X0, X1, SP, POST_INDEXED, 16);
+}
+
+void Patcher::UnlockContext() {
+ // Save scratches.
+ c.STP(X0, X1, SP, PRE_INDEXED, -16);
+
+ // Load lock pointer.
+ c.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
+ c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
+
+ // Load SpinLockUnlocked.
+ c.MOV(W1, SpinLockUnlocked);
+
+ // Store value with release ordering.
+ c.STLR(W1, X0);
+
+ // Load scratches.
+ c.LDP(X0, X1, SP, POST_INDEXED, 16);
+}
+
+} // namespace Core::NCE
diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h
new file mode 100644
index 000000000..c6d1608c1
--- /dev/null
+++ b/src/core/arm/nce/patcher.h
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+#include <unordered_map>
+#include <vector>
+#include <oaknut/code_block.hpp>
+#include <oaknut/oaknut.hpp>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/code_set.h"
+#include "core/hle/kernel/k_typed_address.h"
+#include "core/hle/kernel/physical_memory.h"
+
+namespace Core::NCE {
+
+enum class PatchMode : u32 {
+ None,
+ PreText, ///< Patch section is inserted before .text
+ PostData, ///< Patch section is inserted after .data
+};
+
+using ModuleTextAddress = u64;
+using PatchTextAddress = u64;
+using EntryTrampolines = std::unordered_map<ModuleTextAddress, PatchTextAddress>;
+
+class Patcher {
+public:
+ explicit Patcher();
+ ~Patcher();
+
+ void PatchText(const Kernel::PhysicalMemory& program_image,
+ const Kernel::CodeSet::Segment& code);
+ void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
+ Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
+ size_t GetSectionSize() const noexcept;
+
+ [[nodiscard]] PatchMode GetPatchMode() const noexcept {
+ return mode;
+ }
+
+private:
+ using ModuleDestLabel = uintptr_t;
+
+ struct Trampoline {
+ ptrdiff_t patch_offset;
+ uintptr_t module_offset;
+ };
+
+ void WriteLoadContext();
+ void WriteSaveContext();
+ void LockContext();
+ void UnlockContext();
+ void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id);
+ void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
+ oaknut::SystemReg src_reg);
+ void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg);
+ void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg);
+
+private:
+ void BranchToPatch(uintptr_t module_dest) {
+ m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
+ }
+
+ void BranchToModule(uintptr_t module_dest) {
+ m_branch_to_module_relocations.push_back({c.offset(), module_dest});
+ c.dw(0);
+ }
+
+ void WriteModulePc(uintptr_t module_dest) {
+ m_write_module_pc_relocations.push_back({c.offset(), module_dest});
+ c.dx(0);
+ }
+
+private:
+ // List of patch instructions we have generated.
+ std::vector<u32> m_patch_instructions{};
+
+ // Relocation type for relative branch from module to patch.
+ struct Relocation {
+ ptrdiff_t patch_offset; ///< Offset in bytes from the start of the patch section.
+ uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
+ };
+
+ oaknut::VectorCodeGenerator c;
+ std::vector<Trampoline> m_trampolines;
+ std::vector<Relocation> m_branch_to_patch_relocations{};
+ std::vector<Relocation> m_branch_to_module_relocations{};
+ std::vector<Relocation> m_write_module_pc_relocations{};
+ std::vector<ModuleTextAddress> m_exclusives{};
+ oaknut::Label m_save_context{};
+ oaknut::Label m_load_context{};
+ PatchMode mode{PatchMode::None};
+};
+
+} // namespace Core::NCE
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index 980bb97f9..151eb3870 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -211,6 +211,8 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) {
system.GPU().ObtainContext();
}
+ system.ArmInterface(core).Initialize();
+
auto& kernel = system.Kernel();
auto& scheduler = *kernel.CurrentScheduler();
auto* thread = scheduler.GetSchedulerCurrentThread();
diff --git a/src/core/device_memory.cpp b/src/core/device_memory.cpp
index de3f8ef8f..1aea56a99 100644
--- a/src/core/device_memory.cpp
+++ b/src/core/device_memory.cpp
@@ -6,7 +6,7 @@
namespace Core {
-#ifdef ANDROID
+#ifdef HAS_NCE
constexpr size_t VirtualReserveSize = 1ULL << 38;
#else
constexpr size_t VirtualReserveSize = 1ULL << 39;
@@ -15,6 +15,7 @@ constexpr size_t VirtualReserveSize = 1ULL << 39;
DeviceMemory::DeviceMemory()
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
VirtualReserveSize} {}
+
DeviceMemory::~DeviceMemory() = default;
} // namespace Core
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 0bca05587..cc7af2ea3 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -429,10 +429,6 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
LOG_DEBUG(Loader, "{}", log_string);
}
- if (base_romfs == nullptr) {
- return base_romfs;
- }
-
auto romfs = base_romfs;
// Game Updates
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 1eb1f439a..6de2103a0 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -3,6 +3,7 @@
#include <memory>
+#include "common/assert.h"
#include "common/common_types.h"
#include "common/string_util.h"
#include "common/swap.h"
@@ -101,24 +102,30 @@ void ProcessDirectory(const VirtualFile& file, std::size_t dir_offset, std::size
} // Anonymous namespace
VirtualDir ExtractRomFS(VirtualFile file) {
+ auto root_container = std::make_shared<VectorVfsDirectory>();
+ if (!file) {
+ return root_container;
+ }
+
RomFSHeader header{};
- if (file->ReadObject(&header) != sizeof(RomFSHeader))
- return nullptr;
+ if (file->ReadObject(&header) != sizeof(RomFSHeader)) {
+ return root_container;
+ }
- if (header.header_size != sizeof(RomFSHeader))
- return nullptr;
+ if (header.header_size != sizeof(RomFSHeader)) {
+ return root_container;
+ }
const u64 file_offset = header.file_meta.offset;
const u64 dir_offset = header.directory_meta.offset;
- auto root_container = std::make_shared<VectorVfsDirectory>();
-
ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root_container);
if (auto root = root_container->GetSubdirectory(""); root) {
return std::make_shared<CachedVfsDirectory>(std::move(root));
}
+ ASSERT(false);
return nullptr;
}
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 1bc07dae5..35e149905 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -22,7 +22,7 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi
: content_provider{provider}, filesystem_controller{controller} {
// Load the RomFS from the app
if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
- LOG_ERROR(Service_FS, "Unable to read RomFS!");
+ LOG_WARNING(Service_FS, "Unable to read base RomFS");
}
updatable = app_loader.IsRomFSUpdatable();
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index a72df034e..c7b48a58d 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -167,6 +167,11 @@ protected:
*/
std::pair<f32, f32> MapToTouchScreen(u32 framebuffer_x, u32 framebuffer_y) const;
+ /**
+ * Clip the provided coordinates to be inside the touchscreen area.
+ */
+ std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
+
WindowSystemInfo window_info;
bool strict_context_required = false;
@@ -181,11 +186,6 @@ private:
// By default, ignore this request and do nothing.
}
- /**
- * Clip the provided coordinates to be inside the touchscreen area.
- */
- std::pair<u32, u32> ClipToTouchScreen(u32 new_x, u32 new_y) const;
-
Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
u32 client_area_width; ///< Current client width, should be set by window impl.
diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h
index af1af2b78..4d2d0098e 100644
--- a/src/core/hle/kernel/code_set.h
+++ b/src/core/hle/kernel/code_set.h
@@ -75,12 +75,26 @@ struct CodeSet final {
return segments[2];
}
+#ifdef HAS_NCE
+ Segment& PatchSegment() {
+ return patch_segment;
+ }
+
+ const Segment& PatchSegment() const {
+ return patch_segment;
+ }
+#endif
+
/// The overall data that backs this code set.
Kernel::PhysicalMemory memory;
/// The segments that comprise this code set.
std::array<Segment, 3> segments;
+#ifdef HAS_NCE
+ Segment patch_segment;
+#endif
+
/// The entry point address for this code set.
KProcessAddress entrypoint = 0;
};
diff --git a/src/core/hle/kernel/k_address_space_info.cpp b/src/core/hle/kernel/k_address_space_info.cpp
index 32173e52b..23258071e 100644
--- a/src/core/hle/kernel/k_address_space_info.cpp
+++ b/src/core/hle/kernel/k_address_space_info.cpp
@@ -25,8 +25,8 @@ constexpr std::array<KAddressSpaceInfo, 13> AddressSpaceInfos{{
{ .bit_width = 36, .address = 2_GiB , .size = 64_GiB - 2_GiB , .type = KAddressSpaceInfo::Type::MapLarge, },
{ .bit_width = 36, .address = Size_Invalid, .size = 8_GiB , .type = KAddressSpaceInfo::Type::Heap, },
{ .bit_width = 36, .address = Size_Invalid, .size = 6_GiB , .type = KAddressSpaceInfo::Type::Alias, },
-#ifdef ANDROID
- // With Android, we use a 38-bit address space due to memory limitations. This should (safely) truncate ASLR region.
+#ifdef HAS_NCE
+ // With NCE, we use a 38-bit address space due to memory limitations. This should (safely) truncate ASLR region.
{ .bit_width = 39, .address = 128_MiB , .size = 256_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, },
#else
{ .bit_width = 39, .address = 128_MiB , .size = 512_GiB - 128_MiB, .type = KAddressSpaceInfo::Type::Map39Bit, },
diff --git a/src/core/hle/kernel/k_page_table_base.cpp b/src/core/hle/kernel/k_page_table_base.cpp
index 47dc8fd35..6691586ed 100644
--- a/src/core/hle/kernel/k_page_table_base.cpp
+++ b/src/core/hle/kernel/k_page_table_base.cpp
@@ -88,6 +88,22 @@ Result FlushDataCache(AddressType addr, u64 size) {
R_SUCCEED();
}
+constexpr Common::MemoryPermission ConvertToMemoryPermission(KMemoryPermission perm) {
+ Common::MemoryPermission perms{};
+ if (True(perm & KMemoryPermission::UserRead)) {
+ perms |= Common::MemoryPermission::Read;
+ }
+ if (True(perm & KMemoryPermission::UserWrite)) {
+ perms |= Common::MemoryPermission::Write;
+ }
+#ifdef HAS_NCE
+ if (True(perm & KMemoryPermission::UserExecute)) {
+ perms |= Common::MemoryPermission::Execute;
+ }
+#endif
+ return perms;
+}
+
} // namespace
void KPageTableBase::MemoryRange::Open() {
@@ -170,7 +186,8 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
KMemoryManager::Pool pool, KProcessAddress code_address,
size_t code_size, KSystemResource* system_resource,
KResourceLimit* resource_limit,
- Core::Memory::Memory& memory) {
+ Core::Memory::Memory& memory,
+ KProcessAddress aslr_space_start) {
// Calculate region extents.
const size_t as_width = GetAddressSpaceWidth(as_type);
const KProcessAddress start = 0;
@@ -211,7 +228,8 @@ Result KPageTableBase::InitializeForProcess(Svc::CreateProcessFlag as_type, bool
heap_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Heap);
stack_region_size = GetSpaceSize(KAddressSpaceInfo::Type::Stack);
kernel_map_region_size = GetSpaceSize(KAddressSpaceInfo::Type::MapSmall);
- m_code_region_start = GetSpaceStart(KAddressSpaceInfo::Type::Map39Bit);
+ m_code_region_start = m_address_space_start + aslr_space_start +
+ GetSpaceStart(KAddressSpaceInfo::Type::Map39Bit);
m_code_region_end = m_code_region_start + GetSpaceSize(KAddressSpaceInfo::Type::Map39Bit);
m_alias_code_region_start = m_code_region_start;
m_alias_code_region_end = m_code_region_end;
@@ -5643,7 +5661,8 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
case OperationType::Map: {
ASSERT(virt_addr != 0);
ASSERT(Common::IsAligned(GetInteger(virt_addr), PageSize));
- m_memory->MapMemoryRegion(*m_impl, virt_addr, num_pages * PageSize, phys_addr);
+ m_memory->MapMemoryRegion(*m_impl, virt_addr, num_pages * PageSize, phys_addr,
+ ConvertToMemoryPermission(properties.perm));
// Open references to pages, if we should.
if (this->IsHeapPhysicalAddress(phys_addr)) {
@@ -5658,8 +5677,11 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
}
case OperationType::ChangePermissions:
case OperationType::ChangePermissionsAndRefresh:
- case OperationType::ChangePermissionsAndRefreshAndFlush:
+ case OperationType::ChangePermissionsAndRefreshAndFlush: {
+ m_memory->ProtectRegion(*m_impl, virt_addr, num_pages * PageSize,
+ ConvertToMemoryPermission(properties.perm));
R_SUCCEED();
+ }
default:
UNREACHABLE();
}
@@ -5687,7 +5709,8 @@ Result KPageTableBase::Operate(PageLinkedList* page_list, KProcessAddress virt_a
const size_t size{node.GetNumPages() * PageSize};
// Map the pages.
- m_memory->MapMemoryRegion(*m_impl, virt_addr, size, node.GetAddress());
+ m_memory->MapMemoryRegion(*m_impl, virt_addr, size, node.GetAddress(),
+ ConvertToMemoryPermission(properties.perm));
virt_addr += size;
}
diff --git a/src/core/hle/kernel/k_page_table_base.h b/src/core/hle/kernel/k_page_table_base.h
index ee2c41e67..556d230b3 100644
--- a/src/core/hle/kernel/k_page_table_base.h
+++ b/src/core/hle/kernel/k_page_table_base.h
@@ -235,7 +235,8 @@ public:
bool enable_device_address_space_merge, bool from_back,
KMemoryManager::Pool pool, KProcessAddress code_address,
size_t code_size, KSystemResource* system_resource,
- KResourceLimit* resource_limit, Core::Memory::Memory& memory);
+ KResourceLimit* resource_limit, Core::Memory::Memory& memory,
+ KProcessAddress aslr_space_start);
void Finalize();
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 3cfb414e5..6c29eb72c 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -300,7 +300,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa
False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
params.code_address, params.code_num_pages * PageSize,
- m_system_resource, res_limit, this->GetMemory()));
+ m_system_resource, res_limit, this->GetMemory(), 0));
}
ON_RESULT_FAILURE_2 {
m_page_table.Finalize();
@@ -332,7 +332,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params, const KPa
Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
std::span<const u32> user_caps, KResourceLimit* res_limit,
- KMemoryManager::Pool pool) {
+ KMemoryManager::Pool pool, KProcessAddress aslr_space_start) {
ASSERT(res_limit != nullptr);
// Set members.
@@ -393,7 +393,7 @@ Result KProcess::Initialize(const Svc::CreateProcessParameter& params,
False(params.flags & Svc::CreateProcessFlag::DisableDeviceAddressSpaceMerge);
R_TRY(m_page_table.Initialize(as_type, enable_aslr, enable_das_merge, !enable_aslr, pool,
params.code_address, code_size, m_system_resource, res_limit,
- this->GetMemory()));
+ this->GetMemory(), aslr_space_start));
}
ON_RESULT_FAILURE_2 {
m_page_table.Finalize();
@@ -1128,7 +1128,7 @@ KProcess::KProcess(KernelCore& kernel)
KProcess::~KProcess() = default;
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
- bool is_hbl) {
+ KProcessAddress aslr_space_start, bool is_hbl) {
// Create a resource limit for the process.
const auto physical_memory_size =
m_kernel.MemoryManager().GetSize(Kernel::KMemoryManager::Pool::Application);
@@ -1179,7 +1179,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
.name = {},
.version = {},
.program_id = metadata.GetTitleID(),
- .code_address = code_address,
+ .code_address = code_address + GetInteger(aslr_space_start),
.code_num_pages = static_cast<s32>(code_size / PageSize),
.flags = flag,
.reslimit = Svc::InvalidHandle,
@@ -1193,7 +1193,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
// Initialize for application process.
R_TRY(this->Initialize(params, metadata.GetKernelCapabilities(), res_limit,
- KMemoryManager::Pool::Application));
+ KMemoryManager::Pool::Application, aslr_space_start));
// Assign remaining properties.
m_is_hbl = is_hbl;
@@ -1214,6 +1214,17 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute);
ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read);
ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
+
+#ifdef HAS_NCE
+ if (Settings::IsNceEnabled()) {
+ auto& buffer = m_kernel.System().DeviceMemory().buffer;
+ const auto& code = code_set.CodeSegment();
+ const auto& patch = code_set.PatchSegment();
+ buffer.Protect(GetInteger(base_addr + code.addr), code.size, true, true, true);
+ buffer.Protect(GetInteger(base_addr + patch.addr), patch.size, true, true, true);
+ ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None);
+ }
+#endif
}
bool KProcess::InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type) {
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 8339465fd..d8cd0fdde 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -120,6 +120,9 @@ private:
std::atomic<s64> m_num_ipc_messages{};
std::atomic<s64> m_num_ipc_replies{};
std::atomic<s64> m_num_ipc_receives{};
+#ifdef HAS_NCE
+ std::unordered_map<u64, u64> m_post_handlers{};
+#endif
private:
Result StartTermination();
@@ -150,7 +153,8 @@ public:
std::span<const u32> caps, KResourceLimit* res_limit,
KMemoryManager::Pool pool, bool immortal);
Result Initialize(const Svc::CreateProcessParameter& params, std::span<const u32> user_caps,
- KResourceLimit* res_limit, KMemoryManager::Pool pool);
+ KResourceLimit* res_limit, KMemoryManager::Pool pool,
+ KProcessAddress aslr_space_start);
void Exit();
const char* GetName() const {
@@ -466,6 +470,12 @@ public:
static void Switch(KProcess* cur_process, KProcess* next_process);
+#ifdef HAS_NCE
+ std::unordered_map<u64, u64>& GetPostHandlers() noexcept {
+ return m_post_handlers;
+ }
+#endif
+
public:
// Attempts to insert a watchpoint into a free slot. Returns false if none are available.
bool InsertWatchpoint(KProcessAddress addr, u64 size, DebugWatchpointType type);
@@ -479,7 +489,7 @@ public:
public:
Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
- bool is_hbl);
+ KProcessAddress aslr_space_start, bool is_hbl);
void LoadModule(CodeSet code_set, KProcessAddress base_addr);
diff --git a/src/core/hle/kernel/k_process_page_table.h b/src/core/hle/kernel/k_process_page_table.h
index b7ae5abd0..9e40f68bc 100644
--- a/src/core/hle/kernel/k_process_page_table.h
+++ b/src/core/hle/kernel/k_process_page_table.h
@@ -23,10 +23,11 @@ public:
Result Initialize(Svc::CreateProcessFlag as_type, bool enable_aslr, bool enable_das_merge,
bool from_back, KMemoryManager::Pool pool, KProcessAddress code_address,
size_t code_size, KSystemResource* system_resource,
- KResourceLimit* resource_limit, Core::Memory::Memory& memory) {
- R_RETURN(m_page_table.InitializeForProcess(as_type, enable_aslr, enable_das_merge,
- from_back, pool, code_address, code_size,
- system_resource, resource_limit, memory));
+ KResourceLimit* resource_limit, Core::Memory::Memory& memory,
+ KProcessAddress aslr_space_start) {
+ R_RETURN(m_page_table.InitializeForProcess(
+ as_type, enable_aslr, enable_das_merge, from_back, pool, code_address, code_size,
+ system_resource, resource_limit, memory, aslr_space_start));
}
void Finalize() {
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index e1f80b04f..e9ca5dfca 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -655,6 +655,21 @@ public:
return m_stack_top;
}
+public:
+ // TODO: This shouldn't be defined in kernel namespace
+ struct NativeExecutionParameters {
+ u64 tpidr_el0{};
+ u64 tpidrro_el0{};
+ void* native_context{};
+ std::atomic<u32> lock{1};
+ bool is_running{};
+ u32 magic{Common::MakeMagic('Y', 'U', 'Z', 'U')};
+ };
+
+ NativeExecutionParameters& GetNativeExecutionParameters() {
+ return m_native_execution_parameters;
+ }
+
private:
KThread* RemoveWaiterByKey(bool* out_has_waiters, KProcessAddress key,
bool is_kernel_address_key);
@@ -914,6 +929,7 @@ private:
ThreadWaitReasonForDebugging m_wait_reason_for_debugging{};
uintptr_t m_argument{};
KProcessAddress m_stack_top{};
+ NativeExecutionParameters m_native_execution_parameters{};
public:
using ConditionVariableThreadTreeType = ConditionVariableThreadTree;
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
index 5ee869fa2..073039825 100644
--- a/src/core/hle/kernel/physical_core.cpp
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/settings.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
+#ifdef HAS_NCE
+#include "core/arm/nce/arm_nce.h"
+#endif
#include "core/core.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/kernel.h"
@@ -14,7 +18,8 @@ PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system, KSchedu
: m_core_index{core_index}, m_system{system}, m_scheduler{scheduler} {
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
// TODO(bunnei): Initialization relies on a core being available. We may later replace this with
- // a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager.
+ // an NCE interface or a 32-bit instance of Dynarmic. This should be abstracted out to a CPU
+ // manager.
auto& kernel = system.Kernel();
m_arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
system, kernel.IsMulticore(),
@@ -28,6 +33,13 @@ PhysicalCore::PhysicalCore(std::size_t core_index, Core::System& system, KSchedu
PhysicalCore::~PhysicalCore() = default;
void PhysicalCore::Initialize(bool is_64_bit) {
+#if defined(HAS_NCE)
+ if (Settings::IsNceEnabled()) {
+ m_arm_interface = std::make_unique<Core::ARM_NCE>(m_system, m_system.Kernel().IsMulticore(),
+ m_core_index);
+ return;
+ }
+#endif
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
auto& kernel = m_system.Kernel();
if (!is_64_bit) {
diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp
index 9d1960cb7..3906c0fa4 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.cpp
+++ b/src/core/hle/service/am/applets/applet_cabinet.cpp
@@ -131,7 +131,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
nfp_device->DeleteApplicationArea();
break;
case Service::NFP::CabinetMode::StartRestorer:
- nfp_device->RestoreAmiibo();
+ nfp_device->Restore();
break;
case Service::NFP::CabinetMode::StartFormatter:
nfp_device->Format();
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index 3bcf0ee9f..fcd973414 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -16,7 +16,8 @@ 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_} {
+ : ControllerBase{hid_core_}, 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(
@@ -95,8 +96,8 @@ void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
if (id < active_fingers_count) {
const auto& [active_x, active_y] = active_fingers[id].position;
touch_entry.position = {
- .x = static_cast<u16>(active_x * Layout::ScreenUndocked::Width),
- .y = static_cast<u16>(active_y * Layout::ScreenUndocked::Height),
+ .x = static_cast<u16>(active_x * static_cast<float>(touchscreen_width)),
+ .y = static_cast<u16>(active_y * static_cast<float>(touchscreen_height)),
};
touch_entry.diameter_x = Settings::values.touchscreen.diameter_x;
touch_entry.diameter_y = Settings::values.touchscreen.diameter_y;
@@ -120,4 +121,9 @@ void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
shared_memory->touch_screen_lifo.WriteNextEntry(next_state);
}
+void TouchScreen::SetTouchscreenDimensions(u32 width, u32 height) {
+ touchscreen_width = width;
+ touchscreen_height = height;
+}
+
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index cd342ce91..79f026a81 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -28,6 +28,8 @@ public:
// When the controller is requesting an update for the shared memory
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
+ void SetTouchscreenDimensions(u32 width, u32 height);
+
private:
static constexpr std::size_t MAX_FINGERS = 16;
@@ -53,5 +55,7 @@ private:
Core::HID::EmulatedConsole* console = nullptr;
std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers{};
+ u32 touchscreen_width;
+ u32 touchscreen_height;
};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp
index 583142e35..a7d1578d9 100644
--- a/src/core/hle/service/hid/hid_server.cpp
+++ b/src/core/hle/service/hid/hid_server.cpp
@@ -208,6 +208,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r
{1001, &IHidServer::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
{1002, &IHidServer::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
{1003, &IHidServer::IsFirmwareUpdateNeededForNotification, "IsFirmwareUpdateNeededForNotification"},
+ {1004, &IHidServer::SetTouchScreenResolution, "SetTouchScreenResolution"},
{2000, nullptr, "ActivateDigitizer"},
};
// clang-format on
@@ -2363,6 +2364,21 @@ void IHidServer::IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx) {
rb.Push(false);
}
+void IHidServer::SetTouchScreenResolution(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto width{rp.Pop<u32>()};
+ const auto height{rp.Pop<u32>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ GetResourceManager()->GetTouchScreen()->SetTouchscreenDimensions(width, height);
+
+ LOG_INFO(Service_HID, "called, width={}, height={}, applet_resource_user_id={}", width, height,
+ applet_resource_user_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
std::shared_ptr<ResourceManager> IHidServer::GetResourceManager() {
resource_manager->Initialize();
return resource_manager;
diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h
index eb2e8e7f4..cc7c4ebdd 100644
--- a/src/core/hle/service/hid/hid_server.h
+++ b/src/core/hle/service/hid/hid_server.h
@@ -141,6 +141,7 @@ private:
void GetNpadCommunicationMode(HLERequestContext& ctx);
void SetTouchScreenConfiguration(HLERequestContext& ctx);
void IsFirmwareUpdateNeededForNotification(HLERequestContext& ctx);
+ void SetTouchScreenResolution(HLERequestContext& ctx);
std::shared_ptr<ResourceManager> resource_manager;
std::shared_ptr<HidFirmwareSettings> firmware_settings;
diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp
index a019cc9f7..c27646fcf 100644
--- a/src/core/hle/service/mii/types/ver3_store_data.cpp
+++ b/src/core/hle/service/mii/types/ver3_store_data.cpp
@@ -98,7 +98,7 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
}
void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
- version = 1;
+ version = 3;
mii_information.gender.Assign(static_cast<u8>(store_data.GetGender()));
mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor()));
height = store_data.GetHeight();
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index e7a00deb3..f97e5b44c 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -401,6 +401,12 @@ Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& time
}
Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) {
+ bool is_corrupted = false;
+
+ if (model_type != NFP::ModelType::Amiibo) {
+ return ResultInvalidArgument;
+ }
+
if (device_state != DeviceState::TagFound) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return ResultWrongDeviceState;
@@ -420,26 +426,32 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
if (is_plain_amiibo) {
std::vector<u8> data(sizeof(NFP::NTAG215File));
memcpy(data.data(), &tag_data, sizeof(tag_data));
- WriteBackupData(tag_data.uid, data);
-
- device_state = DeviceState::TagMounted;
- mount_target = mount_target_;
- return ResultSuccess;
}
- if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
- bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
- LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup);
- return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
+ if (!is_plain_amiibo && !NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
+ LOG_ERROR(Service_NFP, "Can't decode amiibo");
+ is_corrupted = true;
}
- std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
- memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
- WriteBackupData(encrypted_tag_data.uuid, data);
+ if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
+ LOG_ERROR(Service_NFP, "Invalid mii data");
+ is_corrupted = true;
+ }
device_state = DeviceState::TagMounted;
mount_target = mount_target_;
+ if (!is_corrupted && mount_target != NFP::MountTarget::Rom) {
+ std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
+ memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
+ WriteBackupData(encrypted_tag_data.uuid, data);
+ }
+
+ if (is_corrupted && mount_target != NFP::MountTarget::Rom) {
+ bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
+ return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
+ }
+
return ResultSuccess;
}
@@ -606,6 +618,17 @@ Result NfcDevice::Restore() {
}
}
+ // Restore mii data in case is corrupted by previous instances of yuzu
+ if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
+ LOG_ERROR(Service_NFP, "Regenerating mii data");
+ Mii::StoreData new_mii{};
+ new_mii.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
+ new_mii.SetNickname({u'y', u'u', u'z', u'u', u'\0'});
+
+ tag_data.owner_mii.BuildFromStoreData(new_mii);
+ tag_data.mii_extension.SetFromStoreData(new_mii);
+ }
+
// Overwrite tag contents with backup and mount the tag
tag_data = temporary_tag_data;
encrypted_tag_data = temporary_encrypted_tag_data;
@@ -851,25 +874,6 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
return Flush();
}
-Result NfcDevice::RestoreAmiibo() {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- if (device_state == DeviceState::TagRemoved) {
- return ResultTagRemoved;
- }
- return ResultWrongDeviceState;
- }
-
- if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
- LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return ResultWrongDeviceState;
- }
-
- // TODO: Load amiibo from backup on system
- LOG_ERROR(Service_NFP, "Not Implemented");
- return ResultSuccess;
-}
-
Result NfcDevice::Format() {
Result result = ResultSuccess;
@@ -877,7 +881,9 @@ Result NfcDevice::Format() {
result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
}
- if (result.IsError()) {
+ // We are formatting all data. Corruption is not an issue.
+ if (result.IsError() &&
+ (result != ResultCorruptedData && result != ResultCorruptedDataWithBackup)) {
return result;
}
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
index 0ed1ff34c..d8efe25ec 100644
--- a/src/core/hle/service/nfc/common/device.h
+++ b/src/core/hle/service/nfc/common/device.h
@@ -68,7 +68,6 @@ public:
Result DeleteRegisterInfo();
Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info);
- Result RestoreAmiibo();
Result Format();
Result OpenApplicationArea(u32 access_id);
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index f5edfdc8b..48304e6d1 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -34,7 +34,9 @@ Result GetFirmwareVersionImpl(FirmwareVersionFormat& out_firmware, Core::System&
nca = bis_system->GetEntry(FirmwareVersionSystemDataId, FileSys::ContentRecordType::Data);
}
if (nca) {
- romfs = FileSys::ExtractRomFS(nca->GetRomFS());
+ if (auto nca_romfs = nca->GetRomFS(); nca_romfs) {
+ romfs = FileSys::ExtractRomFS(nca_romfs);
+ }
}
if (!romfs) {
romfs = FileSys::ExtractRomFS(
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 5c36b71e5..60ee78e89 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -3,6 +3,7 @@
#include <cstring>
#include "common/logging/log.h"
+#include "common/settings.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
@@ -14,6 +15,10 @@
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nso.h"
+#ifdef HAS_NCE
+#include "core/arm/nce/patcher.h"
+#endif
+
namespace Loader {
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
@@ -124,21 +129,43 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
metadata.Print();
- const auto static_modules = {"rtld", "main", "subsdk0", "subsdk1", "subsdk2",
- "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7",
- "subsdk8", "subsdk9", "sdk"};
+ // Enable NCE only for programs with 39-bit address space.
+ const bool is_39bit =
+ metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit;
+ Settings::SetNceEnabled(is_39bit);
+
+ const std::array static_modules = {"rtld", "main", "subsdk0", "subsdk1", "subsdk2",
+ "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7",
+ "subsdk8", "subsdk9", "sdk"};
- // Use the NSO module loader to figure out the code layout
std::size_t code_size{};
- for (const auto& module : static_modules) {
+
+ // Define an nce patch context for each potential module.
+#ifdef HAS_NCE
+ std::array<Core::NCE::Patcher, 13> module_patchers;
+#endif
+
+ const auto GetPatcher = [&](size_t i) -> Core::NCE::Patcher* {
+#ifdef HAS_NCE
+ if (Settings::IsNceEnabled()) {
+ return &module_patchers[i];
+ }
+#endif
+ return nullptr;
+ };
+
+ // Use the NSO module loader to figure out the code layout
+ for (size_t i = 0; i < static_modules.size(); i++) {
+ const auto& module = static_modules[i];
const FileSys::VirtualFile module_file{dir->GetFile(module)};
if (!module_file) {
continue;
}
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
- const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
- process, system, *module_file, code_size, should_pass_arguments, false);
+ const auto tentative_next_load_addr =
+ AppLoader_NSO::LoadModule(process, system, *module_file, code_size,
+ should_pass_arguments, false, {}, GetPatcher(i));
if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
@@ -146,8 +173,18 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
code_size = *tentative_next_load_addr;
}
+ // Enable direct memory mapping in case of NCE.
+ const u64 fastmem_base = [&]() -> size_t {
+ if (Settings::IsNceEnabled()) {
+ auto& buffer = system.DeviceMemory().buffer;
+ buffer.EnableDirectMappedAddress();
+ return reinterpret_cast<u64>(buffer.VirtualBasePointer());
+ }
+ return 0;
+ }();
+
// Setup the process code layout
- if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) {
+ if (process.LoadFromMetadata(metadata, code_size, fastmem_base, is_hbl).IsError()) {
return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
}
@@ -157,7 +194,8 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
VAddr next_load_addr{base_address};
const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(),
system.GetContentProvider()};
- for (const auto& module : static_modules) {
+ for (size_t i = 0; i < static_modules.size(); i++) {
+ const auto& module = static_modules[i];
const FileSys::VirtualFile module_file{dir->GetFile(module)};
if (!module_file) {
continue;
@@ -165,15 +203,16 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
const VAddr load_addr{next_load_addr};
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
- const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
- process, system, *module_file, load_addr, should_pass_arguments, true, pm);
+ const auto tentative_next_load_addr =
+ AppLoader_NSO::LoadModule(process, system, *module_file, load_addr,
+ should_pass_arguments, true, pm, GetPatcher(i));
if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
next_load_addr = *tentative_next_load_addr;
modules.insert_or_assign(load_addr, module);
- LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
+ LOG_DEBUG(Loader, "loaded module {} @ {:#X}", module, load_addr);
}
// Find the RomFS by searching for a ".romfs" file in this directory
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index bf56a08b4..cd6982921 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -91,7 +91,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
// Setup the process code layout
if (process
- .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
+ .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), 0,
+ false)
.IsError()) {
return {ResultStatus::ErrorNotInitialized, {}};
}
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index 4feb6968a..814407535 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -74,10 +74,8 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
return load_result;
}
- if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) {
- system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
- *this, system.GetContentProvider(), system.GetFileSystemController()));
- }
+ system.GetFileSystemController().RegisterRomFS(std::make_unique<FileSys::RomFSFactory>(
+ *this, system.GetContentProvider(), system.GetFileSystemController()));
is_loaded = true;
return load_result;
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 69f1a54ed..e74697cda 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -22,6 +22,10 @@
#include "core/loader/nso.h"
#include "core/memory.h"
+#ifdef HAS_NCE
+#include "core/arm/nce/patcher.h"
+#endif
+
namespace Loader {
struct NroSegmentHeader {
@@ -139,7 +143,8 @@ static constexpr u32 PageAlignSize(u32 size) {
return static_cast<u32>((size + Core::Memory::YUZU_PAGEMASK) & ~Core::Memory::YUZU_PAGEMASK);
}
-static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) {
+static bool LoadNroImpl(Core::System& system, Kernel::KProcess& process,
+ const std::vector<u8>& data) {
if (data.size() < sizeof(NroHeader)) {
return {};
}
@@ -194,14 +199,61 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
codeset.DataSegment().size += bss_size;
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
+ size_t image_size = program_image.size();
+
+#ifdef HAS_NCE
+ const auto& code = codeset.CodeSegment();
+
+ // NROs always have a 39-bit address space.
+ Settings::SetNceEnabled(true);
+
+ // Create NCE patcher
+ Core::NCE::Patcher patch{};
+
+ if (Settings::IsNceEnabled()) {
+ // Patch SVCs and MRS calls in the guest code
+ patch.PatchText(program_image, code);
+
+ // We only support PostData patching for NROs.
+ ASSERT(patch.GetPatchMode() == Core::NCE::PatchMode::PostData);
+
+ // Update patch section.
+ auto& patch_segment = codeset.PatchSegment();
+ patch_segment.addr = image_size;
+ patch_segment.size = static_cast<u32>(patch.GetSectionSize());
+
+ // Add patch section size to the module size.
+ image_size += patch_segment.size;
+ }
+#endif
+
+ // Enable direct memory mapping in case of NCE.
+ const u64 fastmem_base = [&]() -> size_t {
+ if (Settings::IsNceEnabled()) {
+ auto& buffer = system.DeviceMemory().buffer;
+ buffer.EnableDirectMappedAddress();
+ return reinterpret_cast<u64>(buffer.VirtualBasePointer());
+ }
+ return 0;
+ }();
// Setup the process code layout
if (process
- .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
+ .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), image_size, fastmem_base,
+ false)
.IsError()) {
return false;
}
+ // Relocate code patch and copy to the program_image if running under NCE.
+ // This needs to be after LoadFromMetadata so we can use the process entry point.
+#ifdef HAS_NCE
+ if (Settings::IsNceEnabled()) {
+ patch.RelocateAndCopy(process.GetEntryPoint(), code, program_image,
+ &process.GetPostHandlers());
+ }
+#endif
+
// Load codeset for current process
codeset.memory = std::move(program_image);
process.LoadModule(std::move(codeset), process.GetEntryPoint());
@@ -209,8 +261,9 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
return true;
}
-bool AppLoader_NRO::LoadNro(Kernel::KProcess& process, const FileSys::VfsFile& nro_file) {
- return LoadNroImpl(process, nro_file.ReadAllBytes());
+bool AppLoader_NRO::LoadNro(Core::System& system, Kernel::KProcess& process,
+ const FileSys::VfsFile& nro_file) {
+ return LoadNroImpl(system, process, nro_file.ReadAllBytes());
}
AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::System& system) {
@@ -218,7 +271,7 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::KProcess& process, Core::S
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
- if (!LoadNro(process, *file)) {
+ if (!LoadNro(system, process, *file)) {
return {ResultStatus::ErrorLoadingNRO, {}};
}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 8de6eebc6..d2928cba0 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -54,7 +54,7 @@ public:
bool IsRomFSUpdatable() const override;
private:
- bool LoadNro(Kernel::KProcess& process, const FileSys::VfsFile& nro_file);
+ bool LoadNro(Core::System& system, Kernel::KProcess& process, const FileSys::VfsFile& nro_file);
std::vector<u8> icon_data;
std::unique_ptr<FileSys::NACP> nacp;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 1350da8dc..b053a0d14 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -20,6 +20,10 @@
#include "core/loader/nso.h"
#include "core/memory.h"
+#ifdef HAS_NCE
+#include "core/arm/nce/patcher.h"
+#endif
+
namespace Loader {
namespace {
struct MODHeader {
@@ -72,7 +76,8 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& in_file) {
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::System& system,
const FileSys::VfsFile& nso_file, VAddr load_base,
bool should_pass_arguments, bool load_into_process,
- std::optional<FileSys::PatchManager> pm) {
+ std::optional<FileSys::PatchManager> pm,
+ Core::NCE::Patcher* patch) {
if (nso_file.GetSize() < sizeof(NSOHeader)) {
return std::nullopt;
}
@@ -86,6 +91,16 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
return std::nullopt;
}
+ // Allocate some space at the beginning if we are patching in PreText mode.
+ const size_t module_start = [&]() -> size_t {
+#ifdef HAS_NCE
+ if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
+ return patch->GetSectionSize();
+ }
+#endif
+ return 0;
+ }();
+
// Build program image
Kernel::CodeSet codeset;
Kernel::PhysicalMemory program_image;
@@ -95,11 +110,12 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
if (nso_header.IsSegmentCompressed(i)) {
data = DecompressSegment(data, nso_header.segments[i]);
}
- program_image.resize(nso_header.segments[i].location + static_cast<u32>(data.size()));
- std::memcpy(program_image.data() + nso_header.segments[i].location, data.data(),
- data.size());
- codeset.segments[i].addr = nso_header.segments[i].location;
- codeset.segments[i].offset = nso_header.segments[i].location;
+ program_image.resize(module_start + nso_header.segments[i].location +
+ static_cast<u32>(data.size()));
+ std::memcpy(program_image.data() + module_start + nso_header.segments[i].location,
+ data.data(), data.size());
+ codeset.segments[i].addr = module_start + nso_header.segments[i].location;
+ codeset.segments[i].offset = module_start + nso_header.segments[i].location;
codeset.segments[i].size = nso_header.segments[i].size;
}
@@ -118,7 +134,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
}
codeset.DataSegment().size += nso_header.segments[2].bss_size;
- const u32 image_size{
+ u32 image_size{
PageAlignSize(static_cast<u32>(program_image.size()) + nso_header.segments[2].bss_size)};
program_image.resize(image_size);
@@ -129,15 +145,44 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
// Apply patches if necessary
const auto name = nso_file.GetName();
if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
- std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
+ std::span<u8> patchable_section(program_image.data() + module_start,
+ program_image.size() - module_start);
+ std::vector<u8> pi_header(sizeof(NSOHeader) + patchable_section.size());
std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
- std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(),
- program_image.size());
+ std::memcpy(pi_header.data() + sizeof(NSOHeader), patchable_section.data(),
+ patchable_section.size());
pi_header = pm->PatchNSO(pi_header, name);
- std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
+ std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), patchable_section.data());
+ }
+
+#ifdef HAS_NCE
+ // If we are computing the process code layout and using nce backend, patch.
+ const auto& code = codeset.CodeSegment();
+ if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::None) {
+ // Patch SVCs and MRS calls in the guest code
+ patch->PatchText(program_image, code);
+
+ // Add patch section size to the module size.
+ image_size += static_cast<u32>(patch->GetSectionSize());
+ } else if (patch) {
+ // Relocate code patch and copy to the program_image.
+ patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers());
+
+ // Update patch section.
+ auto& patch_segment = codeset.PatchSegment();
+ patch_segment.addr =
+ patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size;
+ patch_segment.size = static_cast<u32>(patch->GetSectionSize());
+
+ // Add patch section size to the module size. In PreText mode image_size
+ // already contains the patch segment as part of module_start.
+ if (patch->GetPatchMode() == Core::NCE::PatchMode::PostData) {
+ image_size += patch_segment.size;
+ }
}
+#endif
// If we aren't actually loading (i.e. just computing the process code layout), we are done
if (!load_into_process) {
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 0b53b4ecd..29b86ed4c 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -15,6 +15,10 @@ namespace Core {
class System;
}
+namespace Core::NCE {
+class Patcher;
+}
+
namespace Kernel {
class KProcess;
}
@@ -88,7 +92,8 @@ public:
static std::optional<VAddr> LoadModule(Kernel::KProcess& process, Core::System& system,
const FileSys::VfsFile& nso_file, VAddr load_base,
bool should_pass_arguments, bool load_into_process,
- std::optional<FileSys::PatchManager> pm = {});
+ std::optional<FileSys::PatchManager> pm = {},
+ Core::NCE::Patcher* patch = nullptr);
LoadResult Load(Kernel::KProcess& process, Core::System& system) override;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index a3431772a..5b376b202 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -53,7 +53,7 @@ struct Memory::Impl {
}
void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
- Common::PhysicalAddress target) {
+ Common::PhysicalAddress target, Common::MemoryPermission perms) {
ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", GetInteger(base));
ASSERT_MSG(target >= DramMemoryMap::Base, "Out of bounds target: {:016X}",
@@ -63,7 +63,7 @@ struct Memory::Impl {
if (Settings::IsFastmemEnabled()) {
system.DeviceMemory().buffer.Map(GetInteger(base),
- GetInteger(target) - DramMemoryMap::Base, size);
+ GetInteger(target) - DramMemoryMap::Base, size, perms);
}
}
@@ -78,6 +78,51 @@ struct Memory::Impl {
}
}
+ void ProtectRegion(Common::PageTable& page_table, VAddr vaddr, u64 size,
+ Common::MemoryPermission perms) {
+ ASSERT_MSG((size & YUZU_PAGEMASK) == 0, "non-page aligned size: {:016X}", size);
+ ASSERT_MSG((vaddr & YUZU_PAGEMASK) == 0, "non-page aligned base: {:016X}", vaddr);
+
+ if (!Settings::IsFastmemEnabled()) {
+ return;
+ }
+
+ const bool is_r = True(perms & Common::MemoryPermission::Read);
+ const bool is_w = True(perms & Common::MemoryPermission::Write);
+ const bool is_x =
+ True(perms & Common::MemoryPermission::Execute) && Settings::IsNceEnabled();
+
+ if (!current_page_table) {
+ system.DeviceMemory().buffer.Protect(vaddr, size, is_r, is_w, is_x);
+ return;
+ }
+
+ u64 protect_bytes{};
+ u64 protect_begin{};
+ for (u64 addr = vaddr; addr < vaddr + size; addr += YUZU_PAGESIZE) {
+ const Common::PageType page_type{
+ current_page_table->pointers[addr >> YUZU_PAGEBITS].Type()};
+ switch (page_type) {
+ case Common::PageType::RasterizerCachedMemory:
+ if (protect_bytes > 0) {
+ system.DeviceMemory().buffer.Protect(protect_begin, protect_bytes, is_r, is_w,
+ is_x);
+ protect_bytes = 0;
+ }
+ break;
+ default:
+ if (protect_bytes == 0) {
+ protect_begin = addr;
+ }
+ protect_bytes += YUZU_PAGESIZE;
+ }
+ }
+
+ if (protect_bytes > 0) {
+ system.DeviceMemory().buffer.Protect(protect_begin, protect_bytes, is_r, is_w, is_x);
+ }
+ }
+
[[nodiscard]] u8* GetPointerFromRasterizerCachedMemory(u64 vaddr) const {
const Common::PhysicalAddress paddr{
current_page_table->backing_addr[vaddr >> YUZU_PAGEBITS]};
@@ -831,14 +876,19 @@ void Memory::SetCurrentPageTable(Kernel::KProcess& process, u32 core_id) {
}
void Memory::MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
- Common::PhysicalAddress target) {
- impl->MapMemoryRegion(page_table, base, size, target);
+ Common::PhysicalAddress target, Common::MemoryPermission perms) {
+ impl->MapMemoryRegion(page_table, base, size, target, perms);
}
void Memory::UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size) {
impl->UnmapRegion(page_table, base, size);
}
+void Memory::ProtectRegion(Common::PageTable& page_table, Common::ProcessAddress vaddr, u64 size,
+ Common::MemoryPermission perms) {
+ impl->ProtectRegion(page_table, GetInteger(vaddr), size, perms);
+}
+
bool Memory::IsValidVirtualAddress(const Common::ProcessAddress vaddr) const {
const Kernel::KProcess& process = *system.ApplicationProcess();
const auto& page_table = process.GetPageTable().GetImpl();
@@ -1001,4 +1051,17 @@ void Memory::FlushRegion(Common::ProcessAddress dest_addr, size_t size) {
impl->FlushRegion(dest_addr, size);
}
+bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
+ bool mapped = true;
+ u8* const ptr = impl->GetPointerImpl(
+ GetInteger(vaddr),
+ [&] {
+ LOG_ERROR(HW_Memory, "Unmapped InvalidateNCE for {} bytes @ {:#x}", size,
+ GetInteger(vaddr));
+ mapped = false;
+ },
+ [&] { impl->system.GPU().InvalidateRegion(GetInteger(vaddr), size); });
+ return mapped && ptr != nullptr;
+}
+
} // namespace Core::Memory
diff --git a/src/core/memory.h b/src/core/memory.h
index 13047a545..ed8ebb5eb 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -15,8 +15,9 @@
#include "core/hle/result.h"
namespace Common {
+enum class MemoryPermission : u32;
struct PageTable;
-}
+} // namespace Common
namespace Core {
class System;
@@ -82,9 +83,10 @@ public:
* @param size The amount of bytes to map. Must be page-aligned.
* @param target Buffer with the memory backing the mapping. Must be of length at least
* `size`.
+ * @param perms The permissions to map the memory with.
*/
void MapMemoryRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
- Common::PhysicalAddress target);
+ Common::PhysicalAddress target, Common::MemoryPermission perms);
/**
* Unmaps a region of the emulated process address space.
@@ -96,6 +98,17 @@ public:
void UnmapRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size);
/**
+ * Protects a region of the emulated process address space with the new permissions.
+ *
+ * @param page_table The page table of the emulated process.
+ * @param base The start address to re-protect. Must be page-aligned.
+ * @param size The amount of bytes to protect. Must be page-aligned.
+ * @param perms The permissions the address range is mapped.
+ */
+ void ProtectRegion(Common::PageTable& page_table, Common::ProcessAddress base, u64 size,
+ Common::MemoryPermission perms);
+
+ /**
* Checks whether or not the supplied address is a valid virtual
* address for the current process.
*
@@ -472,6 +485,7 @@ public:
void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers);
void InvalidateRegion(Common::ProcessAddress dest_addr, size_t size);
+ bool InvalidateNCE(Common::ProcessAddress vaddr, size_t size);
void FlushRegion(Common::ProcessAddress dest_addr, size_t size);
private:
diff --git a/src/frontend_common/config.cpp b/src/frontend_common/config.cpp
index 7474cb0f9..1a0491c2c 100644
--- a/src/frontend_common/config.cpp
+++ b/src/frontend_common/config.cpp
@@ -924,12 +924,14 @@ std::string Config::AdjustOutputString(const std::string& string) {
// Windows requires that two forward slashes are used at the start of a path for unmapped
// network drives so we have to watch for that here
+#ifndef ANDROID
if (string.substr(0, 2) == "//") {
boost::replace_all(adjusted_string, "//", "/");
adjusted_string.insert(0, "/");
} else {
boost::replace_all(adjusted_string, "//", "/");
}
+#endif
// Needed for backwards compatibility with QSettings deserialization
for (const auto& special_character : special_characters) {
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
index 2705ab140..9319ea007 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_memory.cpp
@@ -5,6 +5,7 @@
#include "shader_recompiler/backend/glasm/glasm_emit_context.h"
#include "shader_recompiler/frontend/ir/program.h"
#include "shader_recompiler/frontend/ir/value.h"
+#include "shader_recompiler/profile.h"
#include "shader_recompiler/runtime_info.h"
namespace Shader::Backend::GLASM {
@@ -35,7 +36,9 @@ void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std
continue;
}
const auto& ssbo{ctx.info.storage_buffers_descriptors[index]};
- ctx.Add("LDC.U64 DC.x,c{}[{}];" // ssbo_addr
+ const u64 ssbo_align_mask{~(ctx.profile.min_ssbo_alignment - 1U)};
+ ctx.Add("LDC.U64 DC.x,c{}[{}];" // unaligned_ssbo_addr
+ "AND.U64 DC.x,DC.x,{};" // ssbo_addr = unaligned_ssbo_addr & ssbo_align_mask
"LDC.U32 RC.x,c{}[{}];" // ssbo_size_u32
"CVT.U64.U32 DC.y,RC.x;" // ssbo_size = ssbo_size_u32
"ADD.U64 DC.y,DC.y,DC.x;" // ssbo_end = ssbo_addr + ssbo_size
@@ -44,8 +47,8 @@ void GlobalStorageOp(EmitContext& ctx, Register address, bool pointer_based, std
"AND.U.CC RC.x,RC.x,RC.y;" // cond = a && b
"IF NE.x;" // if cond
"SUB.U64 DC.x,{}.x,DC.x;", // offset = input_addr - ssbo_addr
- ssbo.cbuf_index, ssbo.cbuf_offset, ssbo.cbuf_index, ssbo.cbuf_offset + 8, address,
- address, address);
+ ssbo.cbuf_index, ssbo.cbuf_offset, ssbo_align_mask, ssbo.cbuf_index,
+ ssbo.cbuf_offset + 8, address, address, address);
if (pointer_based) {
ctx.Add("PK64.U DC.y,c[{}];" // host_ssbo = cbuf
"ADD.U64 DC.x,DC.x,DC.y;" // host_addr = host_ssbo + offset
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
index 2f27ef5f9..b2ceeefc4 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
@@ -604,7 +604,10 @@ std::string EmitContext::DefineGlobalMemoryFunctions() {
addr_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, addr_loc / 16, Swizzle(addr_loc));
size_xy[i] = fmt::format("ftou({}[{}].{})", cbuf, size_loc / 16, Swizzle(size_loc));
}
- const auto addr_pack{fmt::format("packUint2x32(uvec2({},{}))", addr_xy[0], addr_xy[1])};
+ const u32 ssbo_align_mask{~(static_cast<u32>(profile.min_ssbo_alignment) - 1U)};
+ const auto aligned_low_addr{fmt::format("{}&{}", addr_xy[0], ssbo_align_mask)};
+ const auto aligned_addr{fmt::format("uvec2({},{})", aligned_low_addr, addr_xy[1])};
+ const auto addr_pack{fmt::format("packUint2x32({})", aligned_addr)};
const auto addr_statment{fmt::format("uint64_t {}={};", ssbo_addr, addr_pack)};
func += addr_statment;
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 57df6fc34..3350f1f85 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -891,7 +891,9 @@ void EmitContext::DefineGlobalMemoryFunctions(const Info& info) {
const Id ssbo_size_pointer{OpAccessChain(uniform_types.U32, cbufs[ssbo.cbuf_index].U32,
zero, ssbo_size_cbuf_offset)};
- const Id ssbo_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))};
+ const u64 ssbo_align_mask{~(profile.min_ssbo_alignment - 1U)};
+ const Id unaligned_addr{OpBitcast(U64, OpLoad(U32[2], ssbo_addr_pointer))};
+ const Id ssbo_addr{OpBitwiseAnd(U64, unaligned_addr, Constant(U64, ssbo_align_mask))};
const Id ssbo_size{OpUConvert(U64, OpLoad(U32[1], ssbo_size_pointer))};
const Id ssbo_end{OpIAdd(U64, ssbo_addr, ssbo_size)};
const Id cond{OpLogicalAnd(U1, OpUGreaterThanEqual(U1, addr, ssbo_addr),
diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
index 8fac6bad3..321ea625b 100644
--- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
@@ -298,7 +298,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::PositionPass(env, program);
- Optimization::GlobalMemoryToStorageBufferPass(program);
+ Optimization::GlobalMemoryToStorageBufferPass(program, host_info);
Optimization::TexturePass(env, program, host_info);
if (Settings::values.resolution_info.active) {
diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h
index 7d2ded907..1b53404fc 100644
--- a/src/shader_recompiler/host_translate_info.h
+++ b/src/shader_recompiler/host_translate_info.h
@@ -16,6 +16,7 @@ struct HostTranslateInfo {
bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered
bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers
bool support_viewport_index_layer{}; ///< True when the device supports gl_Layer in VS
+ u32 min_ssbo_alignment{}; ///< Minimum alignment supported by the device for SSBOs
bool support_geometry_shader_passthrough{}; ///< True when the device supports geometry
///< passthrough shaders
bool support_conditional_barrier{}; ///< True when the device supports barriers in conditional
diff --git a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
index d1e59f22e..0cea79945 100644
--- a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
+++ b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp
@@ -11,6 +11,7 @@
#include "shader_recompiler/frontend/ir/breadth_first_search.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
#include "shader_recompiler/frontend/ir/value.h"
+#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/ir_opt/passes.h"
namespace Shader::Optimization {
@@ -408,7 +409,7 @@ void CollectStorageBuffers(IR::Block& block, IR::Inst& inst, StorageInfo& info)
}
/// Returns the offset in indices (not bytes) for an equivalent storage instruction
-IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer) {
+IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer, u32 alignment) {
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
IR::U32 offset;
if (const std::optional<LowAddrInfo> low_addr{TrackLowAddress(&inst)}) {
@@ -421,7 +422,10 @@ IR::U32 StorageOffset(IR::Block& block, IR::Inst& inst, StorageBufferAddr buffer
}
// Subtract the least significant 32 bits from the guest offset. The result is the storage
// buffer offset in bytes.
- const IR::U32 low_cbuf{ir.GetCbuf(ir.Imm32(buffer.index), ir.Imm32(buffer.offset))};
+ IR::U32 low_cbuf{ir.GetCbuf(ir.Imm32(buffer.index), ir.Imm32(buffer.offset))};
+
+ // Align the offset base to match the host alignment requirements
+ low_cbuf = ir.BitwiseAnd(low_cbuf, ir.Imm32(~(alignment - 1U)));
return ir.ISub(offset, low_cbuf);
}
@@ -516,7 +520,7 @@ void Replace(IR::Block& block, IR::Inst& inst, const IR::U32& storage_index,
}
} // Anonymous namespace
-void GlobalMemoryToStorageBufferPass(IR::Program& program) {
+void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info) {
StorageInfo info;
for (IR::Block* const block : program.post_order_blocks) {
for (IR::Inst& inst : block->Instructions()) {
@@ -540,7 +544,8 @@ void GlobalMemoryToStorageBufferPass(IR::Program& program) {
const IR::U32 index{IR::Value{static_cast<u32>(info.set.index_of(it))}};
IR::Block* const block{storage_inst.block};
IR::Inst* const inst{storage_inst.inst};
- const IR::U32 offset{StorageOffset(*block, *inst, storage_buffer)};
+ const IR::U32 offset{
+ StorageOffset(*block, *inst, storage_buffer, host_info.min_ssbo_alignment)};
Replace(*block, *inst, index, offset);
}
}
diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h
index d4d5285e5..1e637cb23 100644
--- a/src/shader_recompiler/ir_opt/passes.h
+++ b/src/shader_recompiler/ir_opt/passes.h
@@ -16,7 +16,7 @@ void CollectShaderInfoPass(Environment& env, IR::Program& program);
void ConditionalBarrierPass(IR::Program& program);
void ConstantPropagationPass(Environment& env, IR::Program& program);
void DeadCodeEliminationPass(IR::Program& program);
-void GlobalMemoryToStorageBufferPass(IR::Program& program);
+void GlobalMemoryToStorageBufferPass(IR::Program& program, const HostTranslateInfo& host_info);
void IdentityRemovalPass(IR::Program& program);
void LowerFp64ToFp32(IR::Program& program);
void LowerFp16ToFp32(IR::Program& program);
diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h
index a9de9f4a9..66901a965 100644
--- a/src/shader_recompiler/profile.h
+++ b/src/shader_recompiler/profile.h
@@ -85,6 +85,8 @@ struct Profile {
/// Maxwell and earlier nVidia architectures have broken robust support
bool has_broken_robust{};
+
+ u64 min_ssbo_alignment{};
};
} // namespace Shader
diff --git a/src/tests/common/host_memory.cpp b/src/tests/common/host_memory.cpp
index 1b014b632..1a28e862b 100644
--- a/src/tests/common/host_memory.cpp
+++ b/src/tests/common/host_memory.cpp
@@ -11,6 +11,7 @@ using namespace Common::Literals;
static constexpr size_t VIRTUAL_SIZE = 1ULL << 39;
static constexpr size_t BACKING_SIZE = 4_GiB;
+static constexpr auto PERMS = Common::MemoryPermission::ReadWrite;
TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
@@ -19,7 +20,7 @@ TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
TEST_CASE("HostMemory: Simple map", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x5000, 0x8000, 0x1000);
+ mem.Map(0x5000, 0x8000, 0x1000, PERMS);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
data[0] = 50;
@@ -28,8 +29,8 @@ TEST_CASE("HostMemory: Simple map", "[common]") {
TEST_CASE("HostMemory: Simple mirror map", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x5000, 0x3000, 0x2000);
- mem.Map(0x8000, 0x4000, 0x1000);
+ mem.Map(0x5000, 0x3000, 0x2000, PERMS);
+ mem.Map(0x8000, 0x4000, 0x1000, PERMS);
volatile u8* const mirror_a = mem.VirtualBasePointer() + 0x5000;
volatile u8* const mirror_b = mem.VirtualBasePointer() + 0x8000;
@@ -39,7 +40,7 @@ TEST_CASE("HostMemory: Simple mirror map", "[common]") {
TEST_CASE("HostMemory: Simple unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x5000, 0x3000, 0x2000);
+ mem.Map(0x5000, 0x3000, 0x2000, PERMS);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
data[75] = 50;
@@ -50,7 +51,7 @@ TEST_CASE("HostMemory: Simple unmap", "[common]") {
TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x5000, 0x3000, 0x2000);
+ mem.Map(0x5000, 0x3000, 0x2000, PERMS);
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
data[0] = 50;
@@ -58,79 +59,79 @@ TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
mem.Unmap(0x5000, 0x2000);
- mem.Map(0x5000, 0x3000, 0x2000);
+ mem.Map(0x5000, 0x3000, 0x2000, PERMS);
REQUIRE(data[0] == 50);
- mem.Map(0x7000, 0x2000, 0x5000);
+ mem.Map(0x7000, 0x2000, 0x5000, PERMS);
REQUIRE(data[0x3000] == 50);
}
TEST_CASE("HostMemory: Nieche allocation", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x0000, 0, 0x20000);
+ mem.Map(0x0000, 0, 0x20000, PERMS);
mem.Unmap(0x0000, 0x4000);
- mem.Map(0x1000, 0, 0x2000);
- mem.Map(0x3000, 0, 0x1000);
- mem.Map(0, 0, 0x1000);
+ mem.Map(0x1000, 0, 0x2000, PERMS);
+ mem.Map(0x3000, 0, 0x1000, PERMS);
+ mem.Map(0, 0, 0x1000, PERMS);
}
TEST_CASE("HostMemory: Full unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
mem.Unmap(0x8000, 0x4000);
- mem.Map(0x6000, 0, 0x16000);
+ mem.Map(0x6000, 0, 0x16000, PERMS);
}
TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x0000, 0, 0x4000);
+ mem.Map(0x0000, 0, 0x4000, PERMS);
mem.Unmap(0x2000, 0x4000);
- mem.Map(0x2000, 0x80000, 0x4000);
+ mem.Map(0x2000, 0x80000, 0x4000, PERMS);
}
TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
mem.Unmap(0x6000, 0x4000);
- mem.Map(0x8000, 0, 0x2000);
+ mem.Map(0x8000, 0, 0x2000, PERMS);
}
TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x0000, 0, 0x4000);
- mem.Map(0x4000, 0, 0x1b000);
+ mem.Map(0x0000, 0, 0x4000, PERMS);
+ mem.Map(0x4000, 0, 0x1b000, PERMS);
mem.Unmap(0x3000, 0x1c000);
- mem.Map(0x3000, 0, 0x20000);
+ mem.Map(0x3000, 0, 0x20000, PERMS);
}
TEST_CASE("HostMemory: Unmap between placeholders", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x0000, 0, 0x4000);
- mem.Map(0x4000, 0, 0x4000);
+ mem.Map(0x0000, 0, 0x4000, PERMS);
+ mem.Map(0x4000, 0, 0x4000, PERMS);
mem.Unmap(0x2000, 0x4000);
- mem.Map(0x2000, 0, 0x4000);
+ mem.Map(0x2000, 0, 0x4000, PERMS);
}
TEST_CASE("HostMemory: Unmap to origin", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0, 0x4000);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x4000, 0, 0x4000, PERMS);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
mem.Unmap(0x4000, 0x4000);
- mem.Map(0, 0, 0x4000);
- mem.Map(0x4000, 0, 0x4000);
+ mem.Map(0, 0, 0x4000, PERMS);
+ mem.Map(0x4000, 0, 0x4000, PERMS);
}
TEST_CASE("HostMemory: Unmap to right", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0, 0x4000);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x4000, 0, 0x4000, PERMS);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
mem.Unmap(0x8000, 0x4000);
- mem.Map(0x8000, 0, 0x4000);
+ mem.Map(0x8000, 0, 0x4000, PERMS);
}
TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0x10000, 0x4000);
+ mem.Map(0x4000, 0x10000, 0x4000, PERMS);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x1000] = 17;
@@ -142,7 +143,7 @@ TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0x10000, 0x4000);
+ mem.Map(0x4000, 0x10000, 0x4000, PERMS);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x3000] = 19;
@@ -156,7 +157,7 @@ TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0x10000, 0x4000);
+ mem.Map(0x4000, 0x10000, 0x4000, PERMS);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x0000] = 19;
@@ -170,8 +171,8 @@ TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") {
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
- mem.Map(0x4000, 0x10000, 0x2000);
- mem.Map(0x6000, 0x20000, 0x2000);
+ mem.Map(0x4000, 0x10000, 0x2000, PERMS);
+ mem.Map(0x6000, 0x20000, 0x2000, PERMS);
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
ptr[0x0000] = 19;
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 90dbd352f..6d1fc3887 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -1753,15 +1753,25 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr));
return std::min(memory_layout_size, static_cast<u32>(8_MiB));
}();
- const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
- if (!cpu_addr || size == 0) {
+ // Alignment only applies to the offset of the buffer
+ const u32 alignment = runtime.GetStorageBufferAlignment();
+ const GPUVAddr aligned_gpu_addr = Common::AlignDown(gpu_addr, alignment);
+ const u32 aligned_size = static_cast<u32>(gpu_addr - aligned_gpu_addr) + size;
+
+ const std::optional<VAddr> aligned_cpu_addr = gpu_memory->GpuToCpuAddress(aligned_gpu_addr);
+ if (!aligned_cpu_addr || size == 0) {
LOG_WARNING(HW_GPU, "Failed to find storage buffer for cbuf index {}", cbuf_index);
return NULL_BINDING;
}
- const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, YUZU_PAGESIZE);
+ const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
+ ASSERT_MSG(cpu_addr, "Unaligned storage buffer address not found for cbuf index {}",
+ cbuf_index);
+ // The end address used for size calculation does not need to be aligned
+ const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, Core::Memory::YUZU_PAGESIZE);
+
const Binding binding{
- .cpu_addr = *cpu_addr,
- .size = is_written ? size : static_cast<u32>(cpu_end - *cpu_addr),
+ .cpu_addr = *aligned_cpu_addr,
+ .size = is_written ? aligned_size : static_cast<u32>(cpu_end - *aligned_cpu_addr),
.buffer_id = BufferId{},
};
return binding;
diff --git a/src/video_core/buffer_cache/usage_tracker.h b/src/video_core/buffer_cache/usage_tracker.h
index ab05fe415..5f8688d31 100644
--- a/src/video_core/buffer_cache/usage_tracker.h
+++ b/src/video_core/buffer_cache/usage_tracker.h
@@ -58,7 +58,7 @@ private:
void TrackPage(u64 page, u64 offset, u64 size) noexcept {
const size_t offset_in_page = offset % PAGE_BYTES;
const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
- const size_t num_bits = std::min(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
+ const size_t num_bits = std::min<size_t>(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
const size_t mask = ~u64{0} >> (64 - num_bits);
pages[page] |= (~u64{0} & mask) << first_bit;
}
@@ -66,7 +66,7 @@ private:
bool IsPageUsed(u64 page, u64 offset, u64 size) const noexcept {
const size_t offset_in_page = offset % PAGE_BYTES;
const size_t first_bit = offset_in_page >> BYTES_PER_BIT_SHIFT;
- const size_t num_bits = std::min(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
+ const size_t num_bits = std::min<size_t>(size, PAGE_BYTES) >> BYTES_PER_BIT_SHIFT;
const size_t mask = ~u64{0} >> (64 - num_bits);
const size_t mask2 = (~u64{0} & mask) << first_bit;
return (pages[page] & mask2) != 0;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index dfd696de6..ed188b435 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -146,8 +146,12 @@ StagingBufferMap BufferCacheRuntime::UploadStagingBuffer(size_t size) {
return staging_buffer_pool.RequestUploadBuffer(size);
}
-StagingBufferMap BufferCacheRuntime::DownloadStagingBuffer(size_t size) {
- return staging_buffer_pool.RequestDownloadBuffer(size);
+StagingBufferMap BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) {
+ return staging_buffer_pool.RequestDownloadBuffer(size, deferred);
+}
+
+void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferMap& buffer) {
+ staging_buffer_pool.FreeDeferredStagingBuffer(buffer);
}
u64 BufferCacheRuntime::GetDeviceMemoryUsage() const {
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index feccf06f9..1e8708f59 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -66,7 +66,9 @@ public:
[[nodiscard]] StagingBufferMap UploadStagingBuffer(size_t size);
- [[nodiscard]] StagingBufferMap DownloadStagingBuffer(size_t size);
+ [[nodiscard]] StagingBufferMap DownloadStagingBuffer(size_t size, bool deferred = false);
+
+ void FreeDeferredStagingBuffer(StagingBufferMap& buffer);
bool CanReorderUpload(const Buffer&, std::span<const VideoCommon::BufferCopy>) {
return false;
@@ -191,6 +193,10 @@ public:
return device.CanReportMemoryUsage();
}
+ u32 GetStorageBufferAlignment() const {
+ return static_cast<u32>(device.GetShaderStorageBufferAlignment());
+ }
+
private:
static constexpr std::array PABO_LUT{
GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV, GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV,
@@ -242,7 +248,7 @@ struct BufferCacheParams {
static constexpr bool NEEDS_BIND_STORAGE_INDEX = true;
static constexpr bool USE_MEMORY_MAPS = true;
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = true;
- static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = false;
+ static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
// TODO: Investigate why OpenGL seems to perform worse with persistently mapped buffer uploads
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false;
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 94258ccd0..993438a27 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -265,33 +265,33 @@ std::string Device::GetVendorName() const {
if (vendor_name == "Intel") {
// For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris.
// Simply return `INTEL` for those as well as the Windows driver.
- return "INTEL";
+ return "Intel";
}
if (vendor_name == "Intel Open Source Technology Center") {
- return "I965";
+ return "i965";
}
if (vendor_name == "Mesa Project") {
- return "I915";
+ return "i915";
}
if (vendor_name == "Mesa/X.org") {
// This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return
// MESA instead of one of those driver names.
- return "MESA";
+ return "Mesa";
}
if (vendor_name == "AMD") {
- return "RADEONSI";
+ return "RadeonSI";
}
if (vendor_name == "nouveau") {
- return "NOUVEAU";
+ return "Nouveau";
}
if (vendor_name == "X.Org") {
return "R600";
}
if (vendor_name == "Collabora Ltd") {
- return "ZINK";
+ return "Zink";
}
if (vendor_name == "Intel Corporation") {
- return "OPENSWR";
+ return "OpenSWR";
}
if (vendor_name == "Microsoft Corporation") {
return "D3D12";
@@ -300,7 +300,7 @@ std::string Device::GetVendorName() const {
// Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default
// strategy would have returned `NVIDIA` here for this driver, the same result as the
// proprietary driver.
- return "TEGRA";
+ return "Tegra";
}
return vendor_name;
}
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 2888e0238..26f2d0ea7 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -232,6 +232,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
.has_gl_bool_ref_bug = device.HasBoolRefBug(),
.ignore_nan_fp_comparisons = true,
.gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
+ .min_ssbo_alignment = device.GetShaderStorageBufferAlignment(),
},
host_info{
.support_float64 = true,
@@ -240,6 +241,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
.needs_demote_reorder = device.IsAmd(),
.support_snorm_render_buffer = false,
.support_viewport_index_layer = device.HasVertexViewportLayer(),
+ .min_ssbo_alignment = static_cast<u32>(device.GetShaderStorageBufferAlignment()),
.support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
.support_conditional_barrier = device.SupportsConditionalBarriers(),
} {
diff --git a/src/video_core/renderer_opengl/gl_staging_buffer_pool.cpp b/src/video_core/renderer_opengl/gl_staging_buffer_pool.cpp
index bbb06e51f..cadad6507 100644
--- a/src/video_core/renderer_opengl/gl_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_opengl/gl_staging_buffer_pool.cpp
@@ -28,63 +28,69 @@ StagingBuffers::StagingBuffers(GLenum storage_flags_, GLenum map_flags_)
StagingBuffers::~StagingBuffers() = default;
-StagingBufferMap StagingBuffers::RequestMap(size_t requested_size, bool insert_fence) {
+StagingBufferMap StagingBuffers::RequestMap(size_t requested_size, bool insert_fence,
+ bool deferred) {
MICROPROFILE_SCOPE(OpenGL_BufferRequest);
const size_t index = RequestBuffer(requested_size);
- OGLSync* const sync = insert_fence ? &syncs[index] : nullptr;
- sync_indices[index] = insert_fence ? ++current_sync_index : 0;
+ OGLSync* const sync = insert_fence ? &allocs[index].sync : nullptr;
+ allocs[index].sync_index = insert_fence ? ++current_sync_index : 0;
+ allocs[index].deferred = deferred;
return StagingBufferMap{
- .mapped_span = std::span(maps[index], requested_size),
+ .mapped_span = std::span(allocs[index].map, requested_size),
.sync = sync,
- .buffer = buffers[index].handle,
+ .buffer = allocs[index].buffer.handle,
+ .index = index,
};
}
+void StagingBuffers::FreeDeferredStagingBuffer(size_t index) {
+ ASSERT(allocs[index].deferred);
+ allocs[index].deferred = false;
+}
+
size_t StagingBuffers::RequestBuffer(size_t requested_size) {
if (const std::optional<size_t> index = FindBuffer(requested_size); index) {
return *index;
}
-
- OGLBuffer& buffer = buffers.emplace_back();
- buffer.Create();
+ StagingBufferAlloc alloc;
+ alloc.buffer.Create();
const auto next_pow2_size = Common::NextPow2(requested_size);
- glNamedBufferStorage(buffer.handle, next_pow2_size, nullptr,
+ glNamedBufferStorage(alloc.buffer.handle, next_pow2_size, nullptr,
storage_flags | GL_MAP_PERSISTENT_BIT);
- maps.push_back(static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, next_pow2_size,
- map_flags | GL_MAP_PERSISTENT_BIT)));
- syncs.emplace_back();
- sync_indices.emplace_back();
- sizes.push_back(next_pow2_size);
-
- ASSERT(syncs.size() == buffers.size() && buffers.size() == maps.size() &&
- maps.size() == sizes.size());
-
- return buffers.size() - 1;
+ alloc.map = static_cast<u8*>(glMapNamedBufferRange(alloc.buffer.handle, 0, next_pow2_size,
+ map_flags | GL_MAP_PERSISTENT_BIT));
+ alloc.size = next_pow2_size;
+ allocs.emplace_back(std::move(alloc));
+ return allocs.size() - 1;
}
std::optional<size_t> StagingBuffers::FindBuffer(size_t requested_size) {
size_t known_unsignaled_index = current_sync_index + 1;
size_t smallest_buffer = std::numeric_limits<size_t>::max();
std::optional<size_t> found;
- const size_t num_buffers = sizes.size();
+ const size_t num_buffers = allocs.size();
for (size_t index = 0; index < num_buffers; ++index) {
- const size_t buffer_size = sizes[index];
+ StagingBufferAlloc& alloc = allocs[index];
+ const size_t buffer_size = alloc.size;
if (buffer_size < requested_size || buffer_size >= smallest_buffer) {
continue;
}
- if (syncs[index].handle != 0) {
- if (sync_indices[index] >= known_unsignaled_index) {
+ if (alloc.deferred) {
+ continue;
+ }
+ if (alloc.sync.handle != 0) {
+ if (alloc.sync_index >= known_unsignaled_index) {
// This fence is later than a fence that is known to not be signaled
continue;
}
- if (!syncs[index].IsSignaled()) {
+ if (!alloc.sync.IsSignaled()) {
// Since this fence hasn't been signaled, it's safe to assume all later
// fences haven't been signaled either
- known_unsignaled_index = std::min(known_unsignaled_index, sync_indices[index]);
+ known_unsignaled_index = std::min(known_unsignaled_index, alloc.sync_index);
continue;
}
- syncs[index].Release();
+ alloc.sync.Release();
}
smallest_buffer = buffer_size;
found = index;
@@ -143,8 +149,12 @@ StagingBufferMap StagingBufferPool::RequestUploadBuffer(size_t size) {
return upload_buffers.RequestMap(size, true);
}
-StagingBufferMap StagingBufferPool::RequestDownloadBuffer(size_t size) {
- return download_buffers.RequestMap(size, false);
+StagingBufferMap StagingBufferPool::RequestDownloadBuffer(size_t size, bool deferred) {
+ return download_buffers.RequestMap(size, false, deferred);
+}
+
+void StagingBufferPool::FreeDeferredStagingBuffer(StagingBufferMap& buffer) {
+ download_buffers.FreeDeferredStagingBuffer(buffer.index);
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_staging_buffer_pool.h b/src/video_core/renderer_opengl/gl_staging_buffer_pool.h
index 60f72d3a0..07a56b4d2 100644
--- a/src/video_core/renderer_opengl/gl_staging_buffer_pool.h
+++ b/src/video_core/renderer_opengl/gl_staging_buffer_pool.h
@@ -26,23 +26,30 @@ struct StagingBufferMap {
size_t offset = 0;
OGLSync* sync;
GLuint buffer;
+ size_t index;
};
struct StagingBuffers {
explicit StagingBuffers(GLenum storage_flags_, GLenum map_flags_);
~StagingBuffers();
- StagingBufferMap RequestMap(size_t requested_size, bool insert_fence);
+ StagingBufferMap RequestMap(size_t requested_size, bool insert_fence, bool deferred = false);
+
+ void FreeDeferredStagingBuffer(size_t index);
size_t RequestBuffer(size_t requested_size);
std::optional<size_t> FindBuffer(size_t requested_size);
- std::vector<OGLSync> syncs;
- std::vector<OGLBuffer> buffers;
- std::vector<u8*> maps;
- std::vector<size_t> sizes;
- std::vector<size_t> sync_indices;
+ struct StagingBufferAlloc {
+ OGLSync sync;
+ OGLBuffer buffer;
+ u8* map;
+ size_t size;
+ size_t sync_index;
+ bool deferred;
+ };
+ std::vector<StagingBufferAlloc> allocs;
GLenum storage_flags;
GLenum map_flags;
size_t current_sync_index = 0;
@@ -85,7 +92,8 @@ public:
~StagingBufferPool() = default;
StagingBufferMap RequestUploadBuffer(size_t size);
- StagingBufferMap RequestDownloadBuffer(size_t size);
+ StagingBufferMap RequestDownloadBuffer(size_t size, bool deferred = false);
+ void FreeDeferredStagingBuffer(StagingBufferMap& buffer);
private:
StagingBuffers upload_buffers{GL_MAP_WRITE_BIT, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT};
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 512eef575..66a5ca03e 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -557,8 +557,12 @@ StagingBufferMap TextureCacheRuntime::UploadStagingBuffer(size_t size) {
return staging_buffer_pool.RequestUploadBuffer(size);
}
-StagingBufferMap TextureCacheRuntime::DownloadStagingBuffer(size_t size) {
- return staging_buffer_pool.RequestDownloadBuffer(size);
+StagingBufferMap TextureCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) {
+ return staging_buffer_pool.RequestDownloadBuffer(size, deferred);
+}
+
+void TextureCacheRuntime::FreeDeferredStagingBuffer(StagingBufferMap& buffer) {
+ staging_buffer_pool.FreeDeferredStagingBuffer(buffer);
}
u64 TextureCacheRuntime::GetDeviceMemoryUsage() const {
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index e71b87e99..34870c81f 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -74,7 +74,9 @@ public:
StagingBufferMap UploadStagingBuffer(size_t size);
- StagingBufferMap DownloadStagingBuffer(size_t size);
+ StagingBufferMap DownloadStagingBuffer(size_t size, bool deferred = false);
+
+ void FreeDeferredStagingBuffer(StagingBufferMap& buffer);
u64 GetDeviceLocalMemory() const {
return device_access_memory;
@@ -359,7 +361,7 @@ struct TextureCacheParams {
static constexpr bool FRAMEBUFFER_BLITS = true;
static constexpr bool HAS_EMULATED_COPIES = true;
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
- static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = false;
+ static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
using Runtime = OpenGL::TextureCacheRuntime;
using Image = OpenGL::Image;
@@ -367,7 +369,7 @@ struct TextureCacheParams {
using ImageView = OpenGL::ImageView;
using Sampler = OpenGL::Sampler;
using Framebuffer = OpenGL::Framebuffer;
- using AsyncBuffer = u32;
+ using AsyncBuffer = OpenGL::StagingBufferMap;
using BufferType = GLuint;
};
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 66483a900..5e461fbd0 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -1211,7 +1211,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);
return;
}
- aa_renderpass = CreateRenderPassImpl(GetFormat(framebuffer));
+ aa_renderpass = CreateRenderPassImpl(VK_FORMAT_R16G16B16A16_SFLOAT);
aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);
const std::array<VkPipelineShaderStageCreateInfo, 2> fxaa_shader_stages{{
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 7691cc2ba..5958f52f7 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -355,6 +355,10 @@ bool BufferCacheRuntime::CanReportMemoryUsage() const {
return device.CanReportMemoryUsage();
}
+u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
+ return static_cast<u32>(device.GetStorageBufferAlignment());
+}
+
void BufferCacheRuntime::TickFrame(VideoCommon::SlotVector<Buffer>& slot_buffers) noexcept {
for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
it->ResetUsageTracking();
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 4416a902f..0b3fbd6d0 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -91,6 +91,8 @@ public:
bool CanReportMemoryUsage() const;
+ u32 GetStorageBufferAlignment() const;
+
[[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
[[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false);
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 89b455bff..2a13b2a72 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -373,6 +373,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.has_broken_robust =
device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
+ .min_ssbo_alignment = device.GetStorageBufferAlignment(),
};
host_info = Shader::HostTranslateInfo{
@@ -383,6 +384,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
driver_id == VK_DRIVER_ID_AMD_PROPRIETARY || driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE,
.support_snorm_render_buffer = true,
.support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
+ .min_ssbo_alignment = static_cast<u32>(device.GetStorageBufferAlignment()),
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
.support_conditional_barrier = device.SupportsConditionalBarriers(),
};
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index e0ab1eaac..07222e603 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -892,10 +892,6 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateFrontFace(regs);
UpdateStencilOp(regs);
- if (device.IsExtVertexInputDynamicStateSupported()) {
- UpdateVertexInput(regs);
- }
-
if (state_tracker.TouchStateEnable()) {
UpdateDepthBoundsTestEnable(regs);
UpdateDepthTestEnable(regs);
@@ -918,6 +914,9 @@ void RasterizerVulkan::UpdateDynamicStates() {
UpdateBlending(regs);
}
}
+ if (device.IsExtVertexInputDynamicStateSupported()) {
+ UpdateVertexInput(regs);
+ }
}
void RasterizerVulkan::HandleTransformFeedback() {
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 5dbec2e62..38b1619df 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1439,7 +1439,7 @@ void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImag
UploadMemory(map.buffer, map.offset, copies);
}
-void Image::DownloadMemory(VkBuffer buffer, VkDeviceSize offset,
+void Image::DownloadMemory(VkBuffer buffer, size_t offset,
std::span<const VideoCommon::BufferImageCopy> copies) {
std::array buffer_handles{
buffer,
@@ -1450,7 +1450,7 @@ void Image::DownloadMemory(VkBuffer buffer, VkDeviceSize offset,
DownloadMemory(buffer_handles, buffer_offsets, copies);
}
-void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<VkDeviceSize> offsets_span,
+void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> offsets_span,
std::span<const VideoCommon::BufferImageCopy> copies) {
const bool is_rescaled = True(flags & ImageFlagBits::Rescaled);
if (is_rescaled) {
@@ -1530,7 +1530,7 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
map.buffer,
};
std::array offsets{
- map.offset,
+ static_cast<size_t>(map.offset),
};
DownloadMemory(buffers, offsets, copies);
}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index edf5d7635..0dbde65d6 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -147,10 +147,10 @@ public:
void UploadMemory(const StagingBufferRef& map,
std::span<const VideoCommon::BufferImageCopy> copies);
- void DownloadMemory(VkBuffer buffer, VkDeviceSize offset,
+ void DownloadMemory(VkBuffer buffer, size_t offset,
std::span<const VideoCommon::BufferImageCopy> copies);
- void DownloadMemory(std::span<VkBuffer> buffers, std::span<VkDeviceSize> offsets,
+ void DownloadMemory(std::span<VkBuffer> buffers, std::span<size_t> offsets,
std::span<const VideoCommon::BufferImageCopy> copies);
void DownloadMemory(const StagingBufferRef& map,
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index d575c57ca..dade38b18 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -995,7 +995,7 @@ void TextureCache<P>::DownloadImageIntoBuffer(typename TextureCache<P>::Image* i
buffer,
download_map.buffer,
};
- std::array<u64, 2> buffer_offsets{
+ std::array<size_t, 2> buffer_offsets{
buffer_offset,
download_map.offset,
};
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index fde36a49c..1fda0042d 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -519,10 +519,6 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state");
RemoveExtensionFeature(extensions.extended_dynamic_state, features.extended_dynamic_state,
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
-
- LOG_WARNING(Render_Vulkan, "ARM drivers have broken VK_EXT_extended_dynamic_state2");
- RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
- VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
}
if (is_nvidia) {
@@ -611,17 +607,12 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
}
}
if (extensions.vertex_input_dynamic_state && is_qualcomm) {
- const u32 version = (properties.properties.driverVersion << 3) >> 3;
- if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
- version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
- // Qualcomm Adreno 7xx drivers do not properly support vertex_input_dynamic_state.
- LOG_WARNING(
- Render_Vulkan,
- "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state");
- RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
- features.vertex_input_dynamic_state,
- VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
- }
+ // Qualcomm drivers do not properly support vertex_input_dynamic_state.
+ LOG_WARNING(Render_Vulkan,
+ "Qualcomm drivers have broken VK_EXT_vertex_input_dynamic_state");
+ RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
+ features.vertex_input_dynamic_state,
+ VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
}
sets_per_pool = 64;
@@ -704,6 +695,22 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
std::min(properties.properties.limits.maxVertexInputBindings, 16U);
}
+ if (!extensions.extended_dynamic_state && extensions.extended_dynamic_state2) {
+ LOG_INFO(Render_Vulkan,
+ "Removing extendedDynamicState2 due to missing extendedDynamicState");
+ RemoveExtensionFeature(extensions.extended_dynamic_state2, features.extended_dynamic_state2,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
+ }
+
+ if (!extensions.extended_dynamic_state2 && extensions.extended_dynamic_state3) {
+ LOG_INFO(Render_Vulkan,
+ "Removing extendedDynamicState3 due to missing extendedDynamicState2");
+ RemoveExtensionFeature(extensions.extended_dynamic_state3, features.extended_dynamic_state3,
+ VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
+ dynamic_state3_blending = false;
+ dynamic_state3_enables = false;
+ }
+
logical = vk::Device::Create(physical, queue_cis, ExtensionListForVulkan(loaded_extensions),
first_next, dld);
@@ -847,11 +854,41 @@ std::string Device::GetDriverName() const {
case VK_DRIVER_ID_NVIDIA_PROPRIETARY:
return "NVIDIA";
case VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS:
- return "INTEL";
+ return "Intel";
case VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA:
return "ANV";
+ case VK_DRIVER_ID_IMAGINATION_PROPRIETARY:
+ return "PowerVR";
+ case VK_DRIVER_ID_QUALCOMM_PROPRIETARY:
+ return "Qualcomm";
+ case VK_DRIVER_ID_ARM_PROPRIETARY:
+ return "Mali";
+ case VK_DRIVER_ID_GOOGLE_SWIFTSHADER:
+ return "SwiftShader";
+ case VK_DRIVER_ID_BROADCOM_PROPRIETARY:
+ return "Broadcom";
case VK_DRIVER_ID_MESA_LLVMPIPE:
- return "LAVAPIPE";
+ return "Lavapipe";
+ case VK_DRIVER_ID_MOLTENVK:
+ return "MoltenVK";
+ case VK_DRIVER_ID_VERISILICON_PROPRIETARY:
+ return "Vivante";
+ case VK_DRIVER_ID_MESA_TURNIP:
+ return "Turnip";
+ case VK_DRIVER_ID_MESA_V3DV:
+ return "V3DV";
+ case VK_DRIVER_ID_MESA_PANVK:
+ return "PanVK";
+ case VK_DRIVER_ID_MESA_VENUS:
+ return "Venus";
+ case VK_DRIVER_ID_MESA_DOZEN:
+ return "Dozen";
+ case VK_DRIVER_ID_MESA_NVK:
+ return "NVK";
+ case VK_DRIVER_ID_IMAGINATION_OPEN_SOURCE_MESA:
+ return "PVR";
+ // case VK_DRIVER_ID_MESA_AGXV:
+ // return "Asahi";
default:
return properties.driver.driverName;
}
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 2afa72140..ed5750155 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -30,7 +30,6 @@
#include <QSize>
#include <QStringLiteral>
#include <QSurfaceFormat>
-#include <QTimer>
#include <QWindow>
#include <QtCore/qobjectdefs.h>
@@ -66,6 +65,8 @@ class QObject;
class QPaintEngine;
class QSurface;
+constexpr int default_mouse_constrain_timeout = 10;
+
EmuThread::EmuThread(Core::System& system) : m_system{system} {}
EmuThread::~EmuThread() = default;
@@ -304,6 +305,9 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
Qt::QueuedConnection);
connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection);
connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged);
+
+ mouse_constrain_timer.setInterval(default_mouse_constrain_timeout);
+ connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse);
}
void GRenderWindow::ExecuteProgram(std::size_t program_index) {
@@ -393,6 +397,22 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
QWidget::closeEvent(event);
}
+void GRenderWindow::leaveEvent(QEvent* event) {
+ if (Settings::values.mouse_panning) {
+ const QRect& rect = QWidget::geometry();
+ QPoint position = QCursor::pos();
+
+ qint32 x = qBound(rect.left(), position.x(), rect.right());
+ qint32 y = qBound(rect.top(), position.y(), rect.bottom());
+ // Only start the timer if the mouse has left the window bound.
+ // The leave event is also triggered when the window looses focus.
+ if (x != position.x() || y != position.y()) {
+ mouse_constrain_timer.start();
+ }
+ event->accept();
+ }
+}
+
int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) {
static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = {
std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A},
@@ -658,10 +678,19 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y);
+ // Center mouse for mouse panning
if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
}
+ // Constrain mouse for mouse emulation with mouse panning
+ if (Settings::values.mouse_panning && Settings::values.mouse_enabled) {
+ const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y);
+ QCursor::setPos(mapToGlobal(
+ QPoint{static_cast<int>(clamped_mouse_x), static_cast<int>(clamped_mouse_y)}));
+ }
+
+ mouse_constrain_timer.stop();
emit MouseActivity();
}
@@ -675,6 +704,31 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
input_subsystem->GetMouse()->ReleaseButton(button);
}
+void GRenderWindow::ConstrainMouse() {
+ if (emu_thread == nullptr || !Settings::values.mouse_panning) {
+ mouse_constrain_timer.stop();
+ return;
+ }
+ if (!this->isActiveWindow()) {
+ mouse_constrain_timer.stop();
+ return;
+ }
+
+ if (Settings::values.mouse_enabled) {
+ const auto pos = mapFromGlobal(QCursor::pos());
+ const int new_pos_x = std::clamp(pos.x(), 0, width());
+ const int new_pos_y = std::clamp(pos.y(), 0, height());
+
+ QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y}));
+ return;
+ }
+
+ const int center_x = width() / 2;
+ const int center_y = height() / 2;
+
+ QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
+}
+
void GRenderWindow::wheelEvent(QWheelEvent* event) {
const int x = event->angleDelta().x();
const int y = event->angleDelta().y();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 87b23df12..60edd464c 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -17,6 +17,7 @@
#include <QString>
#include <QStringList>
#include <QThread>
+#include <QTimer>
#include <QWidget>
#include <qglobal.h>
#include <qnamespace.h>
@@ -38,7 +39,6 @@ class QMouseEvent;
class QObject;
class QResizeEvent;
class QShowEvent;
-class QTimer;
class QTouchEvent;
class QWheelEvent;
@@ -166,6 +166,7 @@ public:
std::pair<u32, u32> ScaleTouch(const QPointF& pos) const;
void closeEvent(QCloseEvent* event) override;
+ void leaveEvent(QEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
@@ -229,6 +230,7 @@ private:
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
+ void ConstrainMouse();
void RequestCameraCapture();
void OnCameraCapture(int requestId, const QImage& img);
@@ -268,6 +270,8 @@ private:
std::unique_ptr<QTimer> camera_timer;
#endif
+ QTimer mouse_constrain_timer;
+
Core::System& system;
protected:
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp
index a51359903..7e16cf17d 100644
--- a/src/yuzu/configuration/configure_cpu.cpp
+++ b/src/yuzu/configuration/configure_cpu.cpp
@@ -27,6 +27,13 @@ ConfigureCpu::ConfigureCpu(const Core::System& system_,
connect(accuracy_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureCpu::UpdateGroup);
+
+ connect(backend_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
+ &ConfigureCpu::UpdateGroup);
+
+#ifdef HAS_NCE
+ ui->backend_group->setVisible(true);
+#endif
}
ConfigureCpu::~ConfigureCpu() = default;
@@ -34,6 +41,7 @@ ConfigureCpu::~ConfigureCpu() = default;
void ConfigureCpu::SetConfiguration() {}
void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
auto* accuracy_layout = ui->widget_accuracy->layout();
+ auto* backend_layout = ui->widget_backend->layout();
auto* unsafe_layout = ui->unsafe_widget->layout();
std::map<u32, QWidget*> unsafe_hold{};
@@ -62,6 +70,9 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
// Keep track of cpu_accuracy combobox to display/hide the unsafe settings
accuracy_layout->addWidget(widget);
accuracy_combobox = widget->combobox;
+ } else if (setting->Id() == Settings::values.cpu_backend.Id()) {
+ backend_layout->addWidget(widget);
+ backend_combobox = widget->combobox;
} else {
// Presently, all other settings here are unsafe checkboxes
unsafe_hold.insert({setting->Id(), widget});
@@ -73,6 +84,7 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) {
}
UpdateGroup(accuracy_combobox->currentIndex());
+ UpdateGroup(backend_combobox->currentIndex());
}
void ConfigureCpu::UpdateGroup(int index) {
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h
index 61a6de7aa..a102b4c1f 100644
--- a/src/yuzu/configuration/configure_cpu.h
+++ b/src/yuzu/configuration/configure_cpu.h
@@ -49,4 +49,5 @@ private:
std::vector<std::function<void(bool)>> apply_funcs{};
QComboBox* accuracy_combobox;
+ QComboBox* backend_combobox;
};
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui
index f734e842e..13fd43605 100644
--- a/src/yuzu/configuration/configure_cpu.ui
+++ b/src/yuzu/configuration/configure_cpu.ui
@@ -60,6 +60,36 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="backend_group">
+ <property name="title">
+ <string>CPU Backend</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QWidget" name="widget_backend" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout1">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="unsafe_group">
<property name="title">
<string>Unsafe CPU Optimization Settings</string>
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index c727fadd1..701b895e7 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -36,12 +36,29 @@ ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::SetConfiguration() {}
void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
- QLayout& layout = *ui->general_widget->layout();
+ QLayout& general_layout = *ui->general_widget->layout();
+ QLayout& linux_layout = *ui->linux_widget->layout();
- std::map<u32, QWidget*> hold{};
+ std::map<u32, QWidget*> general_hold{};
+ std::map<u32, QWidget*> linux_hold{};
- for (const auto setting :
- UISettings::values.linkage.by_category[Settings::Category::UiGeneral]) {
+ std::vector<Settings::BasicSetting*> settings;
+
+ auto push = [&settings](auto& list) {
+ for (auto setting : list) {
+ settings.push_back(setting);
+ }
+ };
+
+ push(UISettings::values.linkage.by_category[Settings::Category::UiGeneral]);
+ push(Settings::values.linkage.by_category[Settings::Category::Linux]);
+
+ // Only show Linux group on Unix
+#ifndef __unix__
+ ui->LinuxGroupBox->setVisible(false);
+#endif
+
+ for (const auto setting : settings) {
auto* widget = builder.BuildWidget(setting, apply_funcs);
if (widget == nullptr) {
@@ -52,11 +69,23 @@ void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
continue;
}
- hold.emplace(setting->Id(), widget);
+ switch (setting->GetCategory()) {
+ case Settings::Category::UiGeneral:
+ general_hold.emplace(setting->Id(), widget);
+ break;
+ case Settings::Category::Linux:
+ linux_hold.emplace(setting->Id(), widget);
+ break;
+ default:
+ widget->deleteLater();
+ }
}
- for (const auto& [id, widget] : hold) {
- layout.addWidget(widget);
+ for (const auto& [id, widget] : general_hold) {
+ general_layout.addWidget(widget);
+ }
+ for (const auto& [id, widget] : linux_hold) {
+ linux_layout.addWidget(widget);
}
}
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index a10e7d3a5..ef20891a3 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -47,6 +47,33 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="LinuxGroupBox">
+ <property name="title">
+ <string>Linux</string>
+ </property>
+ <layout class="QVBoxLayout" name="LinuxVerticalLayout_1">
+ <item>
+ <widget class="QWidget" name="linux_widget" native="true">
+ <layout class="QVBoxLayout" name="LinuxVerticalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index 2a735836e..04b771129 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -57,7 +57,7 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox">
+ <widget class="QGroupBox" name="coreGroup">
<property name="title">
<string>Core</string>
</property>
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index a7b5def32..7e908924c 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -44,6 +44,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
// Cpu
INSERT(Settings, cpu_accuracy, tr("Accuracy:"), QStringLiteral());
+ INSERT(Settings, cpu_backend, tr("Backend:"), QStringLiteral());
// Cpu Debug
@@ -176,6 +177,9 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"),
QStringLiteral());
+ // Linux
+ INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QStringLiteral());
+
// Ui Debugging
// Ui Multiplayer
@@ -240,6 +244,11 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
PAIR(CpuAccuracy, Unsafe, tr("Unsafe")),
PAIR(CpuAccuracy, Paranoid, tr("Paranoid (disables most optimizations)")),
}});
+ translations->insert({Settings::EnumMetadata<Settings::CpuBackend>::Index(),
+ {
+ PAIR(CpuBackend, Dynarmic, tr("Dynarmic")),
+ PAIR(CpuBackend, Nce, tr("NCE")),
+ }});
translations->insert({Settings::EnumMetadata<Settings::FullscreenMode>::Index(),
{
PAIR(FullscreenMode, Borderless, tr("Borderless Windowed")),
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index dcf68460a..b056c3717 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -17,6 +17,7 @@
#ifdef __unix__
#include <csignal>
#include <sys/socket.h>
+#include "common/linux/gamemode.h"
#endif
#include <boost/container/flat_set.hpp>
@@ -187,7 +188,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
#endif
constexpr int default_mouse_hide_timeout = 2500;
-constexpr int default_mouse_center_timeout = 10;
constexpr int default_input_update_timeout = 1;
constexpr size_t CopyBufferSize = 1_MiB;
@@ -320,6 +320,7 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
provider{std::make_unique<FileSys::ManualContentProvider>()} {
#ifdef __unix__
SetupSigInterrupts();
+ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue());
#endif
system->Initialize();
@@ -437,9 +438,6 @@ GMainWindow::GMainWindow(std::unique_ptr<QtConfig> config_, bool has_broken_vulk
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
- mouse_center_timer.setInterval(default_mouse_center_timeout);
- connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor);
-
update_input_timer.setInterval(default_input_update_timeout);
connect(&update_input_timer, &QTimer::timeout, this, &GMainWindow::UpdateInputDrivers);
update_input_timer.start();
@@ -1376,14 +1374,6 @@ void GMainWindow::InitializeHotkeys() {
}
});
connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
- if (Settings::values.mouse_enabled) {
- Settings::values.mouse_panning = false;
- QMessageBox::warning(
- this, tr("Emulated mouse is enabled"),
- tr("Real mouse input and mouse panning are incompatible. Please disable the "
- "emulated mouse in input advanced settings to allow mouse panning."));
- return;
- }
Settings::values.mouse_panning = !Settings::values.mouse_panning;
if (Settings::values.mouse_panning) {
render_window->installEventFilter(render_window);
@@ -2132,6 +2122,10 @@ void GMainWindow::OnEmulationStopped() {
discord_rpc->Update();
+#ifdef __unix__
+ Common::Linux::StopGamemode();
+#endif
+
// The emulation is stopped, so closing the window or not does not matter anymore
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
@@ -2719,11 +2713,6 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
}
const auto base_romfs = base_nca->GetRomFS();
- if (!base_romfs) {
- failed();
- return;
- }
-
const auto dump_dir =
target == DumpRomFSTarget::Normal
? Common::FS::GetYuzuPath(Common::FS::YuzuPath::DumpDir)
@@ -3514,6 +3503,10 @@ void GMainWindow::OnStartGame() {
play_time_manager->Start();
discord_rpc->Update();
+
+#ifdef __unix__
+ Common::Linux::StartGamemode();
+#endif
}
void GMainWindow::OnRestartGame() {
@@ -3534,6 +3527,10 @@ void GMainWindow::OnPauseGame() {
play_time_manager->Stop();
UpdateMenuState();
AllowOSSleep();
+
+#ifdef __unix__
+ Common::Linux::StopGamemode();
+#endif
}
void GMainWindow::OnPauseContinueGame() {
@@ -3815,6 +3812,9 @@ void GMainWindow::OnConfigure() {
const auto old_theme = UISettings::values.theme;
const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
const auto old_language_index = Settings::values.language_index.GetValue();
+#ifdef __unix__
+ const bool old_gamemode = Settings::values.enable_gamemode.GetValue();
+#endif
Settings::SetConfiguringGlobal(true);
ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(),
@@ -3876,6 +3876,11 @@ void GMainWindow::OnConfigure() {
if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) {
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
}
+#ifdef __unix__
+ if (Settings::values.enable_gamemode.GetValue() != old_gamemode) {
+ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue());
+ }
+#endif
if (!multiplayer_state->IsHostingPublicRoom()) {
multiplayer_state->UpdateCredentials();
@@ -4711,26 +4716,10 @@ void GMainWindow::ShowMouseCursor() {
}
}
-void GMainWindow::CenterMouseCursor() {
- if (emu_thread == nullptr || !Settings::values.mouse_panning) {
- mouse_center_timer.stop();
- return;
- }
- if (!this->isActiveWindow()) {
- mouse_center_timer.stop();
- return;
- }
- const int center_x = render_window->width() / 2;
- const int center_y = render_window->height() / 2;
-
- QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
-}
-
void GMainWindow::OnMouseActivity() {
if (!Settings::values.mouse_panning) {
ShowMouseCursor();
}
- mouse_center_timer.stop();
}
void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
@@ -5031,22 +5020,6 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
AcceptDropEvent(event);
}
-void GMainWindow::leaveEvent(QEvent* event) {
- if (Settings::values.mouse_panning) {
- const QRect& rect = geometry();
- QPoint position = QCursor::pos();
-
- qint32 x = qBound(rect.left(), position.x(), rect.right());
- qint32 y = qBound(rect.top(), position.y(), rect.bottom());
- // Only start the timer if the mouse has left the window bound.
- // The leave event is also triggered when the window looses focus.
- if (x != position.x() || y != position.y()) {
- mouse_center_timer.start();
- }
- event->accept();
- }
-}
-
bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
@@ -5216,6 +5189,14 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
discord_rpc->Update();
}
+#ifdef __unix__
+void GMainWindow::SetGamemodeEnabled(bool state) {
+ if (emulation_running) {
+ Common::Linux::SetGamemodeState(state);
+ }
+}
+#endif
+
void GMainWindow::changeEvent(QEvent* event) {
#ifdef __unix__
// PaletteChange event appears to only reach so far into the GUI, explicitly asking to
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index e99d58995..530e445f9 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -340,6 +340,7 @@ private:
void SetupSigInterrupts();
static void HandleSigInterrupt(int);
void OnSigInterruptNotifierActivated();
+ void SetGamemodeEnabled(bool state);
#endif
private slots:
@@ -451,7 +452,6 @@ private:
void UpdateInputDrivers();
void HideMouseCursor();
void ShowMouseCursor();
- void CenterMouseCursor();
void OpenURL(const QUrl& url);
void LoadTranslation();
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
@@ -535,7 +535,6 @@ private:
bool auto_paused = false;
bool auto_muted = false;
QTimer mouse_hide_timer;
- QTimer mouse_center_timer;
QTimer update_input_timer;
QString startup_icon_theme;
@@ -592,5 +591,4 @@ protected:
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
- void leaveEvent(QEvent* event) override;
};
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 0416d5951..a81635fa4 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -63,6 +63,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
+#ifdef __unix__
+#include "common/linux/gamemode.h"
+#endif
+
static void PrintHelp(const char* argv0) {
std::cout << "Usage: " << argv0
<< " [options] <filename>\n"
@@ -425,6 +429,10 @@ int main(int argc, char** argv) {
exit(0);
});
+#ifdef __unix__
+ Common::Linux::StartGamemode();
+#endif
+
void(system.Run());
if (system.DebuggerEnabled()) {
system.InitializeDebugger();
@@ -436,6 +444,10 @@ int main(int argc, char** argv) {
void(system.Pause());
system.ShutdownMainProcess();
+#ifdef __unix__
+ Common::Linux::StopGamemode();
+#endif
+
detached_tasks.WaitForAllTasks();
return 0;
}