Compare commits

..

1 Commits

Author SHA1 Message Date
wildcard 213a9a7813 [host_shaders] Store the value of results instead of discarding it
the function generated the value but we never stored it effectively discarding the result.
2026-02-04 22:13:11 +01:00
141 changed files with 1066 additions and 6288 deletions
+4 -5
View File
@@ -32,8 +32,8 @@ if (PLATFORM_OPENBSD)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R6/lib")
elseif (PLATFORM_NETBSD)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R7/lib")
endif()
@@ -246,12 +246,11 @@ cmake_dependent_option(YUZU_USE_BUNDLED_MOLTENVK "Download bundled MoltenVK lib"
option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF)
set(DEFAULT_ENABLE_OPENSSL ON)
if (ANDROID OR WIN32 OR APPLE OR PLATFORM_SUN OR PLATFORM_OPENBSD)
if (ANDROID OR WIN32 OR APPLE OR PLATFORM_SUN)
# - Windows defaults to the Schannel backend.
# - macOS defaults to the SecureTransport backend.
# - Android currently has no SSL backend as the NDK doesn't include any SSL
# library; a proper 'native' backend would have to go through Java.
# - Solaris and OpenBSD have too old backends
# But you can force builds for those platforms to use OpenSSL if you have
# your own copy of it.
set(DEFAULT_ENABLE_OPENSSL OFF)
@@ -264,7 +263,7 @@ endif()
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL OFF)
if (EXT_DEFAULT OR PLATFORM_SUN OR PLATFORM_OPENBSD)
if (EXT_DEFAULT OR PLATFORM_SUN)
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL ON)
endif()
-2
View File
@@ -85,8 +85,6 @@ If you have `quazip1_qt6_devel`, uninstall it. It may call `Core5Compat` on CMak
## OpenBSD
System boost doesn't have `context` (as of 7.8); so you may need to specify `-DYUZU_USE_CPM=ON -DBoost_FORCE_BUNDLED=ON`.
After configuration, you may need to modify `externals/ffmpeg/CMakeFiles/ffmpeg-build/build.make` to use `-j$(nproc)` instead of just `-j`.
`-lc++-experimental` doesn't exist in OpenBSD but the LLVM driver still tries to link against it, to solve just symlink `ln -s /usr/lib/libc++.a /usr/lib/libc++experimental.a`. Builds are currently not working due to lack of `std::jthread` and such, either compile libc++ manually or wait for ports to catch up.
+1 -1
View File
@@ -277,7 +277,7 @@ For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv
```sh
pkg_add -u
pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl2 libusb1-1.0.29
pkg_add cmake nasm git boost unzip--iconv autoconf-2.72p0 bash ffmpeg glslang gmake llvm-19.1.7p3 qt6 jq fmt nlohmann-json enet boost vulkan-utility-libraries vulkan-headers spirv-headers spirv-tools catch2 sdl2 libusb1-1.0.27
```
[Caveats](./Caveats.md#openbsd).
+5 -5
View File
@@ -82,15 +82,15 @@ You may additionally need the `Qt Extension Pack` extension if building Qt.
# Build speedup
If you have an HDD, use ramdisk (build in RAM), approximatedly you need 4GB for a full build with debug symbols:
If you have an HDD, use ramdisk (build in RAM):
```sh
mkdir /tmp/ramdisk
chmod 777 /tmp/ramdisk
sudo mkdir /tmp/ramdisk
sudo chmod 777 /tmp/ramdisk
# about 8GB needed
mount -t tmpfs -o size=4G myramdisk /tmp/ramdisk
sudo mount -t tmpfs -o size=8G myramdisk /tmp/ramdisk
cmake -B /tmp/ramdisk
cmake --build /tmp/ramdisk -- -j32
umount /tmp/ramdisk
sudo umount /tmp/ramdisk
```
# Assets and large files
@@ -614,23 +614,4 @@ object NativeLibrary {
* Updates the device power state to global variables
*/
external fun updatePowerState(percentage: Int, isCharging: Boolean, hasBattery: Boolean)
/**
* Profile manager native calls
*/
external fun getAllUsers(): Array<String>?
external fun getUserUsername(uuid: String): String?
external fun getUserCount(): Long
external fun canCreateUser(): Boolean
external fun createUser(uuid: String, username: String): Boolean
external fun updateUserUsername(uuid: String, username: String): Boolean
external fun removeUser(uuid: String): Boolean
external fun getCurrentUser(): String?
external fun setCurrentUser(uuid: String): Boolean
external fun getUserImagePath(uuid: String): String?
external fun saveUserImage(uuid: String, imagePath: String): Boolean
external fun reloadProfiles()
external fun getFirmwareAvatarCount(): Int
external fun getFirmwareAvatarImage(index: Int): ByteArray?
external fun getDefaultAccountBackupJpeg(): ByteArray
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -10,7 +10,6 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
import org.yuzu.yuzu_emu.model.Patch
import org.yuzu.yuzu_emu.model.PatchType
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
@@ -32,12 +31,7 @@ class AddonAdapter(val addonViewModel: AddonViewModel) :
binding.addonSwitch.isChecked = model.enabled
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
if (PatchType.from(model.type) == PatchType.Update && checked) {
addonViewModel.enableOnlyThisUpdate(model)
notifyDataSetChanged()
} else {
model.enabled = checked
}
model.enabled = checked
}
val deleteAction = {
@@ -1,55 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.graphics.Bitmap
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.databinding.ItemFirmwareAvatarBinding
class FirmwareAvatarAdapter(
private val avatars: List<Bitmap>,
private val onAvatarSelected: (Bitmap) -> Unit
) : RecyclerView.Adapter<FirmwareAvatarAdapter.AvatarViewHolder>() {
private var selectedPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AvatarViewHolder {
val binding = ItemFirmwareAvatarBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return AvatarViewHolder(binding)
}
override fun onBindViewHolder(holder: AvatarViewHolder, position: Int) {
holder.bind(avatars[position], position == selectedPosition)
}
override fun getItemCount(): Int = avatars.size
inner class AvatarViewHolder(
private val binding: ItemFirmwareAvatarBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(avatar: Bitmap, isSelected: Boolean) {
binding.imageAvatar.setImageBitmap(avatar)
binding.root.isChecked = isSelected
binding.root.setOnClickListener {
val previousSelected = selectedPosition
selectedPosition = bindingAdapterPosition
if (previousSelected != -1) {
notifyItemChanged(previousSelected)
}
notifyItemChanged(selectedPosition)
onAvatarSelected(avatar)
}
}
}
}
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -10,10 +7,8 @@ import android.net.Uri
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.CardFolderBinding
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
import org.yuzu.yuzu_emu.model.DirectoryType
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
@@ -36,12 +31,6 @@ class FolderAdapter(val activity: FragmentActivity, val gamesViewModel: GamesVie
path.text = Uri.parse(model.uriString).path
path.marquee()
// Set type indicator, shows below folder name, to see if DLC or Games
typeIndicator.text = when (model.type) {
DirectoryType.GAME -> activity.getString(R.string.games)
DirectoryType.EXTERNAL_CONTENT -> activity.getString(R.string.external_content)
}
buttonEdit.setOnClickListener {
GameFolderPropertiesDialogFragment.newInstance(model)
.show(
@@ -1,112 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.adapters
import android.graphics.BitmapFactory
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ListItemProfileBinding
import org.yuzu.yuzu_emu.model.UserProfile
import java.io.File
import org.yuzu.yuzu_emu.NativeLibrary
class ProfileAdapter(
private val onProfileClick: (UserProfile) -> Unit,
private val onEditClick: (UserProfile) -> Unit,
private val onDeleteClick: (UserProfile) -> Unit
) : RecyclerView.Adapter<ProfileAdapter.ProfileViewHolder>() {
private var currentUserUUID: String = ""
private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback<UserProfile>() {
override fun areItemsTheSame(oldItem: UserProfile, newItem: UserProfile): Boolean {
return oldItem.uuid == newItem.uuid
}
override fun areContentsTheSame(oldItem: UserProfile, newItem: UserProfile): Boolean {
return oldItem == newItem
}
})
fun submitList(list: List<UserProfile>) {
differ.submitList(list)
}
fun setCurrentUser(uuid: String) {
currentUserUUID = uuid
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder {
val binding = ListItemProfileBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return ProfileViewHolder(binding)
}
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) {
holder.bind(differ.currentList[position])
}
override fun getItemCount(): Int = differ.currentList.size
inner class ProfileViewHolder(private val binding: ListItemProfileBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(profile: UserProfile) {
binding.textUsername.text = profile.username
binding.textUuid.text = formatUUID(profile.uuid)
val imageFile = File(profile.imagePath)
if (imageFile.exists()) {
val bitmap = BitmapFactory.decodeFile(profile.imagePath)
binding.imageAvatar.setImageBitmap(bitmap)
} else {
val jpegData = NativeLibrary.getDefaultAccountBackupJpeg()
val bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size)
binding.imageAvatar.setImageBitmap(bitmap)
}
if (profile.uuid == currentUserUUID) {
binding.checkContainer.visibility = View.VISIBLE
} else {
binding.checkContainer.visibility = View.GONE
}
binding.root.setOnClickListener {
onProfileClick(profile)
}
binding.buttonEdit.setOnClickListener {
onEditClick(profile)
}
binding.buttonDelete.setOnClickListener {
onDeleteClick(profile)
}
}
private fun formatUUID(uuid: String): String {
if (uuid.length != 32) return uuid
return buildString {
append(uuid.substring(0, 8))
append("-")
append(uuid.substring(8, 12))
append("-")
append(uuid.substring(12, 16))
append("-")
append(uuid.substring(16, 20))
append("-")
append(uuid.substring(20, 32))
}
}
}
}
@@ -1,459 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.FirmwareAvatarAdapter
import org.yuzu.yuzu_emu.databinding.FragmentEditUserDialogBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.ProfileUtils
import org.yuzu.yuzu_emu.model.UserProfile
import java.io.File
import java.io.FileOutputStream
import androidx.core.graphics.scale
import androidx.core.graphics.createBitmap
class EditUserDialogFragment : Fragment() {
private var _binding: FragmentEditUserDialogBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private var currentUUID: String = ""
private var isEditMode = false
private var selectedImageUri: Uri? = null
private var selectedFirmwareAvatar: Bitmap? = null
private var hasCustomImage = false
private var revertedToDefault = false
companion object {
private const val ARG_UUID = "uuid"
private const val ARG_USERNAME = "username"
fun newInstance(profile: UserProfile?): EditUserDialogFragment {
val fragment = EditUserDialogFragment()
profile?.let {
val args = Bundle()
args.putString(ARG_UUID, it.uuid)
args.putString(ARG_USERNAME, it.username)
fragment.arguments = args
}
return fragment
}
}
private val imagePickerLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let { uri ->
selectedImageUri = uri
loadImage(uri)
hasCustomImage = true
binding.buttonRevertImage.visibility = View.VISIBLE
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentEditUserDialogBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setStatusBarShadeVisibility(visible = false)
val existingUUID = arguments?.getString(ARG_UUID)
val existingUsername = arguments?.getString(ARG_USERNAME)
if (existingUUID != null && existingUsername != null) {
isEditMode = true
currentUUID = existingUUID
binding.toolbarNewUser.title = getString(R.string.profile_edit_user)
binding.editUsername.setText(existingUsername)
binding.textUuid.text = formatUUID(existingUUID)
binding.buttonGenerateUuid.visibility = View.GONE
val imagePath = NativeLibrary.getUserImagePath(existingUUID)
val imageFile = File(imagePath)
if (imageFile.exists()) {
val bitmap = BitmapFactory.decodeFile(imagePath)
binding.imageUserAvatar.setImageBitmap(bitmap)
hasCustomImage = true
binding.buttonRevertImage.visibility = View.VISIBLE
} else {
loadDefaultAvatar()
}
} else {
isEditMode = false
currentUUID = ProfileUtils.generateRandomUUID()
binding.toolbarNewUser.title = getString(R.string.profile_new_user)
binding.textUuid.text = formatUUID(currentUUID)
loadDefaultAvatar()
}
binding.toolbarNewUser.setNavigationOnClickListener {
findNavController().popBackStack()
}
binding.editUsername.doAfterTextChanged {
validateInput()
}
binding.buttonGenerateUuid.setOnClickListener {
currentUUID = ProfileUtils.generateRandomUUID()
binding.textUuid.text = formatUUID(currentUUID)
}
binding.buttonSelectImage.setOnClickListener {
selectImage()
}
binding.buttonRevertImage.setOnClickListener {
revertToDefaultImage()
}
if (NativeLibrary.isFirmwareAvailable()) {
binding.buttonFirmwareAvatars.visibility = View.VISIBLE
binding.buttonFirmwareAvatars.setOnClickListener {
showFirmwareAvatarPicker()
}
}
binding.buttonSave.setOnClickListener {
saveUser()
}
binding.buttonCancel.setOnClickListener {
findNavController().popBackStack()
}
validateInput()
setInsets()
}
private fun showFirmwareAvatarPicker() {
val dialogView = LayoutInflater.from(requireContext())
.inflate(R.layout.dialog_firmware_avatar_picker, null)
val gridAvatars = dialogView.findViewById<RecyclerView>(R.id.grid_avatars)
val progressLoading = dialogView.findViewById<View>(R.id.progress_loading)
val textEmpty = dialogView.findViewById<View>(R.id.text_empty)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.profile_firmware_avatars)
.setView(dialogView)
.setNegativeButton(android.R.string.cancel, null)
.create()
dialog.show()
viewLifecycleOwner.lifecycleScope.launch {
val avatars = withContext(Dispatchers.IO) {
loadFirmwareAvatars()
}
if (avatars.isEmpty()) {
progressLoading.visibility = View.GONE
textEmpty.visibility = View.VISIBLE
} else {
progressLoading.visibility = View.GONE
gridAvatars.visibility = View.VISIBLE
val adapter = FirmwareAvatarAdapter(avatars) { selectedAvatar ->
val scaledBitmap = selectedAvatar.scale(256, 256)
binding.imageUserAvatar.setImageBitmap(scaledBitmap)
selectedFirmwareAvatar = scaledBitmap
hasCustomImage = true
binding.buttonRevertImage.visibility = View.VISIBLE
dialog.dismiss()
}
gridAvatars.apply {
layoutManager = GridLayoutManager(requireContext(), 4)
this.adapter = adapter
}
}
}
}
private fun loadFirmwareAvatars(): List<Bitmap> {
val avatars = mutableListOf<Bitmap>()
val count = NativeLibrary.getFirmwareAvatarCount()
for (i in 0 until count) {
try {
val imageData = NativeLibrary.getFirmwareAvatarImage(i) ?: continue
val argbData = IntArray(256 * 256)
for (pixel in 0 until 256 * 256) {
val offset = pixel * 4
val r = imageData[offset].toInt() and 0xFF
val g = imageData[offset + 1].toInt() and 0xFF
val b = imageData[offset + 2].toInt() and 0xFF
val a = imageData[offset + 3].toInt() and 0xFF
argbData[pixel] = (a shl 24) or (r shl 16) or (g shl 8) or b
}
val bitmap = Bitmap.createBitmap(argbData, 256, 256, Bitmap.Config.ARGB_8888)
avatars.add(bitmap)
} catch (e: Exception) {
continue
}
}
return avatars
}
private fun formatUUID(uuid: String): String {
if (uuid.length != 32) return uuid
return buildString {
append(uuid.substring(0, 8))
append("-")
append(uuid.substring(8, 12))
append("-")
append(uuid.substring(12, 16))
append("-")
append(uuid.substring(16, 20))
append("-")
append(uuid.substring(20, 32))
}
}
private fun validateInput() {
val username = binding.editUsername.text.toString()
val isValid = username.isNotEmpty() && username.length <= 32
binding.buttonSave.isEnabled = isValid
}
private fun selectImage() {
val intent = Intent(Intent.ACTION_PICK).apply {
type = "image/*"
}
imagePickerLauncher.launch(intent)
}
private fun loadImage(uri: Uri) {
try {
val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val source = ImageDecoder.createSource(requireContext().contentResolver, uri)
ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
decoder.setTargetSampleSize(1)
}
} else {
@Suppress("DEPRECATION")
MediaStore.Images.Media.getBitmap(requireContext().contentResolver, uri)
}
val croppedBitmap = centerCropBitmap(bitmap, 256, 256)
binding.imageUserAvatar.setImageBitmap(croppedBitmap)
} catch (e: Exception) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.error)
.setMessage(getString(R.string.profile_image_load_error, e.message))
.setPositiveButton(android.R.string.ok, null)
.show()
}
}
private fun loadDefaultAvatar() {
val jpegData = NativeLibrary.getDefaultAccountBackupJpeg()
val bitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.size)
binding.imageUserAvatar.setImageBitmap(bitmap)
hasCustomImage = false
binding.buttonRevertImage.visibility = View.GONE
}
private fun revertToDefaultImage() {
selectedImageUri = null
selectedFirmwareAvatar = null
revertedToDefault = true
loadDefaultAvatar()
}
private fun saveUser() {
val username = binding.editUsername.text.toString()
if (isEditMode) {
if (NativeLibrary.updateUserUsername(currentUUID, username)) {
saveImageIfNeeded()
findNavController().popBackStack()
} else {
showError(getString(R.string.profile_update_failed))
}
} else {
if (NativeLibrary.createUser(currentUUID, username)) {
saveImageIfNeeded()
findNavController().popBackStack()
} else {
showError(getString(R.string.profile_create_failed))
}
}
}
private fun saveImageIfNeeded() {
if (revertedToDefault && isEditMode) {
val imagePath = NativeLibrary.getUserImagePath(currentUUID)
if (imagePath != null) {
val imageFile = File(imagePath)
if (imageFile.exists()) {
imageFile.delete()
}
}
return
}
if (!hasCustomImage) {
return
}
try {
val bitmapToSave: Bitmap? = when {
selectedFirmwareAvatar != null -> selectedFirmwareAvatar
selectedImageUri != null -> {
val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val source = ImageDecoder.createSource(
requireContext().contentResolver,
selectedImageUri!!
)
ImageDecoder.decodeBitmap(source)
} else {
@Suppress("DEPRECATION")
MediaStore.Images.Media.getBitmap(
requireContext().contentResolver,
selectedImageUri
)
}
centerCropBitmap(bitmap, 256, 256)
}
else -> null
}
if (bitmapToSave == null) {
return
}
val tempFile = File(requireContext().cacheDir, "temp_avatar_${currentUUID}.jpg")
FileOutputStream(tempFile).use { out ->
bitmapToSave.compress(Bitmap.CompressFormat.JPEG, 100, out)
}
NativeLibrary.saveUserImage(currentUUID, tempFile.absolutePath)
tempFile.delete()
} catch (e: Exception) {
showError(getString(R.string.profile_image_save_error, e.message))
}
}
private fun centerCropBitmap(source: Bitmap, targetWidth: Int, targetHeight: Int): Bitmap {
val sourceWidth = source.width
val sourceHeight = source.height
val scale = maxOf(
targetWidth.toFloat() / sourceWidth,
targetHeight.toFloat() / sourceHeight
)
val scaledWidth = (sourceWidth * scale).toInt()
val scaledHeight = (sourceHeight * scale).toInt()
val scaledBitmap = source.scale(scaledWidth, scaledHeight)
val x = (scaledWidth - targetWidth) / 2
val y = (scaledHeight - targetHeight) / 2
return Bitmap.createBitmap(scaledBitmap, x, y, targetWidth, targetHeight)
}
private fun showError(message: String) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.error)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show()
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
val leftInset = barInsets.left + cutoutInsets.left
val topInset = cutoutInsets.top
val rightInset = barInsets.right + cutoutInsets.right
val bottomInset = barInsets.bottom + cutoutInsets.bottom
binding.appbar.updatePadding(
left = leftInset,
top = topInset,
right = rightInset
)
binding.scrollContent.updatePadding(
left = leftInset,
right = rightInset
)
binding.buttonContainer.updatePadding(
left = leftInset,
right = rightInset,
bottom = bottomInset
)
windowInsets
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -110,7 +110,7 @@ class FreedrenoSettingsFragment : Fragment() {
val commonVars = listOf(
"TU_DEBUG", "FD_MESA_DEBUG", "IR3_SHADER_DEBUG",
"FD_RD_DUMP", "FD_RD_DUMP_FRAMES", "FD_RD_DUMP_TESTNAME",
"TU_BREADCRUMBS", "FD_DEV_FEATURES"
"TU_BREADCRUMBS"
)
for (varName in commonVars) {
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -9,13 +6,11 @@ package org.yuzu.yuzu_emu.fragments
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
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.DirectoryType
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.utils.NativeConfig
@@ -30,18 +25,14 @@ class GameFolderPropertiesDialogFragment : DialogFragment() {
val binding = DialogFolderPropertiesBinding.inflate(layoutInflater)
val gameDir = requireArguments().parcelable<GameDir>(GAME_DIR)!!
// Hide deepScan for external content, do automatically
if (gameDir.type == DirectoryType.EXTERNAL_CONTENT) {
binding.deepScanSwitch.visibility = View.GONE
} else {
// Restore checkbox state for game dirs
binding.deepScanSwitch.isChecked =
savedInstanceState?.getBoolean(DEEP_SCAN) ?: gameDir.deepScan
// 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
binding.deepScanSwitch.setOnClickListener {
deepScan = binding.deepScanSwitch.isChecked
}
}
return MaterialAlertDialogBuilder(requireContext())
@@ -50,10 +41,8 @@ class GameFolderPropertiesDialogFragment : DialogFragment() {
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
val folderIndex = gamesViewModel.folders.value.indexOf(gameDir)
if (folderIndex != -1) {
if (gameDir.type == DirectoryType.GAME) {
gamesViewModel.folders.value[folderIndex].deepScan =
binding.deepScanSwitch.isChecked
}
gamesViewModel.folders.value[folderIndex].deepScan =
binding.deepScanSwitch.isChecked
gamesViewModel.updateGameDirs()
}
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -15,14 +15,11 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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.DirectoryType
import org.yuzu.yuzu_emu.model.GameDir
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -76,25 +73,7 @@ class GameFoldersFragment : Fragment() {
val mainActivity = requireActivity() as MainActivity
binding.buttonAdd.setOnClickListener {
// Show a model to choose between Game and External Content
val options = arrayOf(
getString(R.string.games),
getString(R.string.external_content)
)
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.add_folders)
.setItems(options) { _, which ->
when (which) {
0 -> { // Game Folder
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
}
1 -> { // External Content Folder
mainActivity.getExternalContentDirectory.launch(null)
}
}
}
.show()
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
}
setInsets()
@@ -1,9 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
import android.Manifest
@@ -117,17 +114,6 @@ class HomeSettingsFragment : Fragment() {
}
)
)
add(
HomeSetting(
R.string.profile_manager,
R.string.profile_manager_description,
R.drawable.ic_account_circle,
{
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_profileManagerFragment)
}
)
)
add(
HomeSetting(
R.string.gpu_driver_manager,
@@ -1,190 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
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.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.adapters.ProfileAdapter
import org.yuzu.yuzu_emu.databinding.FragmentProfileManagerBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.UserProfile
import org.yuzu.yuzu_emu.utils.NativeConfig
class ProfileManagerFragment : Fragment() {
private var _binding: FragmentProfileManagerBinding? = null
private val binding get() = _binding!!
private val homeViewModel: HomeViewModel by activityViewModels()
private lateinit var profileAdapter: ProfileAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentProfileManagerBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
homeViewModel.setStatusBarShadeVisibility(visible = false)
binding.toolbarProfiles.setNavigationOnClickListener {
findNavController().popBackStack()
}
setupRecyclerView()
loadProfiles()
binding.buttonAddUser.setOnClickListener {
if (NativeLibrary.canCreateUser()) {
findNavController().navigate(R.id.action_profileManagerFragment_to_newUserDialog)
} else {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.profile_max_users_title)
.setMessage(R.string.profile_max_users_message)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}
setInsets()
}
override fun onResume() {
super.onResume()
loadProfiles()
}
private fun setupRecyclerView() {
profileAdapter = ProfileAdapter(
onProfileClick = { profile -> selectProfile(profile) },
onEditClick = { profile -> editProfile(profile) },
onDeleteClick = { profile -> confirmDeleteProfile(profile) }
)
binding.listProfiles.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = profileAdapter
}
}
private fun loadProfiles() {
val profiles = mutableListOf<UserProfile>()
val userUUIDs = NativeLibrary.getAllUsers() ?: emptyArray()
val currentUserUUID = NativeLibrary.getCurrentUser()
for (uuid in userUUIDs) {
if (uuid.isNotEmpty()) {
val username = NativeLibrary.getUserUsername(uuid)
if (!username.isNullOrEmpty()) {
val imagePath = NativeLibrary.getUserImagePath(uuid) ?: ""
profiles.add(UserProfile(uuid, username, imagePath))
}
}
}
profileAdapter.submitList(profiles)
profileAdapter.setCurrentUser(currentUserUUID ?: "")
binding.buttonAddUser.isEnabled = NativeLibrary.canCreateUser()
}
private fun selectProfile(profile: UserProfile) {
if (NativeLibrary.setCurrentUser(profile.uuid)) {
loadProfiles()
}
}
private fun editProfile(profile: UserProfile) {
val bundle = Bundle().apply {
putString("uuid", profile.uuid)
putString("username", profile.username)
}
findNavController().navigate(R.id.action_profileManagerFragment_to_newUserDialog, bundle)
}
private fun confirmDeleteProfile(profile: UserProfile) {
val currentUser = NativeLibrary.getCurrentUser()
val isCurrentUser = profile.uuid == currentUser
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.profile_delete_confirm_title)
.setMessage(
if (isCurrentUser) {
getString(R.string.profile_delete_current_user_message, profile.username)
} else {
getString(R.string.profile_delete_confirm_message, profile.username)
}
)
.setPositiveButton(R.string.profile_delete) { _, _ ->
deleteProfile(profile)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun deleteProfile(profile: UserProfile) {
val currentUser = NativeLibrary.getCurrentUser()
if (!currentUser.isNullOrEmpty() && profile.uuid == currentUser) {
val users = NativeLibrary.getAllUsers() ?: emptyArray()
for (uuid in users) {
if (uuid.isNotEmpty() && uuid != profile.uuid) {
NativeLibrary.setCurrentUser(uuid)
break
}
}
}
if (NativeLibrary.removeUser(profile.uuid)) {
loadProfiles()
}
}
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 fabLayoutParams = binding.buttonAddUser.layoutParams as ViewGroup.MarginLayoutParams
fabLayoutParams.leftMargin = leftInsets + 24
fabLayoutParams.rightMargin = rightInsets + 24
fabLayoutParams.bottomMargin = barInsets.bottom + 24
binding.buttonAddUser.layoutParams = fabLayoutParams
windowInsets
}
}
override fun onDestroyView() {
super.onDestroyView()
NativeConfig.saveGlobalConfig()
_binding = null
}
}
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -51,68 +48,16 @@ class AddonViewModel : ViewModel() {
?: emptyArray()
).toMutableList()
patchList.sortBy { it.name }
// Ensure only one update is enabled
ensureSingleUpdateEnabled(patchList)
removeDuplicates(patchList)
_patchList.value = patchList
isRefreshing.set(false)
}
}
}
private fun ensureSingleUpdateEnabled(patchList: MutableList<Patch>) {
val updates = patchList.filter { PatchType.from(it.type) == PatchType.Update }
if (updates.size <= 1) {
return
}
val enabledUpdates = updates.filter { it.enabled }
if (enabledUpdates.size > 1) {
val nandOrSdmcEnabled = enabledUpdates.find {
it.name.contains("(NAND)") || it.name.contains("(SDMC)")
}
val updateToKeep = nandOrSdmcEnabled ?: enabledUpdates.first()
for (patch in patchList) {
if (PatchType.from(patch.type) == PatchType.Update) {
patch.enabled = (patch === updateToKeep)
}
}
}
}
private fun removeDuplicates(patchList: MutableList<Patch>) {
val seen = mutableSetOf<String>()
val iterator = patchList.iterator()
while (iterator.hasNext()) {
val patch = iterator.next()
val key = "${patch.name}|${patch.version}|${patch.type}"
if (seen.contains(key)) {
iterator.remove()
} else {
seen.add(key)
}
}
}
fun setAddonToDelete(patch: Patch?) {
_addonToDelete.value = patch
}
fun enableOnlyThisUpdate(selectedPatch: Patch) {
val currentList = _patchList.value
for (patch in currentList) {
if (PatchType.from(patch.type) == PatchType.Update) {
patch.enabled = (patch === selectedPatch)
}
}
}
fun onDeleteAddon(patch: Patch) {
when (PatchType.from(patch.type)) {
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
@@ -127,27 +72,13 @@ class AddonViewModel : ViewModel() {
return
}
// Check if there are multiple update versions
val updates = _patchList.value.filter { PatchType.from(it.type) == PatchType.Update }
val hasMultipleUpdates = updates.size > 1
NativeConfig.setDisabledAddons(
game!!.programId,
_patchList.value.mapNotNull {
if (it.enabled) {
null
} else {
if (PatchType.from(it.type) == PatchType.Update) {
if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) {
it.name
} else if (hasMultipleUpdates) {
"Update@${it.numericVersion}"
} else {
it.name
}
} else {
it.name
}
it.name
}
}.toTypedArray()
)
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -12,14 +9,5 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class GameDir(
val uriString: String,
var deepScan: Boolean,
val type: DirectoryType = DirectoryType.GAME
) : Parcelable {
// Needed for JNI backward compatability
constructor(uriString: String, deepScan: Boolean) : this(uriString, deepScan, DirectoryType.GAME)
}
enum class DirectoryType {
GAME,
EXTERNAL_CONTENT
}
var deepScan: Boolean
) : Parcelable
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.model
@@ -56,7 +56,7 @@ class GamesViewModel : ViewModel() {
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
getGameDirsAndExternalContent()
getGameDirs()
reloadGames(directoriesChanged = false, firstStartup = true)
}
@@ -144,19 +144,11 @@ class GamesViewModel : ViewModel() {
fun addFolder(gameDir: GameDir, savedFromGameFragment: Boolean) =
viewModelScope.launch {
withContext(Dispatchers.IO) {
when (gameDir.type) {
DirectoryType.GAME -> {
NativeConfig.addGameDir(gameDir)
val isFirstTimeSetup = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getBoolean(org.yuzu.yuzu_emu.features.settings.model.Settings.PREF_FIRST_APP_LAUNCH, true)
getGameDirsAndExternalContent(!isFirstTimeSetup)
}
DirectoryType.EXTERNAL_CONTENT -> {
addExternalContentDir(gameDir.uriString)
NativeConfig.saveGlobalConfig()
getGameDirsAndExternalContent()
}
}
NativeConfig.addGameDir(gameDir)
val isFirstTimeSetup = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
.getBoolean(org.yuzu.yuzu_emu.features.settings.model.Settings.PREF_FIRST_APP_LAUNCH, true)
getGameDirs(!isFirstTimeSetup)
}
if (savedFromGameFragment) {
@@ -176,15 +168,8 @@ class GamesViewModel : ViewModel() {
val removedDirIndex = gameDirs.indexOf(gameDir)
if (removedDirIndex != -1) {
gameDirs.removeAt(removedDirIndex)
when (gameDir.type) {
DirectoryType.GAME -> {
NativeConfig.setGameDirs(gameDirs.filter { it.type == DirectoryType.GAME }.toTypedArray())
}
DirectoryType.EXTERNAL_CONTENT -> {
removeExternalContentDir(gameDir.uriString)
}
}
getGameDirsAndExternalContent()
NativeConfig.setGameDirs(gameDirs.toTypedArray())
getGameDirs()
}
}
}
@@ -192,16 +177,15 @@ class GamesViewModel : ViewModel() {
fun updateGameDirs() =
viewModelScope.launch {
withContext(Dispatchers.IO) {
val gameDirs = _folders.value.filter { it.type == DirectoryType.GAME }
NativeConfig.setGameDirs(gameDirs.toTypedArray())
getGameDirsAndExternalContent()
NativeConfig.setGameDirs(_folders.value.toTypedArray())
getGameDirs()
}
}
fun onOpenGameFoldersFragment() =
viewModelScope.launch {
withContext(Dispatchers.IO) {
getGameDirsAndExternalContent()
getGameDirs()
}
}
@@ -209,36 +193,16 @@ class GamesViewModel : ViewModel() {
NativeConfig.saveGlobalConfig()
viewModelScope.launch {
withContext(Dispatchers.IO) {
getGameDirsAndExternalContent(true)
getGameDirs(true)
}
}
}
private fun getGameDirsAndExternalContent(reloadList: Boolean = false) {
val gameDirs = NativeConfig.getGameDirs().toMutableList()
val externalContentDirs = NativeConfig.getExternalContentDirs().map {
GameDir(it, false, DirectoryType.EXTERNAL_CONTENT)
}
gameDirs.addAll(externalContentDirs)
_folders.value = gameDirs
private fun getGameDirs(reloadList: Boolean = false) {
val gameDirs = NativeConfig.getGameDirs()
_folders.value = gameDirs.toMutableList()
if (reloadList) {
reloadGames(true)
}
}
private fun addExternalContentDir(path: String) {
val currentDirs = NativeConfig.getExternalContentDirs().toMutableList()
if (!currentDirs.contains(path)) {
currentDirs.add(path)
NativeConfig.setExternalContentDirs(currentDirs.toTypedArray())
NativeConfig.saveGlobalConfig()
}
}
private fun removeExternalContentDir(path: String) {
val currentDirs = NativeConfig.getExternalContentDirs().toMutableList()
currentDirs.remove(path)
NativeConfig.setExternalContentDirs(currentDirs.toTypedArray())
NativeConfig.saveGlobalConfig()
}
}
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,6 +12,5 @@ data class Patch(
val version: String,
val type: Int,
val programId: String,
val titleId: String,
val numericVersion: Long = 0
val titleId: String
)
@@ -1,21 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class UserProfile(
val uuid: String,
val username: String,
val imagePath: String = ""
) : Parcelable
object ProfileUtils {
fun generateRandomUUID(): String {
val uuid = java.util.UUID.randomUUID()
return uuid.toString().replace("-", "")
}
}
@@ -63,7 +63,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import androidx.documentfile.provider.DocumentFile
class MainActivity : AppCompatActivity(), ThemeProvider {
private lateinit var binding: ActivityMainBinding
@@ -200,7 +199,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
var artifact = tag
// Nightly builds have a slightly different format
if (NativeLibrary.isNightlyBuild()) {
artifact = tag.substringAfter('.', tag)
val splitTag = tag.split('.')
artifact = splitTag.subList(1, splitTag.size - 1).joinToString(".")
}
downloadAndInstallUpdate(tag, artifact)
}
@@ -389,13 +389,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
val getExternalContentDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) {
processExternalContentDir(result)
}
}
fun processGamesDir(result: Uri, calledFromGameFragment: Boolean = false) {
contentResolver.takePersistableUriPermission(
result,
@@ -417,27 +410,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
.show(supportFragmentManager, AddGameFolderDialogFragment.TAG)
}
fun processExternalContentDir(result: Uri) {
contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
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
}
val externalContentDir = org.yuzu.yuzu_emu.model.GameDir(uriString, false, org.yuzu.yuzu_emu.model.DirectoryType.EXTERNAL_CONTENT)
gamesViewModel.addFolder(externalContentDir, savedFromGameFragment = false)
}
val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) {
processKey(result, "keys")
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
@@ -25,7 +25,6 @@ object DirectoryInitialization {
initializeInternalStorage()
NativeLibrary.initializeSystem(false)
NativeConfig.initializeGlobalConfig()
NativeLibrary.reloadProfiles()
migrateSettings()
areDirectoriesReady = true
}
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
import android.content.SharedPreferences
@@ -49,17 +49,6 @@ object GameHelper {
// Remove previous filesystem provider information so we can get up to date version info
NativeLibrary.clearFilesystemProvider()
// Scan External Content directories and register all NSP/XCI files
val externalContentDirs = NativeConfig.getExternalContentDirs()
for (externalDir in externalContentDirs) {
if (externalDir.isNotEmpty()) {
val externalDirUri = externalDir.toUri()
if (FileUtil.isTreeUriValid(externalDirUri)) {
scanExternalContentRecursive(FileUtil.listFiles(externalDirUri), 3)
}
}
}
val badDirs = mutableListOf<Int>()
gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
val gameDirUri = gameDir.uriString.toUri()
@@ -99,33 +88,6 @@ object GameHelper {
return games.toList()
}
// File extensions considered as external content, buuut should
// be done better imo.
private val externalContentExtensions = setOf("nsp", "xci")
private fun scanExternalContentRecursive(
files: Array<MinimalDocumentFile>,
depth: Int
) {
if (depth <= 0) {
return
}
files.forEach {
if (it.isDirectory) {
scanExternalContentRecursive(
FileUtil.listFiles(it.uri),
depth - 1
)
} else {
val extension = FileUtil.getExtension(it.uri).lowercase()
if (externalContentExtensions.contains(extension)) {
NativeLibrary.addFileToFilesystemProvider(it.uri.toString())
}
}
}
}
private fun addGamesRecursive(
games: MutableList<Game>,
files: Array<MinimalDocumentFile>,
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -204,12 +204,4 @@ object NativeConfig {
external fun getSdmcDir(): String
@Synchronized
external fun setSdmcDir(path: String)
/**
* External Content Provider
*/
@Synchronized
external fun getExternalContentDirs(): Array<String>
@Synchronized
external fun setExternalContentDirs(dirs: Array<String>)
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.utils
@@ -144,15 +144,6 @@ object FreedrenoPresets {
)
)
val DEV_FEATURES_UBWC_HINT = FreedrenoPreset(
name = "Dev - UBWC Flag Hint",
description = "Enable TP UBWC flag hint for development",
icon = "ic_dev_features",
variables = mapOf(
"FD_DEV_FEATURES" to "enable_tp_ubwc_flag_hint=1"
)
)
val PERFORMANCE_DEFAULT = FreedrenoPreset(
name = "Performance - Default",
description = "Clear all debug options for performance",
@@ -168,7 +159,6 @@ object FreedrenoPresets {
CAPTURE_FRAMES,
SHADER_DEBUG,
GPU_HANG_TRACE,
DEV_FEATURES_UBWC_HINT,
PERFORMANCE_DEFAULT
)
}
@@ -1,9 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <common/fs/path_util.h>
#include <common/logging/log.h>
#include <common/settings.h>
#include <input_common/main.h>
#include "android_config.h"
#include "android_settings.h"
@@ -70,18 +69,6 @@ void AndroidConfig::ReadPathValues() {
}
EndArray();
// Read external content directories
Settings::values.external_content_dirs.clear();
const int external_dirs_size = BeginArray(std::string("external_content_dirs"));
for (int i = 0; i < external_dirs_size; ++i) {
SetArrayIndex(i);
std::string dir_path = ReadStringSetting(std::string("path"));
if (!dir_path.empty()) {
Settings::values.external_content_dirs.push_back(dir_path);
}
}
EndArray();
const auto nand_dir_setting = ReadStringSetting(std::string("nand_directory"));
if (!nand_dir_setting.empty()) {
Common::FS::SetEdenPath(Common::FS::EdenPath::NANDDir, nand_dir_setting);
@@ -254,14 +241,6 @@ void AndroidConfig::SavePathValues() {
}
EndArray();
// Save external content directories
BeginArray(std::string("external_content_dirs"));
for (size_t i = 0; i < Settings::values.external_content_dirs.size(); ++i) {
SetArrayIndex(i);
WriteStringSetting(std::string("path"), Settings::values.external_content_dirs[i]);
}
EndArray();
// Save custom NAND directory
const auto nand_path = Common::FS::GetEdenPathString(Common::FS::EdenPath::NANDDir);
WriteStringSetting(std::string("nand_directory"), nand_path,
+13 -496
View File
@@ -49,17 +49,12 @@
#include "common/settings.h"
#include "common/string_util.h"
#include "frontend_common/play_time_manager.h"
#include "core/constants.h"
#include "core/core.h"
#include "core/cpu_manager.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/fs_filesystem.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs/vfs.h"
#include "core/file_sys/vfs/vfs_real.h"
@@ -215,109 +210,6 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
return;
}
const auto extension = Common::ToLower(filepath.substr(filepath.find_last_of('.') + 1));
if (extension == "nsp") {
auto nsp = std::make_shared<FileSys::NSP>(file);
if (nsp->GetStatus() == Loader::ResultStatus::Success) {
std::map<u64, u32> nsp_versions;
std::map<u64, std::string> nsp_version_strings;
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == FileSys::ContentRecordType::Meta) {
const auto meta_nca = std::make_shared<FileSys::NCA>(nca->GetBaseFile());
if (meta_nca->GetStatus() == Loader::ResultStatus::Success) {
const auto section0 = meta_nca->GetSubdirectories();
if (!section0.empty()) {
for (const auto& meta_file : section0[0]->GetFiles()) {
if (meta_file->GetExtension() == "cnmt") {
FileSys::CNMT cnmt(meta_file);
nsp_versions[cnmt.GetTitleID()] = cnmt.GetTitleVersion();
}
}
}
}
}
if (content_type == FileSys::ContentRecordType::Control &&
title_type == FileSys::TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = FileSys::ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
FileSys::NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
nsp_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
for (const auto& [title_id, nca_map] : nsp->GetNCAs()) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type == FileSys::TitleType::Update) {
u32 version = 0;
auto ver_it = nsp_versions.find(title_id);
if (ver_it != nsp_versions.end()) {
version = ver_it->second;
}
std::string version_string;
auto str_it = nsp_version_strings.find(title_id);
if (str_it != nsp_version_strings.end()) {
version_string = str_it->second;
}
m_manual_provider->AddEntryWithVersion(
title_type, content_type, title_id, version, version_string,
nca->GetBaseFile());
LOG_DEBUG(Frontend, "Added NSP update entry - TitleID: {:016X}, Version: {}, VersionStr: {}",
title_id, version, version_string);
} else {
// Use regular AddEntry for non-updates
m_manual_provider->AddEntry(title_type, content_type, title_id,
nca->GetBaseFile());
LOG_DEBUG(Frontend, "Added NSP entry - TitleID: {:016X}, TitleType: {}, ContentType: {}",
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
}
}
}
return;
}
}
// Handle XCI files
if (extension == "xci") {
FileSys::XCI xci{file};
if (xci.GetStatus() == Loader::ResultStatus::Success) {
const auto nsp = xci.GetSecurePartitionNSP();
if (nsp) {
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
return;
}
}
auto loader = Loader::GetLoader(m_system, file);
if (!loader) {
return;
@@ -334,6 +226,17 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
m_manual_provider->AddEntry(FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
program_id, file);
} else if (res2 == Loader::ResultStatus::Success &&
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
const auto nsp = file_type == Loader::FileType::NSP
? std::make_shared<FileSys::NSP>(file)
: FileSys::XCI{file}.GetSecurePartitionNSP();
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
}
@@ -1428,8 +1331,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env
Common::Android::ToJString(env, patch.name),
Common::Android::ToJString(env, patch.version), static_cast<jint>(patch.type),
Common::Android::ToJString(env, std::to_string(patch.program_id)),
Common::Android::ToJString(env, std::to_string(patch.title_id)),
static_cast<jlong>(patch.numeric_version));
Common::Android::ToJString(env, std::to_string(patch.title_id)));
env->SetObjectArrayElement(jpatchArray, i, jpatch);
++i;
}
@@ -1776,7 +1678,7 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
#endif
}
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", artifact_str, variant);
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", version_str, variant);
const std::string url = fmt::format("{}/{}/releases/download/{}/{}",
std::string{Common::g_build_auto_update_website},
std::string{Common::g_build_auto_update_repo},
@@ -1784,7 +1686,6 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
apk_filename);
env->ReleaseStringUTFChars(tag, version_str);
env->ReleaseStringUTFChars(artifact, artifact_str);
env->ReleaseStringUTFChars(packageId, package_id_str);
return env->NewStringUTF(url.c_str());
}
@@ -1796,388 +1697,4 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getBuildVersion(
return env->NewStringUTF(Common::g_build_version);
}
JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getAllUsers(
JNIEnv* env,
[[maybe_unused]] jobject obj) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
manager.ResetUserSaveFile();
if (manager.GetUserCount() == 0) {
manager.CreateNewUser(Common::UUID::MakeRandom(), "Eden");
manager.WriteUserSaveFile();
}
const auto& users = manager.GetAllUsers();
jclass string_class = env->FindClass("java/lang/String");
if (!string_class) {
return env->NewObjectArray(0, env->FindClass("java/lang/Object"), nullptr);
}
jsize valid_count = 0;
for (const auto& user : users) {
if (user.IsValid()) {
valid_count++;
}
}
jobjectArray result = env->NewObjectArray(valid_count, string_class, nullptr);
if (!result) {
return env->NewObjectArray(0, string_class, nullptr);
}
// fill array sequentially with only valid users
jsize array_index = 0;
for (const auto& user : users) {
if (user.IsValid()) {
jstring uuid_str = env->NewStringUTF(user.FormattedString().c_str());
if (uuid_str) {
env->SetObjectArrayElement(result, array_index++, uuid_str);
}
}
}
return result;
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserUsername(
JNIEnv* env,
[[maybe_unused]] jobject obj,
jstring juuid) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
const auto uuid_string = Common::Android::GetJString(env, juuid);
const auto uuid = Common::UUID{uuid_string};
Service::Account::ProfileBase profile{};
if (!manager.GetProfileBase(uuid, profile)) {
jstring result = env->NewStringUTF("");
return result ? result : env->NewStringUTF("");
}
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
jstring result = env->NewStringUTF(text.c_str());
return result ? result : env->NewStringUTF("");
}
JNIEXPORT jlong JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserCount(
JNIEnv* env,
[[maybe_unused]] jobject obj) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
return static_cast<jlong>(manager.GetUserCount());
}
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_canCreateUser(
JNIEnv* env,
[[maybe_unused]] jobject obj) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
return manager.CanSystemRegisterUser();
}
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_createUser(
JNIEnv* env,
[[maybe_unused]] jobject obj,
jstring juuid,
jstring jusername) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
const auto uuid_string = Common::Android::GetJString(env, juuid);
const auto username = Common::Android::GetJString(env, jusername);
const auto uuid = Common::UUID{uuid_string};
const auto result = manager.CreateNewUser(uuid, username);
if (result.IsSuccess()) {
manager.WriteUserSaveFile();
return true;
}
return false;
}
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_updateUserUsername(
JNIEnv* env,
[[maybe_unused]] jobject obj,
jstring juuid,
jstring jusername) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
const auto uuid_string = Common::Android::GetJString(env, juuid);
const auto username = Common::Android::GetJString(env, jusername);
const auto uuid = Common::UUID{uuid_string};
Service::Account::ProfileBase profile{};
if (!manager.GetProfileBase(uuid, profile)) {
return false;
}
std::fill(profile.username.begin(), profile.username.end(), '\0');
std::copy(username.begin(), username.end(), profile.username.begin());
if (manager.SetProfileBase(uuid, profile)) {
manager.WriteUserSaveFile();
return true;
}
return false;
}
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUser(
JNIEnv* env,
[[maybe_unused]] jobject obj,
jstring juuid) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
const auto uuid_string = Common::Android::GetJString(env, juuid);
const auto uuid = Common::UUID{uuid_string};
const auto user_index = manager.GetUserIndex(uuid);
if (!user_index) {
return false;
}
if (Settings::values.current_user.GetValue() == static_cast<s32>(*user_index)) {
Settings::values.current_user = 0;
}
if (manager.RemoveUser(uuid)) {
manager.WriteUserSaveFile();
manager.ResetUserSaveFile();
return true;
}
return false;
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getCurrentUser(
JNIEnv* env,
[[maybe_unused]] jobject obj) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
const auto user_id = manager.GetUser(Settings::values.current_user.GetValue());
if (!user_id) {
jstring result = env->NewStringUTF("");
return result ? result : env->NewStringUTF("");
}
jstring result = env->NewStringUTF(user_id->FormattedString().c_str());
return result ? result : env->NewStringUTF("");
}
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setCurrentUser(
JNIEnv* env,
[[maybe_unused]] jobject obj,
jstring juuid) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
const auto uuid_string = Common::Android::GetJString(env, juuid);
const auto uuid = Common::UUID{uuid_string};
const auto index = manager.GetUserIndex(uuid);
if (index) {
Settings::values.current_user = static_cast<s32>(*index);
return true;
}
return false;
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUserImagePath(
JNIEnv* env,
[[maybe_unused]] jobject obj,
jstring juuid) {
const auto uuid_string = Common::Android::GetJString(env, juuid);
const auto uuid = Common::UUID{uuid_string};
const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) /
fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormattedString());
jstring result = Common::Android::ToJString(env, Common::FS::PathToUTF8String(path));
return result ? result : env->NewStringUTF("");
}
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_saveUserImage(
JNIEnv* env,
[[maybe_unused]] jobject obj,
jstring juuid,
jstring jimagePath) {
const auto uuid_string = Common::Android::GetJString(env, juuid);
const auto uuid = Common::UUID{uuid_string};
const auto image_source = Common::Android::GetJString(env, jimagePath);
const auto dest_path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) /
fmt::format("system/save/8000000000000010/su/avators/{}.jpg", uuid.FormattedString());
const auto dest_dir = dest_path.parent_path();
if (!Common::FS::CreateDirs(dest_dir)) {
return false;
}
try {
std::filesystem::copy_file(image_source, dest_path,
std::filesystem::copy_options::overwrite_existing);
return true;
} catch (const std::filesystem::filesystem_error& e) {
LOG_ERROR(Common_Filesystem, "Failed to copy image file: {}", e.what());
return false;
}
}
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadProfiles(
JNIEnv* env,
[[maybe_unused]] jobject obj) {
auto& manager = EmulationSession::GetInstance().System().GetProfileManager();
manager.ResetUserSaveFile();
// create a default user if non exist
if (manager.GetUserCount() == 0) {
manager.CreateNewUser(Common::UUID::MakeRandom(), "Eden");
manager.WriteUserSaveFile();
}
LOG_INFO(Service_ACC, "Profile manager reloaded, user count: {}", manager.GetUserCount());
}
// for firmware avatar images
static std::vector<uint8_t> DecompressYaz0(const FileSys::VirtualFile& file) {
if (!file) {
return std::vector<uint8_t>();
}
uint32_t magic{};
file->ReadObject(&magic, 0);
if (magic != Common::MakeMagic('Y', 'a', 'z', '0')) {
return std::vector<uint8_t>();
}
uint32_t decoded_length{};
file->ReadObject(&decoded_length, 4);
decoded_length = Common::swap32(decoded_length);
std::size_t input_size = file->GetSize() - 16;
std::vector<uint8_t> input(input_size);
file->ReadBytes(input.data(), input_size, 16);
uint32_t input_offset{};
uint32_t output_offset{};
std::vector<uint8_t> output(decoded_length);
uint16_t mask{};
uint8_t header{};
while (output_offset < decoded_length) {
if ((mask >>= 1) == 0) {
if (input_offset >= input.size()) break;
header = input[input_offset++];
mask = 0x80;
}
if ((header & mask) != 0) {
if (output_offset >= output.size() || input_offset >= input.size()) {
break;
}
output[output_offset++] = input[input_offset++];
} else {
if (input_offset + 1 >= input.size()) break;
uint8_t byte1 = input[input_offset++];
uint8_t byte2 = input[input_offset++];
uint32_t dist = ((byte1 & 0xF) << 8) | byte2;
uint32_t position = output_offset - (dist + 1);
uint32_t length = byte1 >> 4;
if (length == 0) {
if (input_offset >= input.size()) break;
length = static_cast<uint32_t>(input[input_offset++]) + 0x12;
} else {
length += 2;
}
for (uint32_t i = 0; i < length && output_offset < decoded_length; ++i) {
output[output_offset++] = output[position++];
}
}
}
return output;
}
static FileSys::VirtualDir GetFirmwareAvatarDirectory() {
constexpr u64 AvatarImageDataId = 0x010000000000080AULL;
auto* bis_system = EmulationSession::GetInstance().System().GetFileSystemController().GetSystemNANDContents();
if (!bis_system) {
return nullptr;
}
const auto nca = bis_system->GetEntry(AvatarImageDataId, FileSys::ContentRecordType::Data);
if (!nca) {
return nullptr;
}
const auto romfs = nca->GetRomFS();
if (!romfs) {
return nullptr;
}
const auto extracted = FileSys::ExtractRomFS(romfs);
if (!extracted) {
return nullptr;
}
return extracted->GetSubdirectory("chara");
}
JNIEXPORT jint JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getFirmwareAvatarCount(
JNIEnv* env,
[[maybe_unused]] jobject obj) {
const auto chara_dir = GetFirmwareAvatarDirectory();
if (!chara_dir) {
return 0;
}
int count = 0;
for (const auto& item : chara_dir->GetFiles()) {
if (item->GetExtension() == "szs") {
count++;
}
}
return count;
}
JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getFirmwareAvatarImage(
JNIEnv* env,
[[maybe_unused]] jobject obj,
jint index) {
const auto chara_dir = GetFirmwareAvatarDirectory();
if (!chara_dir) {
return nullptr;
}
int current_index = 0;
for (const auto& item : chara_dir->GetFiles()) {
if (item->GetExtension() != "szs") {
continue;
}
if (current_index == index) {
auto image_data = DecompressYaz0(item);
if (image_data.empty()) {
return nullptr;
}
jbyteArray result = env->NewByteArray(image_data.size());
if (result) {
env->SetByteArrayRegion(result, 0, image_data.size(),
reinterpret_cast<const jbyte*>(image_data.data()));
}
return result;
}
current_index++;
}
return nullptr;
}
JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getDefaultAccountBackupJpeg(
JNIEnv* env,
[[maybe_unused]] jobject obj) {
jbyteArray result = env->NewByteArray(Core::Constants::ACCOUNT_BACKUP_JPEG.size());
if (result) {
env->SetByteArrayRegion(result, 0, Core::Constants::ACCOUNT_BACKUP_JPEG.size(),
reinterpret_cast<const jbyte*>(Core::Constants::ACCOUNT_BACKUP_JPEG.data()));
}
return result;
}
} // extern "C"
@@ -583,26 +583,4 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setSdmcDir(JNIEnv* env, jobject
Common::FS::SetEdenPath(Common::FS::EdenPath::SDMCDir, path);
}
jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getExternalContentDirs(JNIEnv* env,
jobject obj) {
const auto& dirs = Settings::values.external_content_dirs;
jobjectArray jdirsArray =
env->NewObjectArray(dirs.size(), Common::Android::GetStringClass(),
Common::Android::ToJString(env, ""));
for (size_t i = 0; i < dirs.size(); ++i) {
env->SetObjectArrayElement(jdirsArray, i, Common::Android::ToJString(env, dirs[i]));
}
return jdirsArray;
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setExternalContentDirs(JNIEnv* env, jobject obj,
jobjectArray jdirs) {
Settings::values.external_content_dirs.clear();
const int size = env->GetArrayLength(jdirs);
for (int i = 0; i < size; ++i) {
auto jdir = static_cast<jstring>(env->GetObjectArrayElement(jdirs, i));
Settings::values.external_content_dirs.push_back(Common::Android::GetJString(env, jdir));
}
}
} // extern "C"
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/**
@@ -258,11 +258,6 @@ Java_org_yuzu_yuzu_1emu_utils_NativeFreedrenoConfig_setFreedrenoEnv(
}
LOG_INFO(Frontend, "[Freedreno] Set {}={}", var_name, value);
if (var_name == "FD_DEV_FEATURES") {
LOG_INFO(Frontend, "[Freedreno] FD_DEV_FEATURES enabled: {}", value);
}
return JNI_TRUE;
}
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3s-3,-1.34 -3,-3S10.34,5 12,5zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22c0.03,-1.99 4,-3.08 6,-3.08c1.99,0 5.97,1.09 6,3.08C16.71,17.92 14.5,19.2 12,19.2z" />
</vector>
@@ -11,7 +11,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:orientation="horizontal"
android:padding="16dp"
android:layout_gravity="center_vertical">
@@ -23,25 +23,12 @@
android:layout_gravity="center_vertical|start"
android:requiresFadingEdge="horizontal"
android:textAlignment="viewStart"
app:layout_constraintBottom_toTopOf="@+id/type_indicator"
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" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/type_indicator"
style="@style/TextAppearance.Material3.LabelSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="?attr/colorOnSurfaceVariant"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/button_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/path"
tools:text="Games" />
<LinearLayout
android:id="@+id/button_layout"
android:layout_width="wrap_content"
@@ -1,39 +0,0 @@
<?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:orientation="vertical"
android:padding="16dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/grid_avatars"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" />
<ProgressBar
android:id="@+id/progress_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<TextView
android:id="@+id/text_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/profile_firmware_avatars_unavailable"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:gravity="center"
android:padding="16dp"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
@@ -1,226 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_new_user"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/profile_new_user"
app:navigationIcon="@drawable/ic_back"
app:titleCentered="false" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="88dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingVertical="16dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="24dp"
style="@style/Widget.Material3.CardView.Elevated"
app:cardCornerRadius="64dp"
app:cardElevation="4dp">
<ImageView
android:id="@+id/image_user_avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:contentDescription="@string/profile_avatar"
tools:src="@drawable/ic_account_circle" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:layout_marginBottom="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_select_image"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/profile_select_image"
style="@style/Widget.Material3.Button.TonalButton"
app:icon="@drawable/ic_add"
android:layout_marginEnd="4dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_firmware_avatars"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/profile_firmware_avatars"
style="@style/Widget.Material3.Button.TonalButton"
app:icon="@drawable/ic_account_circle"
android:layout_marginStart="4dp"
android:visibility="gone" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_revert_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/profile_revert_image"
style="@style/Widget.Material3.Button.TextButton"
android:visibility="gone" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:hint="@string/profile_username">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLength="32"
android:maxLines="1"
android:minHeight="48dp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
style="@style/Widget.Material3.CardView.Filled"
app:cardCornerRadius="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/profile_uuid"
android:textAppearance="?attr/textAppearanceLabelMedium"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginBottom="8dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/text_uuid"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:fontFamily="monospace"
android:textIsSelectable="true"
tools:text="12345678-1234-1234-1234-123456789012" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_generate_uuid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/profile_generate"
style="@style/Widget.Material3.Button.TextButton"
app:icon="@drawable/ic_refresh"
app:iconGravity="textStart" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/profile_uuid_description"
android:textAppearance="?attr/textAppearanceBodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginTop="8dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/button_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
style="@style/Widget.Material3.CardView.Elevated"
app:cardCornerRadius="0dp"
app:cardElevation="8dp">
<LinearLayout
android:layout_width="match_parent"
android:id="@+id/button_container"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:paddingBottom="24dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:text="@android:string/cancel"
style="@style/Widget.Material3.Button.TonalButton" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:text="@string/save"
style="@style/Widget.Material3.Button" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar_profiles"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/profile_manager"
app:navigationIcon="@drawable/ic_back"
app:titleCentered="false" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_profiles"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingTop="8dp"
android:paddingBottom="96dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/list_item_profile" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/button_add_user"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="24dp"
android:text="@string/profile_add_user"
android:contentDescription="@string/profile_add_user"
app:icon="@drawable/ic_add"
app:iconGravity="start"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -1,21 +0,0 @@
<?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"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
style="@style/Widget.Material3.CardView.Elevated"
app:cardCornerRadius="12dp"
android:clickable="true"
android:focusable="true"
android:checkable="true">
<ImageView
android:id="@+id/image_avatar"
android:layout_width="72dp"
android:layout_height="72dp"
android:scaleType="centerCrop"
android:contentDescription="@string/profile_avatar" />
</com.google.android.material.card.MaterialCardView>
@@ -1,125 +0,0 @@
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="6dp"
style="@style/Widget.Material3.CardView.Filled"
app:cardCornerRadius="16dp"
android:clickable="true"
android:focusable="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/avatar_container"
android:layout_width="56dp"
android:layout_height="56dp"
style="@style/Widget.Material3.CardView.Elevated"
app:cardCornerRadius="28dp"
app:cardElevation="1dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:id="@+id/image_avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@drawable/ic_account_circle"
android:contentDescription="@string/profile_avatar" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/check_container"
android:layout_width="20dp"
android:layout_height="20dp"
android:visibility="gone"
style="@style/Widget.Material3.CardView.Filled"
app:cardBackgroundColor="?attr/colorPrimary"
app:cardCornerRadius="10dp"
app:cardElevation="2dp"
app:layout_constraintEnd_toEndOf="@id/avatar_container"
app:layout_constraintBottom_toBottomOf="@id/avatar_container"
tools:visibility="visible">
<ImageView
android:id="@+id/icon_check"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_gravity="center"
android:src="@drawable/ic_check"
app:tint="?attr/colorOnPrimary"
android:contentDescription="@string/profile_current_user" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:id="@+id/text_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="12dp"
android:layout_marginEnd="8dp"
app:layout_constraintStart_toEndOf="@id/avatar_container"
app:layout_constraintEnd_toStartOf="@id/button_edit"
app:layout_constraintTop_toTopOf="@id/avatar_container"
app:layout_constraintBottom_toBottomOf="@id/avatar_container">
<TextView
android:id="@+id/text_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:maxLines="1"
android:ellipsize="end"
tools:text="User Name" />
<TextView
android:id="@+id/text_uuid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textAppearance="?attr/textAppearanceBodySmall"
android:maxLines="1"
android:ellipsize="middle"
android:textColor="?attr/colorOnSurfaceVariant"
tools:text="12345678-1234-1234-1234-123456789012" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_edit"
android:layout_width="40dp"
android:layout_height="40dp"
style="@style/Widget.Material3.Button.IconButton"
app:icon="@drawable/ic_edit"
app:iconTint="?attr/colorOnSurfaceVariant"
app:layout_constraintEnd_toStartOf="@id/button_delete"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:contentDescription="@string/profile_edit" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_delete"
android:layout_width="40dp"
android:layout_height="40dp"
style="@style/Widget.Material3.Button.IconButton"
app:icon="@drawable/ic_delete"
app:iconTint="?attr/colorError"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:contentDescription="@string/profile_delete" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
@@ -36,9 +36,6 @@
<action
android:id="@+id/action_homeSettingsFragment_to_gameFoldersFragment"
app:destination="@id/gameFoldersFragment" />
<action
android:id="@+id/action_homeSettingsFragment_to_profileManagerFragment"
app:destination="@id/profileManagerFragment" />
</fragment>
<fragment
@@ -189,17 +186,5 @@
app:nullable="true"
android:defaultValue="@null" />
</fragment>
<fragment
android:id="@+id/profileManagerFragment"
android:name="org.yuzu.yuzu_emu.fragments.ProfileManagerFragment"
android:label="ProfileManagerFragment" >
<action
android:id="@+id/action_profileManagerFragment_to_newUserDialog"
app:destination="@id/newUserDialogFragment" />
</fragment>
<fragment
android:id="@+id/newUserDialogFragment"
android:name="org.yuzu.yuzu_emu.fragments.EditUserDialogFragment"
android:label="NewUserDialogFragment" />
</navigation>
@@ -381,9 +381,9 @@
<string name="qlaunch_description">Launch applications from the system home screen</string>
<string name="applets">Applet launcher</string>
<string name="applets_description">Launch system applets using installed firmware</string>
<string name="applets_error_firmware">Firmware not installed</string>
<string name="applets_error_firmware">Firmware not installed or invalid version</string>
<string name="applets_error_applet">Applet not available</string>
<string name="applets_error_description"><![CDATA[Please ensure your <a href=\"https://yuzu-mirror.github.io/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> file and <a href=\"https://yuzu-mirror.github.io/help/quickstart/#dumping-system-firmware\">firmware</a> are installed and try again.]]></string>
<string name="applets_error_description"><![CDATA[Please ensure your <a href=\"https://yuzu-mirror.github.io/help/quickstart/#dumping-prodkeys-and-titlekeys\">prod.keys</a> file and <a href=\"https://yuzu-mirror.github.io/help/quickstart/#dumping-system-firmware\">firmware</a> are installed and try again.<br>Additionally, ensure your firmware is of version 19.0.1 or older.]]></string>
<string name="album_applet">Album</string>
<string name="album_applet_description">See images stored in the user screenshots folder with the system photo viewer</string>
<string name="mii_edit_applet">Mii edit</string>
@@ -1239,35 +1239,6 @@
<string name="enable_overlay">Enable Overlay Applet</string>
<string name="enable_overlay_description">Enables Horizon\'s built-in overlay applet. Press and hold the home button for 1 second to show it.</string>
<!-- Profile Management -->
<string name="profile_manager">Profile Manager</string>
<string name="profile_manager_description">Manage user profiles</string>
<string name="profile_add_user">Add User</string>
<string name="profile_new_user">New User</string>
<string name="profile_edit_user">Edit User</string>
<string name="profile_edit">Edit</string>
<string name="profile_delete">Delete</string>
<string name="profile_username">Username</string>
<string name="profile_uuid">User ID (UUID)</string>
<string name="profile_uuid_description">This is the unique identifier for this user profile. It cannot be changed after creation.</string>
<string name="profile_generate">Generate</string>
<string name="profile_avatar">User Avatar</string>
<string name="profile_select_image">Select Image</string>
<string name="profile_firmware_avatars">Firmware Avatars</string>
<string name="profile_firmware_avatars_unavailable">Firmware avatars are not available. Please install firmware to use this feature.</string>
<string name="profile_revert_image">Revert to Default</string>
<string name="profile_current_user">Current User</string>
<string name="profile_max_users_title">Maximum Users Reached</string>
<string name="profile_max_users_message">You cannot create more than 8 user profiles. Please delete an existing profile to create a new one.</string>
<string name="profile_delete_confirm_title">Delete Profile?</string>
<string name="profile_delete_confirm_message">Are you sure you want to delete %1$s? All save data for this user will be deleted.</string>
<string name="profile_delete_current_user_message">Are you sure you want to delete %1$s? This is the currently selected user. The first available user will be selected instead.</string>
<string name="profile_create_failed">Failed to create user profile</string>
<string name="profile_update_failed">Failed to update user profile</string>
<string name="profile_image_load_error">Failed to load image: %1$s</string>
<string name="profile_image_save_error">Failed to save image: %1$s</string>
<string name="error">Error</string>
<!-- Licenses screen strings -->
<string name="licenses">Licenses</string>
<string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string>
@@ -1774,8 +1745,4 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</string>
<string name="external_content">External Content</string>
<string name="add_folders">Add Folder</string>
</resources>
+3 -7
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project SPDX-License-Identifier:
@@ -186,6 +186,8 @@ if(ARCHITECTURE_x86_64)
common
PRIVATE x64/cpu_detect.cpp
x64/cpu_detect.h
x64/cpu_wait.cpp
x64/cpu_wait.h
x64/native_clock.cpp
x64/native_clock.h
x64/rdtsc.cpp
@@ -261,10 +263,4 @@ if(ANDROID)
target_link_libraries(common PRIVATE android)
endif()
# IOPS (needed for power state) requires linking to IOKit
if (APPLE)
find_library(IOKIT_LIBRARY IOKit REQUIRED)
target_link_libraries(common PRIVATE ${IOKIT_LIBRARY})
endif()
create_target_directory_groups(common)
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <jni.h>
@@ -516,7 +516,7 @@ namespace Common::Android {
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
s_patch_constructor = env->GetMethodID(
patch_class, "<init>",
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;J)V");
"(ZLjava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V");
s_patch_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
+24 -83
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -30,7 +30,6 @@
#include <sys/random.h>
#include <mach/vm_map.h>
#include <mach/mach.h>
#include <map>
#endif
// FreeBSD
@@ -397,68 +396,21 @@ private:
#else // ^^^ Windows ^^^ vvv POSIX vvv
// Use NCE friendly mapping techniques
#if defined(ARCHITECTURE_arm64) && defined(HAS_NCE)
#ifdef ARCHITECTURE_arm64
static void* ChooseVirtualBase(size_t virtual_size) {
constexpr uintptr_t Map36BitSize = (1ULL << 36);
#ifdef __APPLE__
// TODO: Fatal flaw, regions may change if map inserts elements, very rare, but MAY HAPPEN!
std::map<u64, u64> aspace_list;
kern_return_t krc = KERN_SUCCESS;
vm_address_t address = 0;
vm_size_t r_size = 0;
uint32_t depth = 1;
do {
struct vm_region_submap_info_64 info;
mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
krc = vm_region_recurse_64(mach_task_self(), &address, &r_size, &depth, (vm_region_info_64_t)&info, &count);
if (krc == KERN_INVALID_ADDRESS)
break;
if (info.is_submap){
depth++;
} else {
aspace_list.insert({ u64(address), u64(r_size) });
address += r_size;
}
} while(1);
for (auto it = aspace_list.begin(); it != aspace_list.end(); it++) {
auto const next = std::next(it);
// properties of hole
auto const addr = it->first + it->second;
auto const size = (next != aspace_list.end()) ? next->first - addr : std::numeric_limits<u64>::max() - addr;
ASSERT(next == aspace_list.end() || it->first < next->first);
if (size > virtual_size && uintptr_t(addr + size) >= Map36BitSize) {
// valid address for NCE
if (uintptr_t(addr) >= Map36BitSize) { //common case: hole after 39 bit
if (size >= virtual_size) {
void* p = mmap(reinterpret_cast<void*>(addr), virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED)
continue;
return p;
}
// skip
} else { //edge case: hole before 39 bit
auto const rem_size = size - (Map36BitSize - uintptr_t(addr));
if (rem_size >= virtual_size) {
void* p = mmap(reinterpret_cast<void*>(Map36BitSize), virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED)
continue;
return p;
}
// skip
}
}
}
UNREACHABLE();
#else
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;
// This is not a cryptographic application, we just want something random.
std::mt19937_64 rng;
// 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.
@@ -482,9 +434,10 @@ static void* ChooseVirtualBase(size_t virtual_size) {
}
return MAP_FAILED;
#endif
}
#else
static void* ChooseVirtualBase(size_t virtual_size) {
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__sun__) || defined(__HAIKU__) || defined(__managarm__) || defined(__AIX__)
void* virtual_base = mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_ALIGNED_SUPER, -1, 0);
@@ -493,6 +446,7 @@ static void* ChooseVirtualBase(size_t virtual_size) {
#endif
return mmap(nullptr, virtual_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
}
#endif
#if defined(__sun__) || defined(__HAIKU__) || defined(__NetBSD__) || defined(__DragonFly__)
@@ -546,10 +500,9 @@ class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_)
: backing_size{backing_size_}, virtual_size{virtual_size_} {
// TODO: Solve all 4k paging issues
//long page_size = sysconf(_SC_PAGESIZE);
//ASSERT_MSG(page_size == 0x1000, "page size {:#x} is incompatible with 4K paging",
// page_size);
long page_size = sysconf(_SC_PAGESIZE);
ASSERT_MSG(page_size == 0x1000, "page size {:#x} is incompatible with 4K paging",
page_size);
// Backing memory initialization
#if defined(__sun__) || defined(__HAIKU__) || defined(__NetBSD__) || defined(__DragonFly__)
fd = shm_open_anon(O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
@@ -619,21 +572,12 @@ public:
prot_flags |= PROT_READ;
if (True(perms & MemoryPermission::Write))
prot_flags |= PROT_WRITE;
// W^X only supported (and needed) with NCE
#ifdef HAS_NCE
#ifdef ARCHITECTURE_arm64
if (True(perms & MemoryPermission::Execute))
prot_flags |= PROT_EXEC;
#endif
int flags = (fd > 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
u8* addr = virtual_base + virtual_offset;
#ifdef __APPLE__
// The way Steve Jobs intended
addr = (u8*)trunc_page(u64(addr) + host_offset);
length = round_page(length);
host_offset = 0;
#endif
void* ret = mmap(addr, length, prot_flags, flags, fd, host_offset);
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
}
@@ -658,21 +602,18 @@ public:
AdjustMap(&virtual_offset, &length);
int flags = PROT_NONE;
if (read)
if (read) {
flags |= PROT_READ;
if (write)
}
if (write) {
flags |= PROT_WRITE;
}
#ifdef HAS_NCE
if (execute)
if (execute) {
flags |= PROT_EXEC;
}
#endif
u8* addr = virtual_base + virtual_offset;
#ifdef __APPLE__
// The way Steve Jobs intended
addr = (u8*)trunc_page(u64(addr));
length = round_page(length);
#endif
int ret = mprotect(addr, length, flags);
int ret = mprotect(virtual_base + virtual_offset, length, flags);
ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
}
+54 -60
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
@@ -7,7 +7,6 @@
#include <atomic>
#include <chrono>
#include <climits>
#include <cstdlib>
#include <regex>
#include <thread>
@@ -42,7 +41,7 @@ namespace {
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
/// do not be fooled this isn't generating new strings on .rodata :)
constexpr const char* TrimSourcePath(std::string_view source) noexcept {
constexpr const char* TrimSourcePath(std::string_view source) {
const auto rfind = [source](const std::string_view match) {
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
};
@@ -52,31 +51,31 @@ constexpr const char* TrimSourcePath(std::string_view source) noexcept {
/// @brief Interface for logging backends.
struct Backend {
virtual ~Backend() noexcept = default;
virtual void Write(const Entry& entry) noexcept = 0;
virtual void EnableForStacktrace() noexcept= 0;
virtual void Flush() noexcept = 0;
virtual ~Backend() = default;
virtual void Write(const Entry& entry) = 0;
virtual void EnableForStacktrace() = 0;
virtual void Flush() = 0;
};
/// @brief Backend that writes to stderr and with color
struct ColorConsoleBackend final : public Backend {
explicit ColorConsoleBackend() noexcept = default;
~ColorConsoleBackend() noexcept override = default;
explicit ColorConsoleBackend() = default;
~ColorConsoleBackend() override = default;
void Write(const Entry& entry) noexcept override {
void Write(const Entry& entry) override {
if (enabled.load(std::memory_order_relaxed))
PrintColoredMessage(entry);
}
void Flush() noexcept override {
void Flush() override {
// stderr shouldn't be buffered
}
void EnableForStacktrace() noexcept override {
void EnableForStacktrace() override {
enabled = true;
}
void SetEnabled(bool enabled_) noexcept {
void SetEnabled(bool enabled_) {
enabled = enabled_;
}
@@ -84,10 +83,9 @@ private:
std::atomic_bool enabled{false};
};
#ifndef __OPENORBIS__
/// @brief Backend that writes to a file passed into the constructor
struct FileBackend final : public Backend {
explicit FileBackend(const std::filesystem::path& filename) noexcept {
explicit FileBackend(const std::filesystem::path& filename) {
auto old_filename = filename;
old_filename += ".old.txt";
@@ -99,9 +97,9 @@ struct FileBackend final : public Backend {
file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
}
~FileBackend() noexcept override = default;
~FileBackend() override = default;
void Write(const Entry& entry) noexcept override {
void Write(const Entry& entry) override {
if (!enabled)
return;
@@ -149,11 +147,11 @@ struct FileBackend final : public Backend {
}
}
void Flush() noexcept override {
void Flush() override {
file->Flush();
}
void EnableForStacktrace() noexcept override {
void EnableForStacktrace() override {
enabled = true;
bytes_written = 0;
}
@@ -163,30 +161,29 @@ private:
std::size_t bytes_written = 0;
bool enabled = true;
};
#endif
#ifdef _WIN32
/// @brief Backend that writes to Visual Studio's output window
struct DebuggerBackend final : public Backend {
explicit DebuggerBackend() noexcept = default;
~DebuggerBackend() noexcept override = default;
void Write(const Entry& entry) noexcept override {
explicit DebuggerBackend() = default;
~DebuggerBackend() override = default;
void Write(const Entry& entry) override {
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
}
void Flush() noexcept override {}
void EnableForStacktrace() noexcept override {}
void Flush() override {}
void EnableForStacktrace() override {}
};
#endif
#ifdef ANDROID
/// @brief Backend that writes to the Android logcat
struct LogcatBackend : public Backend {
explicit LogcatBackend() noexcept = default;
~LogcatBackend() noexcept override = default;
void Write(const Entry& entry) noexcept override {
explicit LogcatBackend() = default;
~LogcatBackend() override = default;
void Write(const Entry& entry) override {
PrintMessageToLogcat(entry);
}
void Flush() noexcept override {}
void EnableForStacktrace() noexcept override {}
void Flush() override {}
void EnableForStacktrace() override {}
};
#endif
@@ -195,13 +192,13 @@ bool initialization_in_progress_suppress_logging = true;
/// @brief Static state as a singleton.
class Impl {
public:
static Impl& Instance() noexcept {
static Impl& Instance() {
if (!instance)
std::abort();
throw std::runtime_error("Using Logging instance before its initialization");
return *instance;
}
static void Initialize() noexcept {
static void Initialize() {
if (instance) {
LOG_WARNING(Log, "Reinitializing logging backend");
return;
@@ -215,25 +212,25 @@ public:
initialization_in_progress_suppress_logging = false;
}
static void Start() noexcept {
static void Start() {
instance->StartBackendThread();
}
static void Stop() noexcept {
static void Stop() {
instance->StopBackendThread();
}
Impl(const Impl&) noexcept = delete;
Impl& operator=(const Impl&) noexcept = delete;
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
Impl(Impl&&) noexcept = delete;
Impl& operator=(Impl&&) noexcept = delete;
Impl(Impl&&) = delete;
Impl& operator=(Impl&&) = delete;
void SetGlobalFilter(const Filter& f) noexcept {
void SetGlobalFilter(const Filter& f) {
filter = f;
}
void SetColorConsoleBackendEnabled(bool enabled) noexcept {
void SetColorConsoleBackendEnabled(bool enabled) {
color_console_backend.SetEnabled(enabled);
}
@@ -241,21 +238,19 @@ public:
return filter.CheckMessage(log_class, log_level);
}
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, std::string&& message) noexcept {
message_queue.EmplaceWait(CreateEntry(log_class, log_level, TrimSourcePath(filename), line_num, function, std::move(message)));
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, std::string&& message) noexcept {
message_queue.EmplaceWait(
CreateEntry(log_class, log_level, TrimSourcePath(filename), line_num, function, std::move(message)));
}
private:
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_) noexcept :
filter{filter_}
#ifndef __OPENORBIS__
, file_backend{file_backend_filename}
#endif
{}
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
: filter{filter_}, file_backend{file_backend_filename} {}
~Impl() noexcept = default;
~Impl() = default;
void StartBackendThread() noexcept {
void StartBackendThread() {
backend_thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("Logger");
Entry entry;
@@ -276,14 +271,15 @@ private:
});
}
void StopBackendThread() noexcept {
void StopBackendThread() {
backend_thread.request_stop();
if (backend_thread.joinable())
backend_thread.join();
ForEachBackend([](Backend& backend) { backend.Flush(); });
}
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, const char* function, std::string&& message) const noexcept {
Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
const char* function, std::string&& message) const {
using std::chrono::duration_cast;
using std::chrono::microseconds;
using std::chrono::steady_clock;
@@ -298,11 +294,9 @@ private:
};
}
void ForEachBackend(auto lambda) noexcept {
void ForEachBackend(auto lambda) {
lambda(static_cast<Backend&>(color_console_backend));
#ifndef __OPENORBIS__
lambda(static_cast<Backend&>(file_backend));
#endif
#ifdef _WIN32
lambda(static_cast<Backend&>(debugger_backend));
#endif
@@ -311,7 +305,7 @@ private:
#endif
}
static void Deleter(Impl* ptr) noexcept {
static void Deleter(Impl* ptr) {
delete ptr;
}
@@ -319,9 +313,7 @@ private:
Filter filter;
ColorConsoleBackend color_console_backend{};
#ifndef __OPENORBIS__
FileBackend file_backend;
#endif
#ifdef _WIN32
DebuggerBackend debugger_backend{};
#endif
@@ -359,7 +351,9 @@ void SetColorConsoleBackendEnabled(bool enabled) {
Impl::Instance().SetColorConsoleBackendEnabled(enabled);
}
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, fmt::string_view format, const fmt::format_args& args) {
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, fmt::string_view format,
const fmt::format_args& args) {
if (!initialization_in_progress_suppress_logging) {
auto& instance = Impl::Instance();
if (instance.CanPushEntry(log_class, log_level))
+5 -6
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -12,15 +9,17 @@
namespace Common::Log {
/// @brief A log entry. Log entries are store in a structured format to permit more varied output
/// formatting on different frontends, as well as facilitating filtering and aggregation.
/**
* A log entry. Log entries are store in a structured format to permit more varied output
* formatting on different frontends, as well as facilitating filtering and aggregation.
*/
struct Entry {
std::chrono::microseconds timestamp;
Class log_class{};
Level log_level{};
const char* filename = nullptr;
unsigned int line_num = 0;
const char* function = nullptr;
std::string function;
std::string message;
};
+11 -17
View File
@@ -24,26 +24,24 @@
namespace Common::Log {
// Some IDEs prefer <file>:<line> instead, so let's just do that :)
std::string FormatLogMessage(const Entry& entry) noexcept {
std::string FormatLogMessage(const Entry& entry) {
if (!entry.filename) return "";
auto const time_seconds = uint32_t(entry.timestamp.count() / 1000000);
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
auto const class_name = GetLogClassName(entry.log_class);
auto const level_name = GetLevelName(entry.log_level);
char const* class_name = GetLogClassName(entry.log_class);
char const* level_name = GetLevelName(entry.log_level);
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional,
class_name, level_name, entry.filename, entry.line_num, entry.function,
entry.message);
}
/// @brief Formats and prints a log entry to stderr.
static void PrintMessage(const Entry& entry) noexcept {
void PrintMessage(const Entry& entry) {
#ifdef _WIN32
auto const str = FormatLogMessage(entry).append(1, '\n');
fwrite(str.c_str(), 1, str.size(), stderr);
#else
#define ESC "\x1b"
auto const color_str = [&entry]() -> const char* {
auto str = std::string{[&entry]() -> const char* {
switch (entry.log_level) {
case Level::Debug: return ESC "[0;36m"; // Cyan
case Level::Info: return ESC "[0;37m"; // Bright gray
@@ -52,19 +50,15 @@ static void PrintMessage(const Entry& entry) noexcept {
case Level::Critical: return ESC "[1;35m"; // Bright magenta
default: return ESC "[1;30m"; // Grey
}
}();
auto const time_seconds = uint32_t(entry.timestamp.count() / 1000000);
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
auto const class_name = GetLogClassName(entry.log_class);
auto const level_name = GetLevelName(entry.log_level);
fprintf(stderr, "%s[%4d.%06d] %s <%s> %s:%u:%s: %s" ESC "[0m\n", color_str,
time_seconds, time_fractional, class_name, level_name, entry.filename,
entry.line_num, entry.function, entry.message.c_str());
}()};
str.append(FormatLogMessage(entry));
str.append(ESC "[0m\n");
#undef ESC
#endif
fwrite(str.c_str(), 1, str.size(), stderr);
}
void PrintColoredMessage(const Entry& entry) noexcept {
void PrintColoredMessage(const Entry& entry) {
#ifdef _WIN32
HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
if (console_handle == INVALID_HANDLE_VALUE)
@@ -90,7 +84,7 @@ void PrintColoredMessage(const Entry& entry) noexcept {
#endif
}
void PrintMessageToLogcat(const Entry& entry) noexcept {
void PrintMessageToLogcat(const Entry& entry) {
#ifdef ANDROID
android_LogPriority android_log_priority = [&]() {
switch (entry.log_level) {
+5 -6
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -13,9 +10,11 @@ namespace Common::Log {
struct Entry;
/// Formats a log entry into the provided text buffer.
std::string FormatLogMessage(const Entry& entry) noexcept;
std::string FormatLogMessage(const Entry& entry);
/// Formats and prints a log entry to stderr.
void PrintMessage(const Entry& entry);
/// Prints the same message as `PrintMessage`, but colored according to the severity level.
void PrintColoredMessage(const Entry& entry) noexcept;
void PrintColoredMessage(const Entry& entry);
/// Formats and prints a log entry to the android logcat.
void PrintMessageToLogcat(const Entry& entry) noexcept;
void PrintMessageToLogcat(const Entry& entry);
} // namespace Common::Log
+34 -32
View File
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <version>
#include "common/settings_enums.h"
#if __cpp_lib_chrono >= 201907L
#include <chrono>
#include <exception>
@@ -16,10 +17,8 @@
#include <functional>
#include <string_view>
#include <type_traits>
#include <deque>
#include <fmt/core.h>
#include "common/settings_enums.h"
#include "common/assert.h"
#include "common/fs/fs_util.h"
#include "common/fs/path_util.h"
@@ -111,38 +110,36 @@ std::string GetTimeZoneString(TimeZone time_zone) {
}
void LogSettings() {
std::deque<std::string> settings_list;
const auto log_setting = [](std::string_view name, const auto& value) {
LOG_INFO(Config, "{}: {}", name, value);
};
const auto log_path = [](std::string_view name, const std::filesystem::path& path) {
LOG_INFO(Config, "{}: {}", name, Common::FS::PathToUTF8String(path));
};
LOG_INFO(Config, "Eden Configuration:");
for (auto& [category, settings] : values.linkage.by_category) {
for (const auto& setting : settings) {
// Hide the token secret, for security reasons.
if (setting->Id() != values.eden_token.Id()) {
auto const is_default = setting->ToString() == setting->DefaultToString();
auto const name = fmt::format(
"{:c}{:c} {}.{}",
is_default ? '-' : 'M',
setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category),
setting->GetLabel());
if (is_default)
settings_list.push_back(fmt::format("{}: {}\n", name, setting->Canonicalize()));
else
settings_list.push_front(fmt::format("{}: {}\n", name, setting->Canonicalize()));
if (setting->Id() == values.eden_token.Id()) {
// Hide the token secret, for security reasons.
continue;
}
const auto name = fmt::format(
"{:c}{:c} {}.{}", setting->ToString() == setting->DefaultToString() ? '-' : 'M',
setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category),
setting->GetLabel());
log_setting(name, setting->Canonicalize());
}
}
std::string settings_str{};
for (auto const& e : settings_list)
settings_str += e;
LOG_INFO(Config, "Eden Configuration:\n{}", settings_str);
#define LOG_PATH(NAME) \
LOG_INFO(Config, #NAME ": {}", Common::FS::PathToUTF8String(Common::FS::GetEdenPath(Common::FS::EdenPath::NAME)))
LOG_PATH(CacheDir);
LOG_PATH(ConfigDir);
LOG_PATH(LoadDir);
LOG_PATH(NANDDir);
LOG_PATH(SaveDir);
LOG_PATH(SDMCDir);
#undef LOG_PATH
log_path("DataStorage_CacheDir", Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir));
log_path("DataStorage_ConfigDir", Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir));
log_path("DataStorage_LoadDir", Common::FS::GetEdenPath(Common::FS::EdenPath::LoadDir));
log_path("DataStorage_NANDDir", Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir));
log_path("DataStorage_SaveDir", Common::FS::GetEdenPath(Common::FS::EdenPath::SaveDir));
log_path("DataStorage_SDMCDir", Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir));
}
bool getDebugKnobAt(u8 i) {
@@ -174,10 +171,12 @@ bool IsDMALevelSafe() {
}
bool IsFastmemEnabled() {
if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging)
if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging) {
return bool(values.cpuopt_fastmem);
else if (values.cpu_accuracy.GetValue() == CpuAccuracy::Unsafe)
}
if (values.cpu_accuracy.GetValue() == CpuAccuracy::Unsafe) {
return bool(values.cpuopt_unsafe_host_mmu);
}
#if !defined(__APPLE__) && !defined(__linux__) && !defined(__ANDROID__) && !defined(_WIN32)
return false;
#else
@@ -342,7 +341,10 @@ void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info)
info.down_shift = 0;
break;
default:
UNREACHABLE();
ASSERT(false);
info.up_scale = 1;
info.down_shift = 0;
break;
}
info.up_factor = static_cast<f32>(info.up_scale) / (1U << info.down_shift);
info.down_factor = static_cast<f32>(1U << info.down_shift) / info.up_scale;
-1
View File
@@ -715,7 +715,6 @@ struct Values {
Category::DataStorage};
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
Category::DataStorage};
std::vector<std::string> external_content_dirs;
// Debugging
bool record_frame_times;
-1
View File
@@ -155,7 +155,6 @@ ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
ENUM(TemperatureUnits, Celsius, Fahrenheit)
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
ENUM(GameListMode, TreeView, GridView);
template <typename Type>
inline std::string_view CanonicalizeEnum(Type id) {
+14 -11
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -13,16 +10,27 @@
namespace Common {
#ifdef __ANDROID__
template <typename T>
T* LookupLibcSymbol(const char* name) {
#if defined(__BIONIC__)
Common::DynamicLibrary provider("libc.so");
ASSERT_MSG(provider.IsOpen(), "Failed to open libc!");
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);
}
ASSERT_MSG(sym != nullptr, "Unable to find symbol {}!", name);
if (sym == nullptr) {
UNREACHABLE_MSG("Unable to find symbol {}!", name);
}
return reinterpret_cast<T*>(sym);
}
@@ -30,10 +38,5 @@ 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);
}
#else
int SigAction(int signum, const struct sigaction* act, struct sigaction* oldact) {
return sigaction(signum, act, oldact);
}
#endif
} // namespace Common
+78
View File
@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#ifdef _MSC_VER
#include <intrin.h>
#endif
#include "common/x64/cpu_detect.h"
#include "common/x64/cpu_wait.h"
#include "common/x64/rdtsc.h"
namespace Common::X64 {
namespace {
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
// For reference:
// At 1 GHz, 100K cycles is 100us
// At 2 GHz, 100K cycles is 50us
// At 4 GHz, 100K cycles is 25us
constexpr auto PauseCycles = 100'000U;
} // Anonymous namespace
#if defined(_MSC_VER) && !defined(__clang__)
__forceinline static void TPAUSE() {
static constexpr auto RequestC02State = 0U;
_tpause(RequestC02State, FencedRDTSC() + PauseCycles);
}
__forceinline static void MWAITX() {
static constexpr auto EnableWaitTimeFlag = 1U << 1;
static constexpr auto RequestC1State = 0U;
// monitor_var should be aligned to a cache line.
alignas(64) u64 monitor_var{};
_mm_monitorx(&monitor_var, 0, 0);
_mm_mwaitx(EnableWaitTimeFlag, RequestC1State, PauseCycles);
}
#else
static void TPAUSE() {
static constexpr auto RequestC02State = 0U;
const auto tsc = FencedRDTSC() + PauseCycles;
const auto eax = static_cast<u32>(tsc & 0xFFFFFFFF);
const auto edx = static_cast<u32>(tsc >> 32);
asm volatile("tpause %0" : : "r"(RequestC02State), "d"(edx), "a"(eax));
}
static void MWAITX() {
static constexpr auto EnableWaitTimeFlag = 1U << 1;
static constexpr auto RequestC1State = 0U;
// monitor_var should be aligned to a cache line.
alignas(64) u64 monitor_var{};
asm volatile("monitorx" : : "a"(&monitor_var), "c"(0), "d"(0));
asm volatile("mwaitx" : : "a"(RequestC1State), "b"(PauseCycles), "c"(EnableWaitTimeFlag));
}
#endif
void MicroSleep() {
static const bool has_waitpkg = GetCPUCaps().waitpkg;
static const bool has_monitorx = GetCPUCaps().monitorx;
if (has_waitpkg) {
TPAUSE();
} else if (has_monitorx) {
MWAITX();
} else {
std::this_thread::yield();
}
}
} // namespace Common::X64
+10
View File
@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace Common::X64 {
void MicroSleep();
} // namespace Common::X64
+67 -83
View File
@@ -13,14 +13,12 @@
#include "core/arm/nce/patcher.h"
#include "core/core.h"
#include "core/memory.h"
#include "core/hle/kernel/k_process.h"
#include "dynarmic/common/context.h"
#include "core/hle/kernel/k_process.h"
#include <signal.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <pthread.h>
namespace Core {
@@ -35,67 +33,95 @@ static_assert(offsetof(NativeExecutionParameters, native_context) == TpidrEl0Nat
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);
}
using namespace Common::Literals;
constexpr u32 StackSize = 128_KiB;
} // namespace
void* ArmNce::RestoreGuestContext(void* raw_context) {
CTX_DECLARE(raw_context);
// Restore all guest state except tpidr_el0.
// 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*>(CTX_X(9));
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(), &CTX_Q(8),
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 = CTX_SP;
CTX_PC = guest_ctx->sp;
CTX_SP = guest_ctx->pc;
CTX_PSTATE = guest_ctx->pstate;
CTX_FPCR = guest_ctx->fpcr;
CTX_FPSR = guest_ctx->fpsr;
std::memcpy(&CTX_X(0), guest_ctx->cpu_registers.data(), sizeof(guest_ctx->cpu_registers));
std::memcpy(&CTX_Q(0), guest_ctx->vector_registers.data(), sizeof(guest_ctx->vector_registers));
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 ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
CTX_DECLARE(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(), &CTX_X(0), sizeof(guest_ctx->cpu_registers));
std::memcpy(guest_ctx->vector_registers.data(), &CTX_Q(0), sizeof(guest_ctx->vector_registers));
guest_ctx->fpsr = CTX_FPSR;
guest_ctx->fpcr = CTX_FPCR;
guest_ctx->pc = CTX_PC;
guest_ctx->sp = CTX_SP;
guest_ctx->pstate = u32(CTX_PSTATE);
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.
CTX_SP = guest_ctx->host_ctx.host_sp;
host_ctx.sp = guest_ctx->host_ctx.host_sp;
// Restore host callee-saved registers.
std::memcpy(&CTX_X(19), guest_ctx->host_ctx.host_saved_regs.data(),
std::memcpy(&host_ctx.regs[19], guest_ctx->host_ctx.host_saved_regs.data(),
sizeof(guest_ctx->host_ctx.host_saved_regs));
std::memcpy(&CTX_Q(8), guest_ctx->host_ctx.host_saved_vregs.data(),
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.
CTX_PC = guest_ctx->host_ctx.host_saved_regs[11];
host_ctx.pc = guest_ctx->host_ctx.host_saved_regs[11];
// Clear esr_el1 and return it.
CTX_X(0) = guest_ctx->esr_el1.exchange(0);
host_ctx.regs[0] = guest_ctx->esr_el1.exchange(0);
}
bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
CTX_DECLARE(raw_context);
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
auto* info = static_cast<siginfo_t*>(raw_info);
// We can't handle the access, so determine why we crashed.
auto const is_prefetch_abort = CTX_PC == reinterpret_cast<u64>(info->si_addr);
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) {
CTX_PC += 4;
host_ctx.pc += 4;
return true;
}
@@ -116,13 +142,17 @@ bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, voi
}
bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
CTX_DECLARE(raw_context);
auto& memory = guest_ctx->system->ApplicationMemory();
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
auto* fpctx = GetFloatingPointState(host_ctx);
auto& memory = guest_ctx->parent->m_running_thread->GetOwnerProcess()->GetMemory();
// Match and execute an instruction.
if (auto next_pc = MatchAndExecuteOneInstruction(memory, raw_context); next_pc) {
CTX_PC = *next_pc;
auto next_pc = MatchAndExecuteOneInstruction(memory, &host_ctx, fpctx);
if (next_pc) {
host_ctx.pc = *next_pc;
return true;
}
// We couldn't handle the access.
return HandleFailedGuestFault(guest_ctx, raw_info, raw_context);
}
@@ -246,51 +276,9 @@ ArmNce::ArmNce(System& system, bool uses_wall_clock, std::size_t core_index)
ArmNce::~ArmNce() = default;
// Borrowed from libusb
static unsigned int posix_gettid(void) {
static thread_local unsigned int tl_tid;
int tid;
if (tl_tid)
return tl_tid;
#if defined(__ANDROID__)
tid = gettid();
#elif defined(__APPLE__)
#ifdef HAVE_PTHREAD_THREADID_NP
uint64_t thread_id;
if (pthread_threadid_np(NULL, &thread_id) == 0)
tid = (int)thread_id;
else
tid = -1;
#else
tid = (int)pthread_mach_thread_np(pthread_self());
#endif
#elif defined(__HAIKU__)
tid = get_pthread_thread_id(pthread_self());
#elif defined(__linux__)
tid = (int)syscall(SYS_gettid);
#elif defined(__NetBSD__)
tid = _lwp_self();
#elif defined(__OpenBSD__)
/* The following only works with OpenBSD > 5.1 as it requires
* real thread support. For 5.1 and earlier, -1 is returned. */
tid = syscall(SYS_getthrid);
#elif defined(__sun__)
tid = _lwp_self();
#else
tid = -1;
#endif
if (tid == -1) {
/* If we don't have a thread ID, at least return a unique
* value that can be used to distinguish individual
* threads. */
tid = (int)(intptr_t)pthread_self();
}
return tl_tid = (unsigned int)tid;
}
void ArmNce::Initialize() {
if (m_thread_id == -1) {
m_thread_id = posix_gettid();
m_thread_id = gettid();
}
// Configure signal stack.
@@ -321,7 +309,7 @@ void ArmNce::Initialize() {
&ArmNce::ReturnToRunCodeByExceptionLevelChangeSignalHandler);
return_to_run_code_action.sa_mask = signal_mask;
Common::SigAction(ReturnToRunCodeByExceptionLevelChangeSignal, &return_to_run_code_action,
nullptr);
nullptr);
struct sigaction break_from_run_code_action {};
break_from_run_code_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
@@ -393,11 +381,7 @@ void ArmNce::SignalInterrupt(Kernel::KThread* thread) {
if (params->is_running) {
// We should signal to the running thread.
// The running thread will unlock the thread context.
#ifdef __linux__
syscall(SYS_tkill, m_thread_id, BreakFromRunCodeSignal);
#else
pthread_kill(pthread_t(m_thread_id), int(BreakFromRunCodeSignal));
#endif
} else {
// If the thread is no longer running, we have nothing to do.
UnlockThreadParameters(params);
+22 -90
View File
@@ -9,15 +9,10 @@
/* static HaltReason Core::ArmNce::ReturnToRunCodeByTrampoline(void* tpidr, Core::GuestContext* ctx, u64 trampoline_addr) */
#ifdef __APPLE__
.global __ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEy
__ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEy:
#else
.section .text._ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, "ax", %progbits
.type _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, %function
.global _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm
.type _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm, %function
_ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm:
#endif
/* Back up host sp to x3. */
/* Back up host tpidr_el0 to x4. */
mov x3, sp
@@ -55,52 +50,38 @@ _ZN4Core6ArmNce27ReturnToRunCodeByTrampolineEPvPNS_12GuestContextEm:
/* static HaltReason Core::ArmNce::ReturnToRunCodeByExceptionLevelChange(int tid, void* tpidr) */
#ifdef __APPLE__
.global __ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv
__ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv:
#else
.section .text._ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv, "ax", %progbits
.type _ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv, %function
.global _ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv
.type _ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv, %function
_ZN4Core6ArmNce37ReturnToRunCodeByExceptionLevelChangeEiPv:
#endif
/* This jumps to the signal handler, which will restore the entire context. */
/* On entry, x0 = thread id, which is already in the right place. Even on macOS. */
mov x9, x1 /* Move tpidr to x9 so it is not trampled. */
mov x1, #(ReturnToRunCodeByExceptionLevelChangeSignal)
#ifdef __APPLE__
/* I can never be happy, why no tkill in mach kernel? Ugh ... */
/* Signature: 328 AUE_PTHREADKILL ALL { int __pthread_kill(int thread_port, int sig); } */
mov x16, #(328)
svc #0x80 /* Tail call the signal handler. */
brk #0xF000 /* See: https://discourse.llvm.org/t/stepping-over-a-brk-instruction-on-arm64/69766/7 */
#else
/* Signature: int tgkill(pid_t tgid, pid_t tid, int sig); */
/* 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)
svc #0 /* Tail call the signal handler. */
brk #1000 /* Block execution from flowing here. */
#endif
mov x1, #(ReturnToRunCodeByExceptionLevelChangeSignal)
/* Tail call the signal handler. */
svc #0
/* Block execution from flowing here. */
brk #1000
/* static void Core::ArmNce::ReturnToRunCodeByExceptionLevelChangeSignalHandler(int sig, void* info, void* raw_context) */
#ifdef __APPLE__
.global __ZN4Core6ArmNce50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_
__ZN4Core6ArmNce50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_:
#else
.section .text._ZN4Core6ArmNce50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_, "ax", %progbits
.type _ZN4Core6ArmNce50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_, %function
.global _ZN4Core6ArmNce50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_
.type _ZN4Core6ArmNce50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_, %function
_ZN4Core6ArmNce50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_:
#endif
stp x29, x30, [sp, #-0x10]!
mov x29, sp
/* Call the context restorer with the raw context. */
mov x0, x2
#ifdef __APPLE__
bl __ZN4Core6ArmNce19RestoreGuestContextEPv
#else
bl _ZN4Core6ArmNce19RestoreGuestContextEPv
#endif
/* Save the old value of tpidr_el0. */
mrs x8, tpidr_el0
@@ -111,11 +92,7 @@ _ZN4Core6ArmNce50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_:
msr tpidr_el0, x0
/* Unlock the context. */
#ifdef __APPLE__
bl __ZN4Core6ArmNce22UnlockThreadParametersEPv
#else
bl _ZN4Core6ArmNce22UnlockThreadParametersEPv
#endif
/* Returning from here will enter the guest. */
ldp x29, x30, [sp], #0x10
@@ -123,15 +100,10 @@ _ZN4Core6ArmNce50ReturnToRunCodeByExceptionLevelChangeSignalHandlerEiPvS1_:
/* static void Core::ArmNce::BreakFromRunCodeSignalHandler(int sig, void* info, void* raw_context) */
#ifdef __APPLE__
.global __ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_
__ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_:
#else
.section .text._ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_, "ax", %progbits
.type _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_, %function
.global _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_
.type _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_, %function
_ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_:
#endif
/* Check to see if we have the correct TLS magic. */
mrs x8, tpidr_el0
ldr w9, [x8, #(TpidrEl0TlsMagic)]
@@ -149,11 +121,7 @@ _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_:
/* Tail call the restorer. */
mov x1, x2
#ifdef __APPLE__
b __ZN4Core6ArmNce16SaveGuestContextEPNS_12GuestContextEPv
#else
b _ZN4Core6ArmNce16SaveGuestContextEPNS_12GuestContextEPv
#endif
/* Returning from here will enter host code. */
@@ -163,15 +131,10 @@ _ZN4Core6ArmNce29BreakFromRunCodeSignalHandlerEiPvS1_:
/* static void Core::ArmNce::GuestAlignmentFaultSignalHandler(int sig, void* info, void* raw_context) */
#ifdef __APPLE__
.global __ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_
__ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_:
#else
.section .text._ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, "ax", %progbits
.type _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, %function
.global _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_
.type _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_, %function
_ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_:
#endif
/* Check to see if we have the correct TLS magic. */
mrs x8, tpidr_el0
ldr w9, [x8, #(TpidrEl0TlsMagic)]
@@ -183,11 +146,7 @@ _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_:
/* Incorrect TLS magic, so this is a host fault. */
/* Tail call the handler. */
#ifdef __APPLE__
b __ZN4Core6ArmNce24HandleHostAlignmentFaultEiPvS1_
#else
b _ZN4Core6ArmNce24HandleHostAlignmentFaultEiPvS1_
#endif
1:
/* Correct TLS magic, so this is a guest fault. */
@@ -204,11 +163,7 @@ _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_:
msr tpidr_el0, x3
/* Call the handler. */
#ifdef __APPLE__
bl __ZN4Core6ArmNce25HandleGuestAlignmentFaultEPNS_12GuestContextEPvS3_
#else
bl _ZN4Core6ArmNce25HandleGuestAlignmentFaultEPNS_12GuestContextEPvS3_
#endif
/* If the handler returned false, we want to preserve the host tpidr_el0. */
cbz x0, 2f
@@ -222,15 +177,10 @@ _ZN4Core6ArmNce32GuestAlignmentFaultSignalHandlerEiPvS1_:
ret
/* static void Core::ArmNce::GuestAccessFaultSignalHandler(int sig, void* info, void* raw_context) */
#ifdef __APPLE__
.global __ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_
__ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_:
#else
.section .text._ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, "ax", %progbits
.type _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, %function
.global _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_
.type _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_, %function
_ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_:
#endif
/* Check to see if we have the correct TLS magic. */
mrs x8, tpidr_el0
ldr w9, [x8, #(TpidrEl0TlsMagic)]
@@ -242,11 +192,7 @@ _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_:
/* Incorrect TLS magic, so this is a host fault. */
/* Tail call the handler. */
#ifdef __APPLE__
b __ZN4Core6ArmNce21HandleHostAccessFaultEiPvS1_
#else
b _ZN4Core6ArmNce21HandleHostAccessFaultEiPvS1_
#endif
1:
/* Correct TLS magic, so this is a guest fault. */
@@ -263,11 +209,7 @@ _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_:
msr tpidr_el0, x3
/* Call the handler. */
#ifdef __APPLE__
bl __ZN4Core6ArmNce22HandleGuestAccessFaultEPNS_12GuestContextEPvS3_
#else
bl _ZN4Core6ArmNce22HandleGuestAccessFaultEPNS_12GuestContextEPvS3_
#endif
/* If the handler returned false, we want to preserve the host tpidr_el0. */
cbz x0, 2f
@@ -282,15 +224,10 @@ _ZN4Core6ArmNce29GuestAccessFaultSignalHandlerEiPvS1_:
/* static void Core::ArmNce::LockThreadParameters(void* tpidr) */
#ifdef __APPLE__
.global __ZN4Core6ArmNce20LockThreadParametersEPv
__ZN4Core6ArmNce20LockThreadParametersEPv:
#else
.section .text._ZN4Core6ArmNce20LockThreadParametersEPv, "ax", %progbits
.type _ZN4Core6ArmNce20LockThreadParametersEPv, %function
.global _ZN4Core6ArmNce20LockThreadParametersEPv
.type _ZN4Core6ArmNce20LockThreadParametersEPv, %function
_ZN4Core6ArmNce20LockThreadParametersEPv:
#endif
/* Offset to lock member. */
add x0, x0, #(TpidrEl0Lock)
@@ -315,15 +252,10 @@ _ZN4Core6ArmNce20LockThreadParametersEPv:
/* static void Core::ArmNce::UnlockThreadParameters(void* tpidr) */
#ifdef __APPLE__
.global __ZN4Core6ArmNce22UnlockThreadParametersEPv
__ZN4Core6ArmNce22UnlockThreadParametersEPv:
#else
.section .text._ZN4Core6ArmNce22UnlockThreadParametersEPv, "ax", %progbits
.type _ZN4Core6ArmNce22UnlockThreadParametersEPv, %function
.global _ZN4Core6ArmNce22UnlockThreadParametersEPv
.type _ZN4Core6ArmNce22UnlockThreadParametersEPv, %function
_ZN4Core6ArmNce22UnlockThreadParametersEPv:
#endif
/* Offset to lock member. */
add x0, x0, #(TpidrEl0Lock)
+6 -16
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project */
/* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -8,24 +5,22 @@
#define __ASSEMBLY__
#ifdef __APPLE__
/* https://cpip.readthedocs.io/en/stable/_static/dictobject.c/signal.h_bbe000f9714f274340a28e000a369354.html */
#define ReturnToRunCodeByExceptionLevelChangeSignal 31
#define BreakFromRunCodeSignal 16
#define GuestAccessFaultSignal 11
#define GuestAlignmentFaultSignal 10
#else
#include <asm-generic/signal.h>
#include <asm-generic/unistd.h>
#define ReturnToRunCodeByExceptionLevelChangeSignal SIGUSR2
#define BreakFromRunCodeSignal SIGURG
#define GuestAccessFaultSignal SIGSEGV
#define GuestAlignmentFaultSignal SIGBUS
#endif
#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
@@ -33,8 +28,3 @@
#define SpinLockLocked 0
#define SpinLockUnlocked 1
#define HostContextSpTpidrEl0 0xE0
#define HostContextTpidrEl0 0xE8
#define HostContextRegs 0x0
#define HostContextVregs 0x60
+12 -15
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -7,8 +7,6 @@
#include <numeric>
#include "core/arm/nce/interpreter_visitor.h"
#include "core/memory.h"
#include "dynarmic/common/context.h"
namespace Core {
@@ -763,25 +761,24 @@ bool InterpreterVisitor::LDR_reg_fpsimd(Imm<2> size, Imm<1> opc_1, Reg Rm, Imm<3
return this->SIMDOffset(scale, shift, opc_0, Rm, option, Rn, Vt);
}
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, void* raw_context) {
CTX_DECLARE(raw_context);
std::span<u64, 31> regs(reinterpret_cast<u64*>(&CTX_X(0)), 31);
std::span<u128, 32> vregs(reinterpret_cast<u128*>(&CTX_Q(0)), 32);
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
fpsimd_context* fpsimd_context) {
std::span<u64, 31> regs(reinterpret_cast<u64*>(context->regs), 31);
std::span<u128, 32> vregs(reinterpret_cast<u128*>(fpsimd_context->vregs), 32);
u64& sp = *reinterpret_cast<u64*>(&context->sp);
const u64& pc = *reinterpret_cast<u64*>(&context->pc);
// Store temporal to not break aliasing rules :)
u64 tmp_sp = CTX_SP;
u64 tmp_pc = CTX_PC;
u32 instruction = memory.Read32(tmp_pc);
InterpreterVisitor visitor(memory, regs, vregs, sp, pc);
u32 instruction = memory.Read32(pc);
bool was_executed = false;
InterpreterVisitor visitor(memory, regs, vregs, tmp_sp, tmp_pc);
if (auto decoder = Dynarmic::A64::Decode<VisitorBase>(instruction)) {
was_executed = decoder->get().call(visitor, instruction);
} else {
LOG_ERROR(Core_ARM, "Unallocated encoding: {:#x}", instruction);
}
CTX_SP = tmp_sp;
CTX_PC = tmp_pc;
return was_executed ? std::optional<u64>(tmp_pc + 4) : std::nullopt;
return was_executed ? std::optional<u64>(pc + 4) : std::nullopt;
}
} // namespace Core
+3 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -9,7 +9,6 @@
#include <atomic>
#include <signal.h>
#include <span>
#include <unistd.h>
#include <span>
@@ -106,6 +105,7 @@ private:
const u64& m_pc;
};
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, void* raw_context);
std::optional<u64> MatchAndExecuteOneInstruction(Core::Memory::Memory& memory, mcontext_t* context,
fpsimd_context* fpsimd_context);
} // namespace Core
+153 -326
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <numeric>
@@ -20,13 +20,9 @@ namespace Core::NCE {
Patcher::Patcher(Patcher&& other) noexcept
: patch_cache(std::move(other.patch_cache)),
m_patch_instructions(std::move(other.m_patch_instructions)),
m_patch_instructions_pre(std::move(other.m_patch_instructions_pre)),
c(m_patch_instructions),
c_pre(m_patch_instructions_pre),
m_save_context(other.m_save_context),
m_load_context(other.m_load_context),
m_save_context_pre(other.m_save_context_pre),
m_load_context_pre(other.m_load_context_pre),
mode(other.mode),
total_program_size(other.total_program_size),
m_relocate_module_index(other.m_relocate_module_index),
@@ -46,25 +42,20 @@ using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
constexpr size_t MaxRelativeBranch = 128_MiB;
constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
Patcher::Patcher() : c(m_patch_instructions), c_pre(m_patch_instructions_pre) {
Patcher::Patcher() : c(m_patch_instructions) {
LOG_WARNING(Core_ARM, "Patcher initialized with LRU cache {}",
patch_cache.isEnabled() ? "enabled" : "disabled");
// The first word of the patch section is always a branch to the first instruction of the
// module.
c.dw(0);
c_pre.dw(0);
// Write save context helper function.
c.l(m_save_context);
WriteSaveContext();
c_pre.l(m_save_context_pre);
WriteSaveContext(c_pre);
// Write load context helper function.
c.l(m_load_context);
WriteLoadContext();
c_pre.l(m_load_context_pre);
WriteLoadContext(c_pre);
}
Patcher::~Patcher() = default;
@@ -73,16 +64,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
const size_t image_size = program_image.size();
// Check if we need split mode for large modules. A64 max takes 128MB
// tests showed that, with update, some are larger. (In this case 208MB)
bool use_split = false;
if (image_size > MaxRelativeBranch) {
if (total_program_size > 0) {
return false;
}
use_split = true;
} else if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) {
if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) {
return false;
}
@@ -92,12 +74,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
// The first word of the patch section is always a branch to the first instruction of the
// module.
if (use_split) {
// curr_patch->m_branch_to_module_relocations.push_back({0, 0});
curr_patch->m_branch_to_module_relocations_pre.push_back({0, 0});
} else {
curr_patch->m_branch_to_module_relocations.push_back({0, 0});
}
curr_patch->m_branch_to_module_relocations.push_back({0, 0});
// Retrieve text segment data.
const auto text = std::span{program_image}.subspan(code.offset, code.size);
@@ -108,18 +85,12 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
for (u32 i = ModuleCodeIndex; i < static_cast<u32>(text_words.size()); i++) {
const u32 inst = text_words[i];
const auto AddRelocations = [&](bool& pre_buffer) {
const auto AddRelocations = [&] {
const uintptr_t this_offset = i * sizeof(u32);
const uintptr_t next_offset = this_offset + sizeof(u32);
pre_buffer = use_split && (this_offset < MaxRelativeBranch);
// Relocate to pre- or post-patch
if (pre_buffer) {
this->BranchToPatchPre(this_offset);
} else {
this->BranchToPatch(this_offset);
}
// Relocate from here to patch.
this->BranchToPatch(this_offset);
// Relocate from patch to next instruction.
return next_offset;
@@ -127,13 +98,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
// SVC
if (auto svc = SVC{inst}; svc.Verify()) {
bool pre_buffer = false;
auto ret = AddRelocations(pre_buffer);
if (pre_buffer) {
WriteSvcTrampoline(ret, svc.GetValue(), c_pre, m_save_context_pre, m_load_context_pre);
} else {
WriteSvcTrampoline(ret, svc.GetValue(), c, m_save_context, m_load_context);
}
WriteSvcTrampoline(AddRelocations(), svc.GetValue());
continue;
}
@@ -144,25 +109,13 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
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())};
bool pre_buffer = false;
auto ret = AddRelocations(pre_buffer);
if (pre_buffer) {
WriteMrsHandler(ret, dest_reg, src_reg, c_pre);
} else {
WriteMrsHandler(ret, dest_reg, src_reg, c);
}
WriteMrsHandler(AddRelocations(), dest_reg, src_reg);
continue;
}
// MRS Xn, CNTPCT_EL0
if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntpctEl0) {
bool pre_buffer = false;
auto ret = AddRelocations(pre_buffer);
if (pre_buffer) {
WriteCntpctHandler(ret, oaknut::XReg{static_cast<int>(mrs.GetRt())}, c_pre);
} else {
WriteCntpctHandler(ret, oaknut::XReg{static_cast<int>(mrs.GetRt())}, c);
}
WriteCntpctHandler(AddRelocations(), oaknut::XReg{static_cast<int>(mrs.GetRt())});
continue;
}
@@ -173,13 +126,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
// MSR TPIDR_EL0, Xn
if (auto msr = MSR{inst}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) {
bool pre_buffer = false;
auto ret = AddRelocations(pre_buffer);
if (pre_buffer) {
WriteMsrHandler(ret, oaknut::XReg{static_cast<int>(msr.GetRt())}, c_pre);
} else {
WriteMsrHandler(ret, oaknut::XReg{static_cast<int>(msr.GetRt())}, c);
}
WriteMsrHandler(AddRelocations(), oaknut::XReg{static_cast<int>(msr.GetRt())});
continue;
}
@@ -190,11 +137,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
// Determine patching mode for the final relocation step
total_program_size += image_size;
if (use_split) {
this->mode = PatchMode::Split;
} else {
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
}
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
return true;
}
@@ -203,9 +146,7 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
Kernel::PhysicalMemory& program_image,
EntryTrampolines* out_trampolines) {
const size_t patch_size = GetSectionSize();
const size_t pre_patch_size = GetPreSectionSize();
const size_t image_size = (mode == PatchMode::Split) ? program_image.size() - pre_patch_size : program_image.size();
const size_t image_size = program_image.size();
// Retrieve text segment data.
const auto text = std::span{program_image}.subspan(code.offset, code.size);
@@ -221,16 +162,6 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
}
};
const auto ApplyBranchToPatchRelocationPre = [&](u32* target, const Relocation& rel) {
oaknut::CodeGenerator rc{target};
rc.B(static_cast<ptrdiff_t>(rel.patch_offset) - static_cast<ptrdiff_t>(pre_patch_size) - static_cast<ptrdiff_t>(rel.module_offset));
};
const auto ApplyBranchToPatchRelocationPostSplit = [&](u32* target, const Relocation& rel) {
oaknut::CodeGenerator rc{target};
rc.B(static_cast<ptrdiff_t>(image_size) + static_cast<ptrdiff_t>(rel.patch_offset) - static_cast<ptrdiff_t>(rel.module_offset));
};
const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) {
oaknut::CodeGenerator rc{target};
if (mode == PatchMode::PreText) {
@@ -240,16 +171,6 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
}
};
const auto ApplyBranchToModuleRelocationPre = [&](u32* target, const Relocation& rel) {
oaknut::CodeGenerator rc{target};
rc.B(static_cast<ptrdiff_t>(pre_patch_size) + static_cast<ptrdiff_t>(rel.module_offset) - static_cast<ptrdiff_t>(rel.patch_offset));
};
const auto ApplyBranchToModuleRelocationPostSplit = [&](u32* target, const Relocation& rel) {
oaknut::CodeGenerator rc{target};
rc.B(static_cast<ptrdiff_t>(rel.module_offset) - static_cast<ptrdiff_t>(image_size) - static_cast<ptrdiff_t>(rel.patch_offset));
};
const auto RebasePatch = [&](ptrdiff_t patch_offset) {
if (mode == PatchMode::PreText) {
return GetInteger(load_base) + patch_offset;
@@ -261,87 +182,28 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
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;
}
if (mode == PatchMode::Split) {
return GetInteger(load_base) + pre_patch_size + module_offset;
}
return GetInteger(load_base) + module_offset;
};
// We are now ready to relocate!
auto& patch = modules[m_relocate_module_index++];
if (mode == PatchMode::Split) {
for (const Relocation& rel : patch.m_branch_to_pre_patch_relocations) {
ApplyBranchToPatchRelocationPre(text_words.data() + rel.module_offset / sizeof(u32), rel);
}
LOG_DEBUG(Core_ARM, "applied Pre: {}", patch.m_branch_to_pre_patch_relocations.size());
for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
ApplyBranchToPatchRelocationPostSplit(text_words.data() + rel.module_offset / sizeof(u32), rel);
}
LOG_DEBUG(Core_ARM, "applied Post: {}", patch.m_branch_to_patch_relocations.size());
for (const Relocation& rel : patch.m_branch_to_module_relocations_pre) {
ApplyBranchToModuleRelocationPre(m_patch_instructions_pre.data() + rel.patch_offset / sizeof(u32), rel);
}
LOG_DEBUG(Core_ARM, "aplied Pre-module {}", patch.m_branch_to_module_relocations_pre.size());
for (const Relocation& rel : patch.m_branch_to_module_relocations) {
ApplyBranchToModuleRelocationPostSplit(m_patch_instructions.data() + rel.patch_offset / sizeof(u32), rel);
}
LOG_DEBUG(Core_ARM, "applied Post-module {}", patch.m_branch_to_module_relocations.size());
// Pre
for (const Relocation& rel : patch.m_write_module_pc_relocations_pre) {
oaknut::CodeGenerator rc{m_patch_instructions_pre.data() + rel.patch_offset / sizeof(u32)};
rc.dx(RebasePc(rel.module_offset));
}
// Post
for (const Relocation& rel : patch.m_write_module_pc_relocations) {
oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
rc.dx(RebasePc(rel.module_offset));
}
// Trampolines (split pre + post)
for (const Trampoline& rel : patch.m_trampolines_pre) {
out_trampolines->insert({RebasePc(rel.module_offset),
GetInteger(load_base) + rel.patch_offset});
}
for (const Trampoline& rel : patch.m_trampolines) {
out_trampolines->insert({RebasePc(rel.module_offset),
GetInteger(load_base) + pre_patch_size + image_size + rel.patch_offset});
}
if (!m_patch_instructions_pre.empty()) {
u32 insn = m_patch_instructions_pre[0];
if ((insn & 0xFC000000) == 0x14000000) {
s32 imm26 = insn & 0x3FFFFFF;
// Sign extend
if (imm26 & 0x2000000) imm26 |= 0xFC000000;
}
}
} else {
for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
}
for (const Relocation& rel : patch.m_branch_to_module_relocations) {
ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
rel);
}
// Rewrite PC constants
for (const Relocation& rel : patch.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 Relocation& rel : patch.m_branch_to_patch_relocations) {
ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
}
for (const Relocation& rel : patch.m_branch_to_module_relocations) {
ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
rel);
}
if (mode != PatchMode::Split) {
for (const Trampoline& rel : patch.m_trampolines) {
out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)});
}
// Rewrite PC constants and record post trampolines
for (const Relocation& rel : patch.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 : patch.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.
@@ -361,15 +223,6 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
ASSERT(image_size == total_program_size);
std::memcpy(program_image.data(), m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
} else if (this->mode == PatchMode::Split) {
const size_t current_size = program_image.size();
program_image.resize(current_size + patch_size);
// Copy pre-patch buffer to the beginning
std::memcpy(program_image.data(), m_patch_instructions_pre.data(),
m_patch_instructions_pre.size() * sizeof(u32));
// Same for post-patch buffer to the end
std::memcpy(program_image.data() + current_size, 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(),
@@ -385,225 +238,202 @@ size_t Patcher::GetSectionSize() const noexcept {
return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE);
}
size_t Patcher::GetPreSectionSize() const noexcept {
return Common::AlignUp(m_patch_instructions_pre.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE);
}
void Patcher::WriteLoadContext(oaknut::VectorCodeGenerator& cg) {
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.
cg.STR(X30, SP, 8);
cg.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
cg.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
c.STR(X30, SP, 8);
c.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
// Load system registers.
cg.LDR(W0, X30, offsetof(GuestContext, fpsr));
cg.MSR(oaknut::SystemReg::FPSR, X0);
cg.LDR(W0, X30, offsetof(GuestContext, fpcr));
cg.MSR(oaknut::SystemReg::FPCR, X0);
cg.LDR(W0, X30, offsetof(GuestContext, nzcv));
cg.MSR(oaknut::SystemReg::NZCV, X0);
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) {
cg.LDP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i);
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) {
cg.LDP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i);
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.
cg.LDR(X30, SP, 8);
cg.RET();
c.LDR(X30, SP, 8);
c.RET();
}
void Patcher::WriteSaveContext(oaknut::VectorCodeGenerator& cg) {
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.
cg.STR(X30, SP, 8);
cg.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
cg.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
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) {
cg.STP(oaknut::XReg{i}, oaknut::XReg{i + 1}, X30, 8 * i);
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) {
cg.STP(oaknut::QReg{i}, oaknut::QReg{i + 1}, X30, VEC_OFF + 16 * i);
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.
cg.STR(X0, SP, PRE_INDEXED, -16);
cg.LDR(X0, SP, 16);
cg.STR(X0, X30, 8 * 30);
cg.ADD(X0, SP, 32);
cg.STR(X0, X30, offsetof(GuestContext, sp));
cg.MRS(X0, oaknut::SystemReg::FPSR);
cg.STR(W0, X30, offsetof(GuestContext, fpsr));
cg.MRS(X0, oaknut::SystemReg::FPCR);
cg.STR(W0, X30, offsetof(GuestContext, fpcr));
cg.MRS(X0, oaknut::SystemReg::NZCV);
cg.STR(W0, X30, offsetof(GuestContext, nzcv));
cg.LDR(X0, SP, POST_INDEXED, 16);
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.
cg.LDR(X30, SP, 8);
cg.RET();
c.LDR(X30, SP, 8);
c.RET();
}
void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut::VectorCodeGenerator& cg, oaknut::Label& save_ctx, oaknut::Label& load_ctx) {
// Determine if we're writing to the pre-patch buffer
const bool is_pre = (&cg == &c_pre);
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(cg);
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.
cg.STR(X30, SP, PRE_INDEXED, -16);
cg.BL(save_ctx);
cg.LDR(X30, SP, POST_INDEXED, 16);
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;
cg.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
cg.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
cg.LDR(X2, pc_after_svc);
cg.STR(X2, X1, offsetof(GuestContext, pc));
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
cg.MOV(X2, svc_id);
cg.STR(W2, X1, offsetof(GuestContext, svc));
c.MOV(X2, svc_id);
c.STR(W2, X1, offsetof(GuestContext, svc));
// 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;
cg.ADD(X2, X1, offsetof(GuestContext, esr_el1));
cg.l(retry);
cg.LDAXR(X0, X2);
cg.STLXR(W3, XZR, X2);
cg.CBNZ(W3, 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.
cg.ORR(X0, X0, static_cast<u64>(HaltReason::SupervisorCall));
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.
cg.ADD(X1, X1, offsetof(GuestContext, host_ctx));
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));
cg.LDP(X2, X3, X1, offsetof(HostContext, host_sp));
cg.MOV(SP, X2);
cg.MSR(oaknut::SystemReg::TPIDR_EL0, X3);
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);
cg.LDP(X19, X20, X1, HOST_REGS_OFF);
cg.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64));
cg.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64));
cg.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64));
cg.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64));
cg.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64));
cg.LDP(Q8, Q9, X1, HOST_VREGS_OFF);
cg.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128));
cg.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128));
cg.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128));
cg.RET();
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.
if (is_pre) {
curr_patch->m_trampolines_pre.push_back({cg.offset(), module_dest});
} else {
curr_patch->m_trampolines.push_back({cg.offset(), module_dest});
}
curr_patch->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.
cg.MRS(X2, oaknut::SystemReg::TPIDR_EL0);
cg.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context));
cg.ADD(X0, X2, offsetof(GuestContext, host_ctx));
cg.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64));
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.
cg.STR(X30, SP, PRE_INDEXED, -16);
cg.BL(load_ctx);
cg.LDR(X30, SP, POST_INDEXED, 16);
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.
cg.STR(X1, SP, PRE_INDEXED, -16);
cg.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
cg.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
cg.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30);
cg.LDR(X1, SP, POST_INDEXED, 16);
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(cg);
this->UnlockContext();
// Jump back to the instruction after the emulated SVC.
if (&cg == &c_pre)
this->BranchToModulePre(module_dest);
else
this->BranchToModule(module_dest);
this->BranchToModule(module_dest);
// Store PC after call.
cg.l(pc_after_svc);
if (&cg == &c_pre)
this->WriteModulePcPre(module_dest);
else
this->WriteModulePc(module_dest);
c.l(pc_after_svc);
this->WriteModulePc(module_dest);
}
void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& cg) {
oaknut::SystemReg src_reg) {
// Retrieve emulated TLS register from GuestContext.
cg.MRS(dest_reg, oaknut::SystemReg::TPIDR_EL0);
c.MRS(dest_reg, oaknut::SystemReg::TPIDR_EL0);
if (src_reg == oaknut::SystemReg::TPIDRRO_EL0) {
cg.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0));
c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidrro_el0));
} else {
cg.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0));
c.LDR(dest_reg, dest_reg, offsetof(NativeExecutionParameters, tpidr_el0));
}
// Jump back to the instruction after the emulated MRS.
if (&cg == &c_pre)
this->BranchToModulePre(module_dest);
else
this->BranchToModule(module_dest);
this->BranchToModule(module_dest);
}
void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& cg) {
void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) {
const auto scratch_reg = src_reg.index() == 0 ? X1 : X0;
cg.STR(scratch_reg, SP, PRE_INDEXED, -16);
c.STR(scratch_reg, SP, PRE_INDEXED, -16);
// Save guest value to NativeExecutionParameters::tpidr_el0.
cg.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0);
cg.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0));
c.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0);
c.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0));
// Restore scratch register.
cg.LDR(scratch_reg, SP, POST_INDEXED, 16);
c.LDR(scratch_reg, SP, POST_INDEXED, 16);
// Jump back to the instruction after the emulated MSR.
if (&cg == &c_pre)
this->BranchToModulePre(module_dest);
else
this->BranchToModule(module_dest);
this->BranchToModule(module_dest);
}
void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) {
void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) {
static Common::Arm64::NativeClock clock{};
const auto factor = clock.GetGuestCNTFRQFactor();
const auto raw_factor = std::bit_cast<std::array<u64, 2>>(factor);
@@ -616,83 +446,80 @@ void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_
oaknut::Label factorhi;
// Save scratches.
cg.STP(scratch0, scratch1, SP, PRE_INDEXED, -16);
c.STP(scratch0, scratch1, SP, PRE_INDEXED, -16);
// Load counter value.
cg.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0);
c.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0);
// Load scaling factor.
cg.LDR(scratch0, factorlo);
cg.LDR(scratch1, factorhi);
c.LDR(scratch0, factorlo);
c.LDR(scratch1, factorhi);
// Multiply low bits and get result.
cg.UMULH(scratch0, dest_reg, scratch0);
c.UMULH(scratch0, dest_reg, scratch0);
// Multiply high bits and add low bit result.
cg.MADD(dest_reg, dest_reg, scratch1, scratch0);
c.MADD(dest_reg, dest_reg, scratch1, scratch0);
// Reload scratches.
cg.LDP(scratch0, scratch1, SP, POST_INDEXED, 16);
c.LDP(scratch0, scratch1, SP, POST_INDEXED, 16);
// Jump back to the instruction after the emulated MRS.
if (&cg == &c_pre)
this->BranchToModulePre(module_dest);
else
this->BranchToModule(module_dest);
this->BranchToModule(module_dest);
// Scaling factor constant values.
cg.l(factorlo);
cg.dx(raw_factor[0]);
cg.l(factorhi);
cg.dx(raw_factor[1]);
c.l(factorlo);
c.dx(raw_factor[0]);
c.l(factorhi);
c.dx(raw_factor[1]);
}
void Patcher::LockContext(oaknut::VectorCodeGenerator& cg) {
void Patcher::LockContext() {
oaknut::Label retry;
// Save scratches.
cg.STP(X0, X1, SP, PRE_INDEXED, -16);
c.STP(X0, X1, SP, PRE_INDEXED, -16);
// Reload lock pointer.
cg.l(retry);
cg.CLREX();
cg.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
cg.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
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.
cg.LDAXR(W1, X0);
c.LDAXR(W1, X0);
// If the value was SpinLockLocked, clear monitor and retry.
cg.CBZ(W1, retry);
c.CBZ(W1, retry);
// Store-conditional SpinLockLocked with relaxed ordering.
cg.STXR(W1, WZR, X0);
c.STXR(W1, WZR, X0);
// If we failed to store, retry.
cg.CBNZ(W1, retry);
c.CBNZ(W1, retry);
// We succeeded! Reload scratches.
cg.LDP(X0, X1, SP, POST_INDEXED, 16);
c.LDP(X0, X1, SP, POST_INDEXED, 16);
}
void Patcher::UnlockContext(oaknut::VectorCodeGenerator& cg) {
void Patcher::UnlockContext() {
// Save scratches.
cg.STP(X0, X1, SP, PRE_INDEXED, -16);
c.STP(X0, X1, SP, PRE_INDEXED, -16);
// Load lock pointer.
cg.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
cg.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
c.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
// Load SpinLockUnlocked.
cg.MOV(W1, SpinLockUnlocked);
c.MOV(W1, SpinLockUnlocked);
// Store value with release ordering.
cg.STLR(W1, X0);
c.STLR(W1, X0);
// Load scratches.
cg.LDP(X0, X1, SP, POST_INDEXED, 16);
c.LDP(X0, X1, SP, POST_INDEXED, 16);
}
} // namespace Core::NCE
+10 -44
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -41,7 +41,6 @@ enum class PatchMode : u32 {
None,
PreText, ///< Patch section is inserted before .text
PostData, ///< Patch section is inserted after .data
Split, ///< Patch sections are inserted before .text and after .data
};
using ModuleTextAddress = u64;
@@ -64,7 +63,6 @@ public:
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
size_t GetSectionSize() const noexcept;
size_t GetPreSectionSize() const noexcept;
[[nodiscard]] PatchMode GetPatchMode() const noexcept {
return mode;
@@ -78,25 +76,15 @@ private:
uintptr_t module_offset;
};
// Core implementations with explicit code generator
void WriteLoadContext(oaknut::VectorCodeGenerator& code);
void WriteSaveContext(oaknut::VectorCodeGenerator& code);
void LockContext(oaknut::VectorCodeGenerator& code);
void UnlockContext(oaknut::VectorCodeGenerator& code);
void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut::VectorCodeGenerator& code, oaknut::Label& save_ctx, oaknut::Label& load_ctx);
void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& code);
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& code);
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code);
// Convenience wrappers using default code generator
void WriteLoadContext() { WriteLoadContext(c); }
void WriteSaveContext() { WriteSaveContext(c); }
void LockContext() { LockContext(c); }
void UnlockContext() { UnlockContext(c); }
void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) { WriteSvcTrampoline(module_dest, svc_id, c, m_save_context, m_load_context); }
void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg) { WriteMrsHandler(module_dest, dest_reg, src_reg, c); }
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg) { WriteMsrHandler(module_dest, src_reg, c); }
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg) { WriteCntpctHandler(module_dest, dest_reg, c); }
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:
static constexpr size_t CACHE_SIZE = 16384; // Cache size for patch entries
@@ -123,34 +111,19 @@ private:
}
}
void BranchToPatchPre(uintptr_t module_dest) {
curr_patch->m_branch_to_pre_patch_relocations.push_back({c_pre.offset(), module_dest});
}
void BranchToModule(uintptr_t module_dest) {
curr_patch->m_branch_to_module_relocations.push_back({c.offset(), module_dest});
c.dw(0);
}
void BranchToModulePre(uintptr_t module_dest) {
curr_patch->m_branch_to_module_relocations_pre.push_back({c_pre.offset(), module_dest});
c_pre.dw(0);
}
void WriteModulePc(uintptr_t module_dest) {
curr_patch->m_write_module_pc_relocations.push_back({c.offset(), module_dest});
c.dx(0);
}
void WriteModulePcPre(uintptr_t module_dest) {
curr_patch->m_write_module_pc_relocations_pre.push_back({c_pre.offset(), module_dest});
c_pre.dx(0);
}
private:
// List of patch instructions we have generated.
std::vector<u32> m_patch_instructions{};
std::vector<u32> m_patch_instructions_pre{};
// Relocation type for relative branch from module to patch.
struct Relocation {
@@ -160,22 +133,15 @@ private:
struct ModulePatch {
std::vector<Trampoline> m_trampolines;
std::vector<Trampoline> m_trampolines_pre;
std::vector<Relocation> m_branch_to_patch_relocations{};
std::vector<Relocation> m_branch_to_pre_patch_relocations{};
std::vector<Relocation> m_branch_to_module_relocations{};
std::vector<Relocation> m_branch_to_module_relocations_pre{};
std::vector<Relocation> m_write_module_pc_relocations{};
std::vector<Relocation> m_write_module_pc_relocations_pre{};
std::vector<ModuleTextAddress> m_exclusives{};
};
oaknut::VectorCodeGenerator c;
oaknut::VectorCodeGenerator c_pre;
oaknut::Label m_save_context{};
oaknut::Label m_load_context{};
oaknut::Label m_save_context_pre{};
oaknut::Label m_load_context_pre{};
PatchMode mode{PatchMode::None};
size_t total_program_size{};
size_t m_relocate_module_index{};
+1 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
@@ -142,7 +142,6 @@ struct System::Impl {
void ReinitializeIfNecessary(System& system) {
const bool must_reinitialize =
!device_memory.has_value() ||
is_multicore != Settings::values.use_multi_core.GetValue() ||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
Settings::MemoryLayout::Memory_4Gb);
@@ -415,7 +414,6 @@ struct System::Impl {
cpu_manager.Shutdown();
debugger.reset();
kernel.Shutdown();
device_memory.reset();
stop_event = {};
Network::RestartSocketOperations();
+27 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -13,6 +13,10 @@
#include "common/windows/timer_resolution.h"
#endif
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_wait.h"
#endif
#include "common/settings.h"
#include "core/core_timing.h"
#include "core/hardware_properties.h"
@@ -283,7 +287,28 @@ void CoreTiming::ThreadLoop() {
if (next_time) {
// There are more events left in the queue, wait until the next event.
auto wait_time = *next_time - GetGlobalTimeNs().count();
event.WaitFor(std::chrono::nanoseconds(wait_time));
if (wait_time > 0) {
#ifdef _WIN32
while (!paused && !event.IsSet() && wait_time > 0) {
wait_time = *next_time - GetGlobalTimeNs().count();
if (wait_time >= timer_resolution_ns) {
Common::Windows::SleepForOneTick();
} else {
#ifdef ARCHITECTURE_x86_64
Common::X64::MicroSleep();
#else
std::this_thread::yield();
#endif
}
}
if (event.IsSet()) {
event.Reset();
}
#else
event.WaitFor(std::chrono::nanoseconds(wait_time));
#endif
}
} else {
// Queue is empty, wait until another event is scheduled and signals us to
// continue.
+43 -411
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -137,127 +137,12 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs;
const auto& disabled = Settings::values.disabled_addons[title_id];
bool update_disabled = true;
std::optional<u32> enabled_version;
bool checked_external = false;
bool checked_manual = false;
const auto* content_union = dynamic_cast<const ContentProviderUnion*>(&content_provider);
const auto update_tid = GetUpdateTitleID(title_id);
if (content_union) {
// First, check ExternalContentProvider
const auto* external_provider = content_union->GetExternalProvider();
if (external_provider) {
const auto update_versions = external_provider->ListUpdateVersions(update_tid);
if (!update_versions.empty()) {
checked_external = true;
for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
update_disabled = false;
enabled_version = update_entry.version;
break;
}
}
}
}
// Also check ManualContentProvider (for Android)
if (!checked_external) {
const auto* manual_provider = dynamic_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider) {
const auto manual_update_versions = manual_provider->ListUpdateVersions(update_tid);
if (!manual_update_versions.empty()) {
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
update_disabled = false;
enabled_version = update_entry.version;
break;
}
}
}
}
}
}
// check for original NAND style
// Check NAND if: no external updates exist, OR all external updates are disabled
if (!checked_external && !checked_manual) {
// Only enable NAND update if it exists AND is not disabled
// We need to check if an update actually exists in the content provider
const bool has_nand_update = content_provider.HasEntry(update_tid, ContentRecordType::Program);
if (has_nand_update) {
const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
const bool generic_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
if (!nand_disabled && !sdmc_disabled && !generic_disabled) {
update_disabled = false;
}
}
} else if (update_disabled && content_union) {
const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
if (!nand_disabled || !sdmc_disabled) {
const auto nand_sdmc_entries = content_union->ListEntriesFilterOrigin(
std::nullopt, TitleType::Update, ContentRecordType::Program, update_tid);
for (const auto& [slot, entry] : nand_sdmc_entries) {
if (slot == ContentProviderUnionSlot::UserNAND ||
slot == ContentProviderUnionSlot::SysNAND) {
if (!nand_disabled) {
update_disabled = false;
break;
}
} else if (slot == ContentProviderUnionSlot::SDMC) {
if (!sdmc_disabled) {
update_disabled = false;
break;
}
}
}
}
}
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
// Game Updates
std::unique_ptr<NCA> update = nullptr;
// If we have a specific enabled version from external provider, use it
if (enabled_version.has_value() && content_union) {
const auto* external_provider = content_union->GetExternalProvider();
if (external_provider) {
auto file = external_provider->GetEntryForVersion(update_tid, ContentRecordType::Program, *enabled_version);
if (file != nullptr) {
update = std::make_unique<NCA>(file);
}
}
// Also try ManualContentProvider
if (update == nullptr) {
const auto* manual_provider = dynamic_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider) {
auto file = manual_provider->GetEntryForVersion(update_tid, ContentRecordType::Program, *enabled_version);
if (file != nullptr) {
update = std::make_unique<NCA>(file);
}
}
}
}
// Fallback to regular content provider if no external update was loaded
if (update == nullptr && !update_disabled) {
update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
}
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
@@ -562,103 +447,21 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update_raw = content_provider.GetEntryRaw(update_tid, type);
const auto& disabled = Settings::values.disabled_addons[title_id];
bool update_disabled = true;
std::optional<u32> enabled_version;
VirtualFile update_raw = nullptr;
bool checked_external = false;
bool checked_manual = false;
const auto* content_union = dynamic_cast<const ContentProviderUnion*>(&content_provider);
if (content_union) {
// First, check ExternalContentProvider
const auto* external_provider = content_union->GetExternalProvider();
if (external_provider) {
const auto update_versions = external_provider->ListUpdateVersions(update_tid);
if (!update_versions.empty()) {
checked_external = true;
for (const auto& update_entry : update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = external_provider->GetEntryForVersion(update_tid, type, update_entry.version);
break;
}
}
}
}
if (!checked_external) {
const auto* manual_provider = dynamic_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider) {
const auto manual_update_versions = manual_provider->ListUpdateVersions(update_tid);
if (!manual_update_versions.empty()) {
checked_manual = true;
for (const auto& update_entry : manual_update_versions) {
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
if (std::find(disabled.cbegin(), disabled.cend(), disabled_key) == disabled.cend()) {
update_disabled = false;
enabled_version = update_entry.version;
update_raw = manual_provider->GetEntryForVersion(update_tid, type, update_entry.version);
break;
}
}
}
}
}
}
if (!checked_external && !checked_manual) {
const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
const bool generic_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
if (!nand_disabled && !sdmc_disabled && !generic_disabled) {
update_disabled = false;
}
if (!update_disabled) {
update_raw = content_provider.GetEntryRaw(update_tid, type);
}
} else if (update_disabled && content_union) {
const bool nand_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (NAND)") != disabled.cend();
const bool sdmc_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update (SDMC)") != disabled.cend();
if (!nand_disabled || !sdmc_disabled) {
const auto nand_sdmc_entries = content_union->ListEntriesFilterOrigin(
std::nullopt, TitleType::Update, type, update_tid);
for (const auto& [slot, entry] : nand_sdmc_entries) {
if (slot == ContentProviderUnionSlot::UserNAND ||
slot == ContentProviderUnionSlot::SysNAND) {
if (!nand_disabled) {
update_disabled = false;
update_raw = content_provider.GetEntryRaw(update_tid, type);
break;
}
} else if (slot == ContentProviderUnionSlot::SDMC) {
if (!sdmc_disabled) {
update_disabled = false;
update_raw = content_provider.GetEntryRaw(update_tid, type);
break;
}
}
}
}
}
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
if (!update_disabled && update_raw != nullptr && base_nca != nullptr) {
const auto new_nca = std::make_shared<NCA>(update_raw, base_nca);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
enabled_version.has_value() ? FormatTitleVersion(*enabled_version) :
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
const auto version =
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0));
}
} else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) {
const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca);
@@ -687,179 +490,34 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
PatchManager update{update_tid, fs_controller, content_provider};
const auto metadata = update.GetControlMetadata();
const auto& nacp = metadata.first;
std::vector<Patch> external_update_patches;
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
Patch update_patch = {.enabled = !update_disabled,
.name = "Update",
.version = "",
.type = PatchType::Update,
.program_id = title_id,
.title_id = title_id};
const auto* content_union = dynamic_cast<const ContentProviderUnion*>(&content_provider);
if (content_union) {
// First, check ExternalContentProvider for updates
const auto* external_provider = content_union->GetExternalProvider();
if (external_provider) {
const auto update_versions = external_provider->ListUpdateVersions(update_tid);
for (const auto& update_entry : update_versions) {
std::string version_str = update_entry.version_string;
if (version_str.empty()) {
version_str = FormatTitleVersion(update_entry.version);
}
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
Patch update_patch = {.enabled = !update_disabled,
.name = "Update",
.version = version_str,
.type = PatchType::Update,
.program_id = title_id,
.title_id = update_tid,
.source = PatchSource::External,
.numeric_version = update_entry.version};
external_update_patches.push_back(update_patch);
}
}
const auto* manual_provider = dynamic_cast<const ManualContentProvider*>(
content_union->GetSlotProvider(ContentProviderUnionSlot::FrontendManual));
if (manual_provider && external_update_patches.empty()) {
const auto manual_update_versions = manual_provider->ListUpdateVersions(update_tid);
for (const auto& update_entry : manual_update_versions) {
std::string version_str = update_entry.version_string;
if (version_str.empty()) {
version_str = FormatTitleVersion(update_entry.version);
}
std::string disabled_key = fmt::format("Update@{}", update_entry.version);
const auto update_disabled =
std::find(disabled.cbegin(), disabled.cend(), disabled_key) != disabled.cend();
Patch update_patch = {.enabled = !update_disabled,
.name = "Update",
.version = version_str,
.type = PatchType::Update,
.program_id = title_id,
.title_id = update_tid,
.source = PatchSource::External,
.numeric_version = update_entry.version};
external_update_patches.push_back(update_patch);
}
}
if (external_update_patches.size() > 1) {
bool found_enabled = false;
for (auto& patch : external_update_patches) {
if (patch.enabled) {
if (found_enabled) {
patch.enabled = false;
} else {
found_enabled = true;
}
}
}
}
for (auto& patch : external_update_patches) {
out.push_back(std::move(patch));
}
const auto all_updates = content_union->ListEntriesFilterOrigin(
std::nullopt, std::nullopt, ContentRecordType::Program, update_tid);
for (const auto& [slot, entry] : all_updates) {
if (slot == ContentProviderUnionSlot::External) {
continue;
}
PatchSource source_type = PatchSource::Unknown;
std::string source_suffix;
switch (slot) {
case ContentProviderUnionSlot::UserNAND:
case ContentProviderUnionSlot::SysNAND:
source_type = PatchSource::NAND;
source_suffix = " (NAND)";
break;
case ContentProviderUnionSlot::SDMC:
source_type = PatchSource::NAND;
source_suffix = " (SDMC)";
break;
default:
break;
}
std::string version_str;
u32 numeric_ver = 0;
PatchManager update{update_tid, fs_controller, content_provider};
const auto metadata = update.GetControlMetadata();
const auto& nacp = metadata.first;
if (nacp != nullptr) {
version_str = nacp->GetVersionString();
}
const auto meta_ver = content_provider.GetEntryVersion(update_tid);
if (meta_ver.has_value()) {
numeric_ver = *meta_ver;
if (version_str.empty() && numeric_ver != 0) {
version_str = FormatTitleVersion(numeric_ver);
}
}
std::string patch_name = "Update" + source_suffix;
bool update_disabled =
std::find(disabled.cbegin(), disabled.cend(), patch_name) != disabled.cend();
Patch update_patch = {.enabled = !update_disabled,
.name = patch_name,
.version = version_str,
.type = PatchType::Update,
.program_id = title_id,
.title_id = update_tid,
.source = source_type,
.numeric_version = numeric_ver};
out.push_back(update_patch);
}
if (nacp != nullptr) {
update_patch.version = nacp->GetVersionString();
out.push_back(update_patch);
} else {
PatchManager update{update_tid, fs_controller, content_provider};
const auto metadata = update.GetControlMetadata();
const auto& nacp = metadata.first;
bool update_disabled =
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
Patch update_patch = {.enabled = !update_disabled,
.name = "Update",
.version = "",
.type = PatchType::Update,
.program_id = title_id,
.title_id = title_id,
.source = PatchSource::Unknown,
.numeric_version = 0};
if (nacp != nullptr) {
update_patch.version = nacp->GetVersionString();
out.push_back(update_patch);
} else {
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = content_provider.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) {
out.push_back(update_patch);
} else {
update_patch.version = FormatTitleVersion(*meta_ver);
update_patch.numeric_version = *meta_ver;
out.push_back(update_patch);
}
} else if (update_raw != nullptr) {
update_patch.version = "PACKED";
update_patch.source = PatchSource::Packed;
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = content_provider.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) {
out.push_back(update_patch);
} else {
update_patch.version = FormatTitleVersion(*meta_ver);
out.push_back(update_patch);
}
} else if (update_raw != nullptr) {
update_patch.version = "PACKED";
out.push_back(update_patch);
}
}
@@ -875,8 +533,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
.version = "Cheats",
.type = PatchType::Mod,
.program_id = title_id,
.title_id = title_id,
.source = PatchSource::Unknown
.title_id = title_id
});
}
@@ -897,7 +554,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
} else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(),
file->GetName()) != EXEFS_FILE_NAMES.end()) {
layeredfs = true;
}
}
}
if (ips)
@@ -922,8 +579,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
.version = types,
.type = PatchType::Mod,
.program_id = title_id,
.title_id = title_id,
.source = PatchSource::Unknown});
.title_id = title_id});
}
}
@@ -937,7 +593,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs")) ||
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) {
AppendCommaIfNotEmpty(types, "LayeredFS");
}
}
if (!types.empty()) {
const auto mod_disabled =
@@ -947,44 +603,21 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
.version = types,
.type = PatchType::Mod,
.program_id = title_id,
.title_id = title_id,
.source = PatchSource::Unknown});
.title_id = title_id});
}
}
// DLC
const auto dlc_entries =
content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
std::vector<ContentProviderEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
[this](const ContentProviderEntry& entry) {
const auto base_tid = GetBaseTitleID(entry.title_id);
const bool matches_base = base_tid == title_id;
if (!matches_base) {
LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}",
entry.title_id, base_tid, title_id);
return false;
}
auto nca = content_provider.GetEntry(entry);
if (!nca) {
LOG_DEBUG(Loader, "Failed to get NCA for DLC {:016X}", entry.title_id);
return false;
}
const auto status = nca->GetStatus();
if (status != Loader::ResultStatus::Success) {
LOG_DEBUG(Loader, "DLC {:016X} NCA has status {}",
entry.title_id, static_cast<int>(status));
return false;
}
return true;
return GetBaseTitleID(entry.title_id) == title_id &&
content_provider.GetEntry(entry)->GetStatus() ==
Loader::ResultStatus::Success;
});
if (!dlc_match.empty()) {
// Ensure sorted so DLC IDs show in order.
std::sort(dlc_match.begin(), dlc_match.end());
@@ -1002,8 +635,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
.version = std::move(list),
.type = PatchType::DLC,
.program_id = title_id,
.title_id = dlc_match.back().title_id,
.source = PatchSource::Unknown});
.title_id = dlc_match.back().title_id});
}
return out;
-12
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -31,13 +28,6 @@ class NACP;
enum class PatchType { Update, DLC, Mod };
enum class PatchSource {
Unknown,
NAND,
External,
Packed,
};
struct Patch {
bool enabled;
std::string name;
@@ -45,8 +35,6 @@ struct Patch {
PatchType type;
u64 program_id;
u64 title_id;
PatchSource source;
u32 numeric_version{0};
};
// A centralized class to manage patches to games.
+1 -518
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -13,15 +13,12 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/crypto/key_manager.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs/vfs_concat.h"
#include "core/loader/loader.h"
@@ -977,22 +974,6 @@ std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry(
return iter->first;
}
const ExternalContentProvider* ContentProviderUnion::GetExternalProvider() const {
auto it = providers.find(ContentProviderUnionSlot::External);
if (it != providers.end() && it->second != nullptr) {
return dynamic_cast<const ExternalContentProvider*>(it->second);
}
return nullptr;
}
const ContentProvider* ContentProviderUnion::GetSlotProvider(ContentProviderUnionSlot slot) const {
auto it = providers.find(slot);
if (it != providers.end()) {
return it->second;
}
return nullptr;
}
ManualContentProvider::~ManualContentProvider() = default;
void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
@@ -1000,51 +981,8 @@ void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType con
entries.insert_or_assign({title_type, content_type, title_id}, file);
}
void ManualContentProvider::AddEntryWithVersion(TitleType title_type, ContentRecordType content_type,
u64 title_id, u32 version,
const std::string& version_string, VirtualFile file) {
if (title_type == TitleType::Update) {
auto it = std::find_if(multi_version_entries.begin(), multi_version_entries.end(),
[title_id, version](const ExternalUpdateEntry& entry) {
return entry.title_id == title_id && entry.version == version;
});
if (it != multi_version_entries.end()) {
// Update existing entry
it->files[content_type] = file;
if (!version_string.empty()) {
it->version_string = version_string;
}
} else {
// Add new entry
ExternalUpdateEntry new_entry;
new_entry.title_id = title_id;
new_entry.version = version;
new_entry.version_string = version_string;
new_entry.files[content_type] = file;
multi_version_entries.push_back(new_entry);
}
auto existing = entries.find({title_type, content_type, title_id});
if (existing == entries.end()) {
entries.insert_or_assign({title_type, content_type, title_id}, file);
} else {
// Check if this version is higher
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id && entry.version > version) {
return; // Don't replace with lower version
}
}
entries.insert_or_assign({title_type, content_type, title_id}, file);
}
} else {
entries.insert_or_assign({title_type, content_type, title_id}, file);
}
}
void ManualContentProvider::ClearAllEntries() {
entries.clear();
multi_version_entries.clear();
}
void ManualContentProvider::Refresh() {}
@@ -1098,459 +1036,4 @@ std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
std::vector<ExternalUpdateEntry> ManualContentProvider::ListUpdateVersions(u64 title_id) const {
std::vector<ExternalUpdateEntry> out;
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id) {
out.push_back(entry);
}
}
std::sort(out.begin(), out.end(), [](const ExternalUpdateEntry& a, const ExternalUpdateEntry& b) {
return a.version > b.version;
});
return out;
}
VirtualFile ManualContentProvider::GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const {
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id && entry.version == version) {
auto it = entry.files.find(type);
if (it != entry.files.end()) {
return it->second;
}
}
}
return nullptr;
}
bool ManualContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
int count = 0;
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id && entry.files.count(type) > 0) {
count++;
if (count > 1) {
return true;
}
}
}
return false;
}
ExternalContentProvider::ExternalContentProvider(std::vector<VirtualDir> load_directories)
: load_dirs(std::move(load_directories)) {
ExternalContentProvider::Refresh();
}
ExternalContentProvider::~ExternalContentProvider() = default;
void ExternalContentProvider::AddDirectory(VirtualDir directory) {
if (directory != nullptr) {
load_dirs.push_back(std::move(directory));
ScanDirectory(load_dirs.back());
}
}
void ExternalContentProvider::ClearDirectories() {
load_dirs.clear();
entries.clear();
versions.clear();
multi_version_entries.clear();
}
void ExternalContentProvider::Refresh() {
entries.clear();
versions.clear();
multi_version_entries.clear();
for (const auto& dir : load_dirs) {
if (dir != nullptr) {
ScanDirectory(dir);
}
}
}
void ExternalContentProvider::ScanDirectory(const VirtualDir& dir) {
if (dir == nullptr) {
return;
}
for (const auto& file : dir->GetFiles()) {
const auto filename = file->GetName();
const auto dot_pos = filename.find_last_of('.');
if (dot_pos == std::string::npos) {
continue;
}
const auto extension = Common::ToLower(filename.substr(dot_pos + 1));
if (extension == "nsp") {
ProcessNSP(file);
} else if (extension == "xci") {
ProcessXCI(file);
}
}
for (const auto& subdir : dir->GetSubdirectories()) {
ScanDirectory(subdir);
}
}
void ExternalContentProvider::ProcessNSP(const VirtualFile& file) {
auto nsp = NSP(file);
if (nsp.GetStatus() != Loader::ResultStatus::Success) {
return;
}
LOG_DEBUG(Service_FS, "Processing NSP file: {}", file->GetName());
const auto ncas = nsp.GetNCAs();
std::map<u64, u32> nsp_versions;
std::map<u64, std::string> nsp_version_strings; // title_id -> NACP version string
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
const auto section0 = subdirs[0];
const auto files = section0->GetFiles();
for (const auto& inner_file : files) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
const auto cnmt_title_id = cnmt.GetTitleID();
const auto version = cnmt.GetTitleVersion();
nsp_versions[cnmt_title_id] = version;
versions[cnmt_title_id] = version;
break;
}
}
}
}
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
nsp_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
std::map<std::pair<u64, u32>, std::map<ContentRecordType, VirtualFile>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
continue;
}
auto nca_file = nsp.GetNCAFile(title_id, content_type, title_type);
if (nca_file != nullptr) {
entries[{title_id, content_type, title_type}] = nca_file;
if (title_type == TitleType::Update) {
u32 version = 0;
auto ver_it = nsp_versions.find(title_id);
if (ver_it != nsp_versions.end()) {
version = ver_it->second;
}
version_files[{title_id, version}][content_type] = nca_file;
}
LOG_DEBUG(Service_FS, "Added entry - Title ID: {:016X}, Type: {}, Content: {}",
title_id, static_cast<int>(title_type), static_cast<int>(content_type));
}
}
}
for (const auto& [key, files_map] : version_files) {
const auto& [title_id, version] = key;
std::string ver_str;
auto str_it = nsp_version_strings.find(title_id);
if (str_it != nsp_version_strings.end()) {
ver_str = str_it->second;
}
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
for (const auto& [content_type, _file] : files_map) {
existing.files[content_type] = _file;
}
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
version_exists = true;
break;
}
}
if (!version_exists && !files_map.empty()) {
ExternalUpdateEntry update_entry{
.title_id = title_id,
.version = version,
.version_string = ver_str,
.files = files_map
};
multi_version_entries.push_back(update_entry);
LOG_DEBUG(Service_FS, "Added multi-version update - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
title_id, version, ver_str, files_map.size());
}
}
}
void ExternalContentProvider::ProcessXCI(const VirtualFile& file) {
auto xci = XCI(file);
if (xci.GetStatus() != Loader::ResultStatus::Success) {
return;
}
auto nsp = xci.GetSecurePartitionNSP();
if (nsp == nullptr) {
return;
}
const auto ncas = nsp->GetNCAs();
std::map<u64, u32> xci_versions;
std::map<u64, std::string> xci_version_strings;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (content_type == ContentRecordType::Meta) {
const auto subdirs = nca->GetSubdirectories();
if (!subdirs.empty()) {
const auto section0 = subdirs[0];
const auto files = section0->GetFiles();
for (const auto& inner_file : files) {
if (inner_file->GetExtension() == "cnmt") {
const CNMT cnmt(inner_file);
const auto cnmt_title_id = cnmt.GetTitleID();
const auto version = cnmt.GetTitleVersion();
xci_versions[cnmt_title_id] = version;
versions[cnmt_title_id] = version;
break;
}
}
}
}
if (content_type == ContentRecordType::Control && title_type == TitleType::Update) {
auto romfs = nca->GetRomFS();
if (romfs) {
auto extracted = ExtractRomFS(romfs);
if (extracted) {
auto nacp_file = extracted->GetFile("control.nacp");
if (!nacp_file) {
nacp_file = extracted->GetFile("Control.nacp");
}
if (nacp_file) {
NACP nacp(nacp_file);
auto ver_str = nacp.GetVersionString();
if (!ver_str.empty()) {
xci_version_strings[title_id] = ver_str;
}
}
}
}
}
}
}
std::map<std::pair<u64, u32>, std::map<ContentRecordType, VirtualFile>> version_files;
for (const auto& [title_id, nca_map] : ncas) {
for (const auto& [type_pair, nca] : nca_map) {
const auto& [title_type, content_type] = type_pair;
if (title_type != TitleType::AOC && title_type != TitleType::Update) {
continue;
}
auto nca_file = nsp->GetNCAFile(title_id, content_type, title_type);
if (nca_file != nullptr) {
entries[{title_id, content_type, title_type}] = nca_file;
if (title_type == TitleType::Update) {
u32 version = 0;
auto ver_it = xci_versions.find(title_id);
if (ver_it != xci_versions.end()) {
version = ver_it->second;
}
version_files[{title_id, version}][content_type] = nca_file;
}
}
}
}
for (const auto& [key, files_map] : version_files) {
const auto& [title_id, version] = key;
std::string ver_str;
auto str_it = xci_version_strings.find(title_id);
if (str_it != xci_version_strings.end()) {
ver_str = str_it->second;
}
bool version_exists = false;
for (auto& existing : multi_version_entries) {
if (existing.title_id == title_id && existing.version == version) {
for (const auto& [content_type, _file] : files_map) {
existing.files[content_type] = _file;
}
if (existing.version_string.empty() && !ver_str.empty()) {
existing.version_string = ver_str;
}
version_exists = true;
break;
}
}
if (!version_exists && !files_map.empty()) {
ExternalUpdateEntry update_entry{
.title_id = title_id,
.version = version,
.version_string = ver_str,
.files = files_map
};
multi_version_entries.push_back(update_entry);
LOG_DEBUG(Service_FS, "Added multi-version update from XCI - Title ID: {:016X}, Version: {}, VersionStr: {}, Content types: {}",
title_id, version, ver_str, files_map.size());
}
}
}
bool ExternalContentProvider::HasEntry(u64 title_id, ContentRecordType type) const {
return GetEntryRaw(title_id, type) != nullptr;
}
std::optional<u32> ExternalContentProvider::GetEntryVersion(u64 title_id) const {
const auto it = versions.find(title_id);
if (it != versions.end()) {
return it->second;
}
return std::nullopt;
}
VirtualFile ExternalContentProvider::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
return GetEntryRaw(title_id, type);
}
VirtualFile ExternalContentProvider::GetEntryRaw(u64 title_id, ContentRecordType type) const {
// Try to find in AOC (DLC) entries
{
const auto it = entries.find({title_id, type, TitleType::AOC});
if (it != entries.end()) {
return it->second;
}
}
// Try to find in Update entries
{
const auto it = entries.find({title_id, type, TitleType::Update});
if (it != entries.end()) {
return it->second;
}
}
return nullptr;
}
std::unique_ptr<NCA> ExternalContentProvider::GetEntry(u64 title_id,
ContentRecordType type) const {
const auto file = GetEntryRaw(title_id, type);
if (file == nullptr) {
return nullptr;
}
return std::make_unique<NCA>(file);
}
std::vector<ContentProviderEntry> ExternalContentProvider::ListEntriesFilter(
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const {
std::vector<ContentProviderEntry> out;
for (const auto& [key, file] : entries) {
const auto& [e_title_id, e_content_type, e_title_type] = key;
if ((title_type == std::nullopt || e_title_type == *title_type) &&
(record_type == std::nullopt || e_content_type == *record_type) &&
(title_id == std::nullopt || e_title_id == *title_id)) {
out.emplace_back(ContentProviderEntry{e_title_id, e_content_type});
}
}
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
std::vector<ExternalUpdateEntry> ExternalContentProvider::ListUpdateVersions(u64 title_id) const {
std::vector<ExternalUpdateEntry> out;
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id) {
out.push_back(entry);
}
}
std::sort(out.begin(), out.end(), [](const ExternalUpdateEntry& a, const ExternalUpdateEntry& b) {
return a.version > b.version;
});
return out;
}
VirtualFile ExternalContentProvider::GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const {
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id && entry.version == version) {
auto it = entry.files.find(type);
if (it != entry.files.end()) {
return it->second;
}
}
}
return nullptr;
}
bool ExternalContentProvider::HasMultipleVersions(u64 title_id, ContentRecordType type) const {
size_t count = 0;
for (const auto& entry : multi_version_entries) {
if (entry.title_id == title_id && entry.files.count(type) > 0) {
count++;
if (count > 1) {
return true;
}
}
}
return false;
}
} // namespace FileSys
+1 -56
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -17,8 +14,7 @@
#include "core/file_sys/vfs/vfs.h"
namespace FileSys {
class ExternalContentProvider;
class CNMT;
class CNMT;
class NCA;
class NSP;
class XCI;
@@ -52,13 +48,6 @@ struct ContentProviderEntry {
std::string DebugInfo() const;
};
struct ExternalUpdateEntry {
u64 title_id;
u32 version;
std::string version_string;
std::map<ContentRecordType, VirtualFile> files;
};
constexpr u64 GetUpdateTitleID(u64 base_title_id) {
return base_title_id | 0x800;
}
@@ -219,7 +208,6 @@ enum class ContentProviderUnionSlot {
UserNAND, ///< User NAND
SDMC, ///< SD Card
FrontendManual, ///< Frontend-defined game list or similar
External, ///< External content from NSP/XCI files in configured directories
};
// Combines multiple ContentProvider(s) (i.e. SysNAND, UserNAND, SDMC) into one interface.
@@ -240,9 +228,6 @@ public:
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const override;
const ExternalContentProvider* GetExternalProvider() const;
const ContentProvider* GetSlotProvider(ContentProviderUnionSlot slot) const;
std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ListEntriesFilterOrigin(
std::optional<ContentProviderUnionSlot> origin = {},
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
@@ -261,8 +246,6 @@ public:
void AddEntry(TitleType title_type, ContentRecordType content_type, u64 title_id,
VirtualFile file);
void AddEntryWithVersion(TitleType title_type, ContentRecordType content_type, u64 title_id,
u32 version, const std::string& version_string, VirtualFile file);
void ClearAllEntries();
void Refresh() override;
@@ -275,46 +258,8 @@ public:
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const override;
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private:
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
std::vector<ExternalUpdateEntry> multi_version_entries;
};
class ExternalContentProvider : public ContentProvider {
public:
explicit ExternalContentProvider(std::vector<VirtualDir> load_directories = {});
~ExternalContentProvider() override;
void AddDirectory(VirtualDir directory);
void ClearDirectories();
void Refresh() override;
bool HasEntry(u64 title_id, ContentRecordType type) const override;
std::optional<u32> GetEntryVersion(u64 title_id) const override;
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const override;
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const override;
std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const override;
std::vector<ContentProviderEntry> ListEntriesFilter(
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
std::optional<u64> title_id) const override;
std::vector<ExternalUpdateEntry> ListUpdateVersions(u64 title_id) const;
VirtualFile GetEntryForVersion(u64 title_id, ContentRecordType type, u32 version) const;
bool HasMultipleVersions(u64 title_id, ContentRecordType type) const;
private:
void ScanDirectory(const VirtualDir& dir);
void ProcessNSP(const VirtualFile& file);
void ProcessXCI(const VirtualFile& file);
std::vector<VirtualDir> load_dirs;
std::map<std::tuple<u64, ContentRecordType, TitleType>, VirtualFile> entries;
std::map<u64, u32> versions;
std::vector<ExternalUpdateEntry> multi_version_entries;
};
} // namespace FileSys
-11
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -278,14 +275,6 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] =
std::move(next_nca);
} else {
// fix for Bayonetta Origins in Bayonetta 3 and external content
// where multiple update NCAs exist for the same title and type.
auto& target_map = ncas[cnmt.GetTitleID()];
auto existing = target_map.find({cnmt.GetType(), rec.type});
if (existing != target_map.end() && rec.type == ContentRecordType::Program) {
continue;
}
ncas[cnmt.GetTitleID()][{cnmt.GetType(), rec.type}] = std::move(next_nca);
}
} else {
-12
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -86,14 +83,6 @@ struct CodeSet final {
const Segment& PatchSegment() const {
return patch_segment;
}
Segment& PostPatchSegment() {
return post_patch_segment;
}
const Segment& PostPatchSegment() const {
return post_patch_segment;
}
#endif
/// The overall data that backs this code set.
@@ -104,7 +93,6 @@ struct CodeSet final {
#ifdef HAS_NCE
Segment patch_segment;
Segment post_patch_segment;
#endif
/// The entry point address for this code set.
+1 -10
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -1258,7 +1258,6 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
#ifdef HAS_NCE
const auto& patch = code_set.PatchSegment();
const auto& post_patch = code_set.PostPatchSegment();
if (this->IsApplication() && Settings::IsNceEnabled() && patch.size != 0) {
auto& buffer = m_kernel.System().DeviceMemory().buffer;
const auto& code = code_set.CodeSegment();
@@ -1266,15 +1265,7 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,
Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
// Protect post-patch segment if it exists like abve
if (post_patch.size != 0) {
buffer.Protect(GetInteger(base_addr + post_patch.addr), post_patch.size,
Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
}
ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None);
if (post_patch.size != 0) {
ReprotectSegment(code_set.PostPatchSegment(), Svc::MemoryPermission::None);
}
}
#endif
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -36,7 +36,7 @@ IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_,
{100, nullptr, "SetApplicationCoreUsageMode"},
{300, D<&IAppletCommonFunctions::GetCurrentApplicationId>, "GetCurrentApplicationId"},
{310, nullptr, "IsSystemAppletHomeMenu"}, //19.0.0+
{320, D<&IAppletCommonFunctions::SetGpuTimeSliceBoost>, "SetGpuTimeSliceBoost"}, //19.0.0+
{320, nullptr, "SetGpuTimeSliceBoost"}, //19.0.0+
{321, nullptr, "SetGpuTimeSliceBoostDueToApplication"}, //19.0.0+
{350, D<&IAppletCommonFunctions::Unknown350>, "Unknown350"} //20.0.0+
};
@@ -82,11 +82,6 @@ Result IAppletCommonFunctions::GetCurrentApplicationId(Out<u64> out_application_
R_SUCCEED();
}
Result IAppletCommonFunctions::SetGpuTimeSliceBoost(s64 time_span) {
LOG_WARNING(Service_AM, "(STUBBED) called, time_span={}", time_span);
R_SUCCEED();
}
Result IAppletCommonFunctions::Unknown350(Out<u16> out_unknown) {
LOG_WARNING(Service_AM, "(STUBBED) called");
*out_unknown = 0;
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -24,7 +24,6 @@ private:
Result SetDisplayMagnification(f32 x, f32 y, f32 width, f32 height);
Result SetCpuBoostRequestPriority(s32 priority);
Result GetCurrentApplicationId(Out<u64> out_application_id);
Result SetGpuTimeSliceBoost(s64 time_span);
Result Unknown350(Out<u16> out_unknown);
const std::shared_ptr<Applet> applet;
@@ -179,14 +179,7 @@ Result ILibraryAppletSelfAccessor::GetMainAppletStorageId(Out<FileSys::StorageId
Result ILibraryAppletSelfAccessor::ExitProcessAndReturn() {
LOG_INFO(Service_AM, "called");
if (const auto caller_applet = m_applet->caller_applet.lock(); caller_applet) {
m_applet->process->Terminate();
} else {
system.GetUserChannel() = m_applet->user_channel_launch_parameter;
system.ExecuteProgram(0);
}
m_applet->process->Terminate();
R_SUCCEED();
}
+1 -37
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -9,7 +9,6 @@
#include "common/assert.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/file_sys/bis_factory.h"
@@ -508,10 +507,6 @@ FileSys::RegisteredCache* FileSystemController::GetSDMCContents() const {
return sdmc_factory->GetSDMCContents();
}
FileSys::ExternalContentProvider* FileSystemController::GetExternalContentProvider() const {
return external_provider.get();
}
FileSys::PlaceholderCache* FileSystemController::GetSystemNANDPlaceholder() const {
LOG_TRACE(Service_FS, "Opening System NAND Placeholder");
@@ -689,7 +684,6 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
if (overwrite) {
bis_factory = nullptr;
sdmc_factory = nullptr;
external_provider = nullptr;
}
using EdenPath = Common::FS::EdenPath;
@@ -722,36 +716,6 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
sdmc_factory->GetSDMCContents());
}
if (external_provider == nullptr) {
std::vector<FileSys::VirtualDir> external_dirs;
LOG_DEBUG(Service_FS, "Initializing ExternalContentProvider with {} configured directories",
Settings::values.external_content_dirs.size());
for (const auto& dir_path : Settings::values.external_content_dirs) {
if (!dir_path.empty()) {
LOG_DEBUG(Service_FS, "Attempting to open directory: {}", dir_path);
auto dir = vfs.OpenDirectory(dir_path, FileSys::OpenMode::Read);
if (dir != nullptr) {
external_dirs.push_back(std::move(dir));
LOG_DEBUG(Service_FS, "Successfully opened directory: {}", dir_path);
} else {
LOG_ERROR(Service_FS, "Failed to open directory: {}", dir_path);
}
}
}
LOG_DEBUG(Service_FS, "Creating ExternalContentProvider with {} opened directories",
external_dirs.size());
external_provider = std::make_unique<FileSys::ExternalContentProvider>(
std::move(external_dirs));
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::External,
external_provider.get());
LOG_DEBUG(Service_FS, "ExternalContentProvider registered to content provider union");
}
}
void FileSystemController::Reset() {
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -20,7 +17,6 @@ class System;
namespace FileSys {
class BISFactory;
class ExternalContentProvider;
class NCA;
class RegisteredCache;
class RegisteredCacheUnion;
@@ -121,8 +117,6 @@ public:
FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
FileSys::ExternalContentProvider* GetExternalContentProvider() const;
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
// above is called.
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
@@ -144,8 +138,6 @@ private:
std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
std::unique_ptr<FileSys::BISFactory> bis_factory;
std::unique_ptr<FileSys::ExternalContentProvider> external_provider;
std::unique_ptr<FileSys::XCI> gamecard;
std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;
std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder;
+4 -32
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -102,8 +102,6 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
auto* patch = &patches->operator[](patch_index);
if (patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
return patch->GetSectionSize();
} else if (patch->GetPatchMode() == Core::NCE::PatchMode::Split) {
return patch->GetPreSectionSize();
}
}
#endif
@@ -180,26 +178,12 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
}
} else if (patch) {
// Relocate code patch and copy to the program_image.
// Save size before RelocateAndCopy (which may resize)
const size_t size_before_relocate = program_image.size();
if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) {
// Update patch section.
auto& patch_segment = codeset.PatchSegment();
auto& post_patch_segment = codeset.PostPatchSegment();
const auto patch_mode = patch->GetPatchMode();
if (patch_mode == Core::NCE::PatchMode::PreText) {
patch_segment.addr = 0;
patch_segment.size = static_cast<u32>(patch->GetSectionSize());
} else if (patch_mode == Core::NCE::PatchMode::Split) {
// For Split-mode, we are using pre-patch buffer at start, post-patch buffer at end
patch_segment.addr = 0;
patch_segment.size = static_cast<u32>(patch->GetPreSectionSize());
post_patch_segment.addr = size_before_relocate;
post_patch_segment.size = static_cast<u32>(patch->GetSectionSize());
} else {
patch_segment.addr = image_size;
patch_segment.size = static_cast<u32>(patch->GetSectionSize());
}
patch_segment.addr =
patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size;
patch_segment.size = static_cast<u32>(patch->GetSectionSize());
}
// Refresh image_size to take account the patch section if it was added by RelocateAndCopy
@@ -209,18 +193,6 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
// If we aren't actually loading (i.e. just computing the process code layout), we are done
if (!load_into_process) {
#ifdef HAS_NCE
// Ok, so for Split mode, we need to account for pre-patch and post-patch space
// which will be added during RelocateAndCopy in the second pass. Where it crashed
// in Android Studio at PreText. May be a better way. Works for now.
if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::Split) {
return load_base + patch->GetPreSectionSize() + image_size + patch->GetSectionSize();
} else if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
return load_base + patch->GetSectionSize() + image_size;
} else if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PostData) {
return load_base + image_size + patch->GetSectionSize();
}
#endif
return load_base + image_size;
}
@@ -433,10 +433,9 @@ int RegAlloc::AllocateRegister(const std::array<HostLocInfo, 32>& regs, const st
std::vector<int> candidates;
std::copy_if(order.begin(), order.end(), std::back_inserter(candidates), [&](int i) { return regs[i].MaybeAllocatable(); });
// TODO: The candidate was chosen randomly before, and a LRU was
// suggested as an improvement. However, using an incrementing index
// seems to be close enough. Determine if an LRU is still needed.
return candidates[alloc_candidate_index++ % candidates.size()];
// TODO: LRU
std::uniform_int_distribution<size_t> dis{0, candidates.size() - 1};
return candidates[dis(rand_gen)];
}
void RegAlloc::SpillGpr(int index) {
@@ -160,12 +160,8 @@ class RegAlloc final {
public:
using ArgumentInfo = std::array<Argument, IR::max_arg_count>;
explicit RegAlloc(oaknut::CodeGenerator& code, FpsrManager& fpsr_manager, std::vector<int> gpr_order, std::vector<int> fpr_order) noexcept
: code{code}
, fpsr_manager{fpsr_manager}
, gpr_order{gpr_order}
, fpr_order{fpr_order}
{}
explicit RegAlloc(oaknut::CodeGenerator& code, FpsrManager& fpsr_manager, std::vector<int> gpr_order, std::vector<int> fpr_order)
: code{code}, fpsr_manager{fpsr_manager}, gpr_order{gpr_order}, fpr_order{fpr_order}, rand_gen{std::random_device{}()} {}
ArgumentInfo GetArgumentInfo(IR::Inst* inst);
bool WasValueDefined(IR::Inst* inst) const;
@@ -335,7 +331,7 @@ private:
HostLocInfo flags;
std::array<HostLocInfo, SpillCount> spills;
mutable size_t alloc_candidate_index = 0;
mutable std::mt19937 rand_gen;
ankerl::unordered_dense::set<const IR::Inst*> defined_insts;
};
+1 -2
View File
@@ -13,8 +13,7 @@ add_library(frontend_common STATIC
data_manager.h data_manager.cpp
play_time_manager.cpp
play_time_manager.h
settings_generator.h settings_generator.cpp
mod_manager.h mod_manager.cpp)
settings_generator.h settings_generator.cpp)
if (ENABLE_UPDATE_CHECKER)
target_link_libraries(frontend_common PRIVATE httplib::httplib)
-62
View File
@@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <filesystem>
#include <fmt/format.h>
#include "common/fs/fs.h"
#include "common/fs/fs_types.h"
#include "common/logging/backend.h"
#include "frontend_common/data_manager.h"
#include "mod_manager.h"
namespace FrontendCommon {
// TODO: Handle cases where the folder appears to contain multiple mods.
std::vector<std::filesystem::path> GetModFolder(const std::string& root) {
std::vector<std::filesystem::path> paths;
auto callback = [&paths](const std::filesystem::directory_entry& entry) -> bool {
const auto name = entry.path().filename().string();
static const std::array<std::string, 5> valid_names = {"exefs",
"romfs"
"romfs_ext",
"cheats", "romfslite"};
if (std::ranges::find(valid_names, name) != valid_names.end()) {
paths.emplace_back(entry.path().parent_path());
}
return true;
};
Common::FS::IterateDirEntriesRecursively(root, callback, Common::FS::DirEntryFilter::Directory);
return paths;
}
ModInstallResult InstallMod(const std::filesystem::path& path, const u64 program_id, const bool copy) {
const auto program_id_string = fmt::format("{:016X}", program_id);
const auto mod_name = path.filename();
const auto mod_dir =
DataManager::GetDataDir(DataManager::DataDir::Mods) / program_id_string / mod_name;
// pre-emptively remove any existing mod here
std::filesystem::remove_all(mod_dir);
// now copy
try {
std::filesystem::copy(path, mod_dir, std::filesystem::copy_options::recursive);
if (!copy)
std::filesystem::remove_all(path);
} catch (std::exception& e) {
LOG_ERROR(Frontend, "Mod install failed with message {}", e.what());
return Failed;
}
LOG_INFO(Frontend, "Copied mod from {} to {}", path.string(), mod_dir.string());
return Success;
}
} // namespace FrontendCommon
-21
View File
@@ -1,21 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <filesystem>
#include <optional>
#include "common/common_types.h"
namespace FrontendCommon {
enum ModInstallResult {
Cancelled,
Failed,
Success,
};
std::vector<std::filesystem::path> GetModFolder(const std::string& root);
ModInstallResult InstallMod(const std::filesystem::path &path, const u64 program_id, const bool copy = true);
}
+3 -3
View File
@@ -22,8 +22,6 @@ add_library(qt_common STATIC
util/rom.h util/rom.cpp
util/applet.h util/applet.cpp
util/compress.h util/compress.cpp
util/fs.h util/fs.cpp
util/mod.h util/mod.cpp
abstract/frontend.h abstract/frontend.cpp
abstract/qt_progress_dialog.h abstract/qt_progress_dialog.cpp
@@ -31,7 +29,9 @@ add_library(qt_common STATIC
qt_string_lookup.h
qt_compat.h
discord/discord.h)
discord/discord.h
util/fs.h util/fs.cpp
)
if (UNIX)
target_sources(qt_common PRIVATE gui_settings.cpp gui_settings.h)
+1 -26
View File
@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QLineEdit>
#include "frontend.h"
#include "qt_common/qt_common.h"
@@ -9,9 +8,6 @@
#include <QFileDialog>
#endif
#include <QAbstractButton>
#include <QInputDialog>
namespace QtCommon::Frontend {
StandardButton ShowMessage(
@@ -54,25 +50,4 @@ const QString GetExistingDirectory(const QString& caption, const QString& dir,
#endif
}
int Choice(const QString& title, const QString& caption, const QStringList& options) {
QMessageBox box(rootObject);
box.setText(caption);
box.setWindowTitle(title);
for (const QString &opt : options) {
box.addButton(opt, QMessageBox::AcceptRole);
}
box.addButton(QMessageBox::Cancel);
box.exec();
auto button = box.clickedButton();
return options.indexOf(button->text());
}
const QString GetTextInput(const QString& title, const QString& caption,
const QString& defaultText) {
return QInputDialog::getText(rootObject, title, caption, QLineEdit::Normal, defaultText);
}
} // namespace QtCommon::Frontend
+1 -7
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef FRONTEND_H
@@ -139,11 +139,5 @@ const QString GetExistingDirectory(const QString &caption = QString(),
const QString &dir = QString(),
Options options = Option::ShowDirsOnly);
int Choice(const QString& title = QString(), const QString& caption = QString(),
const QStringList& options = {});
const QString GetTextInput(const QString& title = QString(), const QString& caption = QString(),
const QString& defaultText = QString());
} // namespace QtCommon::Frontend
#endif // FRONTEND_H
+1 -18
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -231,16 +231,6 @@ void QtConfig::ReadPathValues() {
QString::fromStdString(ReadStringSetting(std::string("recentFiles")))
.split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive);
const int external_dirs_size = BeginArray(std::string("external_content_dirs"));
for (int i = 0; i < external_dirs_size; ++i) {
SetArrayIndex(i);
std::string dir_path = ReadStringSetting(std::string("path"));
if (!dir_path.empty()) {
Settings::values.external_content_dirs.push_back(dir_path);
}
}
EndArray();
ReadCategory(Settings::Category::Paths);
EndGroup();
@@ -456,13 +446,6 @@ void QtConfig::SavePathValues() {
WriteStringSetting(std::string("recentFiles"),
UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
BeginArray(std::string("external_content_dirs"));
for (int i = 0; i < static_cast<int>(Settings::values.external_content_dirs.size()); ++i) {
SetArrayIndex(i);
WriteStringSetting(std::string("path"), Settings::values.external_content_dirs[i]);
}
EndArray();
EndGroup();
}
@@ -766,12 +766,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
PAIR(ExtendedDynamicState, EDS3, tr("ExtendedDynamicState 3")),
}});
translations->insert({Settings::EnumMetadata<Settings::GameListMode>::Index(),
{
PAIR(GameListMode, TreeView, tr("Tree View")),
PAIR(GameListMode, GridView, tr("Grid View")),
}});
#undef PAIR
#undef CTX_PAIR
+1 -4
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
@@ -210,9 +210,6 @@ struct Values {
Setting<u32> folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList};
Setting<u8> row_1_text_id{linkage, 3, "row_1_text_id", Category::UiGameList};
Setting<u8> row_2_text_id{linkage, 2, "row_2_text_id", Category::UiGameList};
Setting<Settings::GameListMode> game_list_mode{linkage, Settings::GameListMode::TreeView, "game_list_mode", Category::UiGameList};
Setting<bool> show_game_name{linkage, true, "show_game_name", Category::UiGameList};
std::atomic_bool is_game_list_reload_pending{false};
Setting<bool> cache_game_list{linkage, true, "cache_game_list", Category::UiGameList};
Setting<bool> favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList};
+3 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -98,7 +98,8 @@ static const constexpr frozen::map<StringKey, frozen::string, 29> strings = {
{FwCheckErrorFirmwareMissing,
QT_TR_NOOP(
"Firmware missing. Firmware is required to run certain games and use the Home Menu.")},
"Firmware missing. Firmware is required to run certain games and use the Home Menu. "
"Versions 19.0.1 or earlier are recommended, as 20.0.0+ is currently experimental.")},
{FwCheckErrorFirmwareCorrupted,
QT_TR_NOOP(
"Firmware reported as present, but was unable to be read. Check for decryption keys and "
+10 -6
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "qt_common/util/game.h"
@@ -373,23 +373,27 @@ void RemoveCacheStorage(u64 program_id)
}
// Metadata //
void ResetMetadata(bool show_message) {
void ResetMetadata(bool show_message)
{
const QString title = tr("Reset Metadata Cache");
if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) /
"game_list/")) {
if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir)
/ "game_list/")) {
if (show_message)
QtCommon::Frontend::Warning(rootObject, title,
QtCommon::Frontend::Warning(rootObject,
title,
tr("The metadata cache is already empty."));
} else if (Common::FS::RemoveDirRecursively(
Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list")) {
if (show_message)
QtCommon::Frontend::Information(rootObject, title,
QtCommon::Frontend::Information(rootObject,
title,
tr("The operation completed successfully."));
UISettings::values.is_game_list_reload_pending.exchange(true);
} else {
if (show_message)
QtCommon::Frontend::Warning(
title,
tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
}
-139
View File
@@ -1,139 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <filesystem>
#include <JlCompress.h>
#include "frontend_common/mod_manager.h"
#include "mod.h"
#include "qt_common/abstract/frontend.h"
namespace QtCommon::Mod {
QStringList GetModFolders(const QString& root, const QString& fallbackName) {
namespace fs = std::filesystem;
const auto std_root = root.toStdString();
auto paths = FrontendCommon::GetModFolder(std_root);
// multi mod zip
if (paths.size() > 1) {
// We just have to assume it's properly formed here.
// If not, you're out of luck.
QStringList qpaths;
for (const fs::path& path : paths) {
qpaths << QString::fromStdString(path.string());
}
return qpaths;
}
// either frontend didn't detect any romfs/exefs, or is a single-mod zip
else {
fs::path std_path;
if (!paths.empty())
std_path = paths[0];
QString default_name;
if (!fallbackName.isEmpty())
default_name = fallbackName;
else if (!paths.empty())
default_name = QString::fromStdString(std_path.filename().string());
else
default_name = root.split(QLatin1Char('/')).last();
QString name = QtCommon::Frontend::GetTextInput(
tr("Mod Name"), tr("What should this mod be called?"), default_name);
if (name.isEmpty()) return {};
// if std_path is empty, frontend_common could not determine mod type and/or name.
// so we have to prompt the user and set up the structure ourselves
if (paths.empty()) {
// TODO: Carboxyl impl.
const QStringList choices = {
tr("RomFS"),
tr("ExeFS/Patch"),
tr("Cheat"),
};
int choice = QtCommon::Frontend::Choice(
tr("Mod Type"),
tr("Could not detect mod type automatically. Please manually "
"specify the type of mod you downloaded.\n\nMost mods are RomFS mods, but "
"patches "
"(.pchtxt) are typically ExeFS mods."),
choices);
std::string to_make;
switch (choice) {
case 0:
to_make = "romfs";
break;
case 1:
to_make = "exefs";
break;
case 2:
to_make = "cheats";
break;
default:
return {};
}
// now make a temp directory...
const auto mod_dir = fs::temp_directory_path() / "eden" / "mod" / name.toStdString();
const auto tmp = mod_dir / to_make;
fs::remove_all(mod_dir);
if (!fs::create_directories(tmp)) {
LOG_ERROR(Frontend, "Failed to create temporary directory {}", tmp.string());
return {};
}
std_path = mod_dir;
// ... and copy everything from the root to the temp dir
for (const auto& entry : fs::directory_iterator(root.toStdString())) {
const auto target = tmp / entry.path().filename();
fs::copy(entry.path(), target,
fs::copy_options::recursive | fs::copy_options::overwrite_existing);
}
} else {
// Rename the existing mod folder.
const auto new_path = std_path.parent_path() / name.toStdString();
fs::rename(std_path, new_path);
std_path = new_path;
}
return {QString::fromStdString(std_path.string())};
}
}
// TODO(crueter): Make this a common extract_to_tmp func
const QString ExtractMod(const QString& path) {
namespace fs = std::filesystem;
fs::path tmp{fs::temp_directory_path() / "eden" / "unzip_mod"};
fs::remove_all(tmp);
if (!fs::create_directories(tmp)) {
QtCommon::Frontend::Critical(tr("Mod Extract Failed"),
tr("Failed to create temporary directory %1")
.arg(QString::fromStdString(tmp.string())));
return QString();
}
QString qCacheDir = QString::fromStdString(tmp.string());
QFile zip{path};
// TODO(crueter): use QtCompress
QStringList result = JlCompress::extractDir(&zip, qCacheDir);
if (result.isEmpty()) {
QtCommon::Frontend::Critical(tr("Mod Extract Failed"),
tr("Zip file %1 is empty").arg(path));
return QString();
}
return qCacheDir;
}
} // namespace QtCommon::Mod
-16
View File
@@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QString>
#include "common/common_types.h"
#include "frontend_common/mod_manager.h"
namespace QtCommon::Mod {
QStringList GetModFolders(const QString &root, const QString &fallbackName);
const QString ExtractMod(const QString &path);
}
+64 -64
View File
@@ -14,68 +14,68 @@ set(GLSL_INCLUDES
)
set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_d24s8_to_abgr8.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_depth_to_float.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_float_to_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.vert
${CMAKE_CURRENT_SOURCE_DIR}/opengl_convert_s8d24.comp
${CMAKE_CURRENT_SOURCE_DIR}/opengl_copy_bc4.comp
${CMAKE_CURRENT_SOURCE_DIR}/opengl_fidelityfx_fsr.frag
${CMAKE_CURRENT_SOURCE_DIR}/opengl_fidelityfx_fsr_easu.frag
${CMAKE_CURRENT_SOURCE_DIR}/opengl_fidelityfx_fsr_rcas.frag
${CMAKE_CURRENT_SOURCE_DIR}/opengl_lmem_warmup.comp
${CMAKE_CURRENT_SOURCE_DIR}/opengl_present.frag
${CMAKE_CURRENT_SOURCE_DIR}/opengl_present.vert
${CMAKE_CURRENT_SOURCE_DIR}/opengl_present_scaleforce.frag
${CMAKE_CURRENT_SOURCE_DIR}/opengl_smaa.glsl
${CMAKE_CURRENT_SOURCE_DIR}/pitch_unswizzle.comp
${CMAKE_CURRENT_SOURCE_DIR}/present_area.frag
${CMAKE_CURRENT_SOURCE_DIR}/present_bicubic.frag
${CMAKE_CURRENT_SOURCE_DIR}/present_zero_tangent.frag
${CMAKE_CURRENT_SOURCE_DIR}/present_bspline.frag
${CMAKE_CURRENT_SOURCE_DIR}/present_mitchell.frag
${CMAKE_CURRENT_SOURCE_DIR}/present_gaussian.frag
${CMAKE_CURRENT_SOURCE_DIR}/present_lanczos.frag
${CMAKE_CURRENT_SOURCE_DIR}/present_spline1.frag
${CMAKE_CURRENT_SOURCE_DIR}/present_mmpx.frag
${CMAKE_CURRENT_SOURCE_DIR}/queries_prefix_scan_sum.comp
${CMAKE_CURRENT_SOURCE_DIR}/queries_prefix_scan_sum_nosubgroups.comp
${CMAKE_CURRENT_SOURCE_DIR}/resolve_conditional_render.comp
${CMAKE_CURRENT_SOURCE_DIR}/smaa_edge_detection.vert
${CMAKE_CURRENT_SOURCE_DIR}/smaa_edge_detection.frag
${CMAKE_CURRENT_SOURCE_DIR}/smaa_blending_weight_calculation.vert
${CMAKE_CURRENT_SOURCE_DIR}/smaa_blending_weight_calculation.frag
${CMAKE_CURRENT_SOURCE_DIR}/smaa_neighborhood_blending.vert
${CMAKE_CURRENT_SOURCE_DIR}/smaa_neighborhood_blending.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_blit_depth_stencil.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_color_clear.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_color_clear.vert
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_depthstencil_clear.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr.vert
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_easu_fp16.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_easu_fp32.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_rcas_fp16.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_rcas_fp32.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present.vert
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp16.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp32.frag
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_quad_indexed.comp
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_turbo_mode.comp
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_uint8.comp
astc_decoder.comp
blit_color_float.frag
block_linear_unswizzle_2d.comp
block_linear_unswizzle_3d.comp
block_linear_unswizzle_3d_bcn.comp
convert_abgr8_to_d24s8.frag
convert_abgr8_to_d32f.frag
convert_d32f_to_abgr8.frag
convert_d24s8_to_abgr8.frag
convert_depth_to_float.frag
convert_float_to_depth.frag
convert_msaa_to_non_msaa.comp
convert_non_msaa_to_msaa.comp
convert_s8d24_to_abgr8.frag
full_screen_triangle.vert
fxaa.frag
fxaa.vert
opengl_convert_s8d24.comp
opengl_copy_bc4.comp
opengl_fidelityfx_fsr.frag
opengl_fidelityfx_fsr_easu.frag
opengl_fidelityfx_fsr_rcas.frag
opengl_lmem_warmup.comp
opengl_present.frag
opengl_present.vert
opengl_present_scaleforce.frag
opengl_smaa.glsl
pitch_unswizzle.comp
present_area.frag
present_bicubic.frag
present_zero_tangent.frag
present_bspline.frag
present_mitchell.frag
present_gaussian.frag
present_lanczos.frag
present_spline1.frag
present_mmpx.frag
queries_prefix_scan_sum.comp
queries_prefix_scan_sum_nosubgroups.comp
resolve_conditional_render.comp
smaa_edge_detection.vert
smaa_edge_detection.frag
smaa_blending_weight_calculation.vert
smaa_blending_weight_calculation.frag
smaa_neighborhood_blending.vert
smaa_neighborhood_blending.frag
vulkan_blit_depth_stencil.frag
vulkan_color_clear.frag
vulkan_color_clear.vert
vulkan_depthstencil_clear.frag
vulkan_fidelityfx_fsr.vert
vulkan_fidelityfx_fsr_easu_fp16.frag
vulkan_fidelityfx_fsr_easu_fp32.frag
vulkan_fidelityfx_fsr_rcas_fp16.frag
vulkan_fidelityfx_fsr_rcas_fp32.frag
vulkan_present.frag
vulkan_present.vert
vulkan_present_scaleforce_fp16.frag
vulkan_present_scaleforce_fp32.frag
vulkan_quad_indexed.comp
vulkan_turbo_mode.comp
vulkan_uint8.comp
)
if (PLATFORM_HAIKU)
@@ -118,9 +118,9 @@ if (NOT GLSLANG_ERROR STREQUAL "")
set(QUIET_FLAG "")
endif()
foreach(SOURCE_FILE IN ITEMS ${SHADER_FILES})
get_filename_component(FILENAME ${SOURCE_FILE} NAME)
foreach(FILENAME IN ITEMS ${SHADER_FILES})
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
# Skip generating source headers on Vulkan exclusive files
if (NOT ${FILENAME} MATCHES "vulkan.*")
set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
+1 -17
View File
@@ -436,23 +436,7 @@ void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayo
.layerCount = 1,
},
};
// Blit image barrier - transition can happen at any stage
// Using specific stages for better parallelism, but keeping broad since this is a general utility
const VkPipelineStageFlags src_stages_blit =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
const VkPipelineStageFlags dst_stages_blit =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_blit, dst_stages_blit,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, barrier);
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -68,16 +68,7 @@ void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayo
.layerCount = 1,
},
};
// Present path - use specific stages for better parallelism on tile-based GPUs
const VkPipelineStageFlags src_stages =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
const VkPipelineStageFlags dst_stages =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages, dst_stages,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, barrier);
}
@@ -160,12 +151,11 @@ void DownloadColorImage(vk::CommandBuffer& cmdbuf, VkImage image, VkBuffer buffe
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
// Host will read the downloaded data
static constexpr VkMemoryBarrier memory_write_barrier{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
const VkBufferImageCopy copy{
.bufferOffset = 0,
@@ -180,20 +170,10 @@ void DownloadColorImage(vk::CommandBuffer& cmdbuf, VkImage image, VkBuffer buffe
.imageOffset{.x = 0, .y = 0, .z = 0},
.imageExtent{extent},
};
const VkPipelineStageFlags src_stages_copy =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_copy, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
read_barrier);
cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, copy);
const VkPipelineStageFlags dst_stages_copy =
VK_PIPELINE_STAGE_HOST_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_copy, 0,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0,
memory_write_barrier, nullptr, image_write_barrier);
}
@@ -436,27 +436,13 @@ void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) {
if (barrier) {
// Buffer writes can come from vertex input, shaders, or compute
const VkPipelineStageFlags src_stages =
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
}
cmdbuf.CopyBuffer(src_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies));
if (barrier) {
// Buffer reads can go to vertex input, shaders, or compute
const VkPipelineStageFlags dst_stages =
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
dst_stages, 0, WRITE_BARRIER);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
}
});
}
@@ -470,36 +456,21 @@ void BufferCacheRuntime::PreCopyBarrier() {
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([](vk::CommandBuffer cmdbuf) {
const VkPipelineStageFlags src_stages =
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages, VK_PIPELINE_STAGE_TRANSFER_BIT,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, READ_BARRIER);
});
}
void BufferCacheRuntime::PostCopyBarrier() {
// Specific access flags for buffer destinations: vertex input, uniforms, storage, index
static constexpr VkMemoryBarrier WRITE_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT |
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([](vk::CommandBuffer cmdbuf) {
const VkPipelineStageFlags dst_stages =
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, WRITE_BARRIER);
});
}
@@ -523,24 +494,10 @@ void BufferCacheRuntime::ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t si
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([dest_buffer, offset, size, value](vk::CommandBuffer cmdbuf) {
// Buffer writes can come from vertex input, shaders, or compute
const VkPipelineStageFlags src_stages_clear =
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_clear, VK_PIPELINE_STAGE_TRANSFER_BIT,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, READ_BARRIER);
cmdbuf.FillBuffer(dest_buffer, offset, size, value);
// Buffer reads can go to vertex input, shaders, or compute
const VkPipelineStageFlags dst_stages_clear =
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_clear,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, WRITE_BARRIER);
});
}
@@ -446,26 +446,13 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
// Memory writes can come from graphics or compute stages
const VkPipelineStageFlags src_stages_compute =
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_compute,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.Dispatch(1, 1, 1);
// Conditional rendering result used by draw indirect or conditional rendering
const VkPipelineStageFlags dst_stages_cond =
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT |
VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
dst_stages_cond, 0, write_barrier);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier);
});
}
@@ -533,29 +520,14 @@ void QueriesPrefixScanPass::Run(VkBuffer accumulation_buffer, VkBuffer dst_buffe
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
// Memory writes can come from graphics or compute stages
const VkPipelineStageFlags src_stages_scan =
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_scan,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
cmdbuf.Dispatch(1, 1, 1);
// Query prefix scan results used by draw indirect, conditional rendering, or shaders
const VkPipelineStageFlags dst_stages_scan =
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT |
VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT |
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
dst_stages_scan, 0, write_barrier);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, write_barrier);
});
}
}
@@ -607,15 +579,8 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
// If initialized, memory writes can come from graphics or compute stages
const VkPipelineStageFlags src_stages_astc = is_initialized ?
(VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT) :
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
cmdbuf.PipelineBarrier(src_stages_astc,
cmdbuf.PipelineBarrier(is_initialized ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline);
});
@@ -658,7 +623,7 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -673,7 +638,7 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, image_barrier);
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, image_barrier);
});
scheduler.Finish();
}
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -441,13 +441,7 @@ void PresentManager::CopyToSwapchainImpl(Frame* frame) {
},
};
// Previous operations that might have written to the frame image
const VkPipelineStageFlags src_stages_present =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_present, VK_PIPELINE_STAGE_TRANSFER_BIT, {},
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {},
{}, {}, pre_barriers);
if (blit_supported) {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -809,19 +809,12 @@ public:
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_HOST_READ_BIT | VK_ACCESS_SHADER_READ_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([](vk::CommandBuffer cmdbuf) {
// After transfer, results may be read by host or used in subsequent operations
const VkPipelineStageFlags dst_stages_query =
VK_PIPELINE_STAGE_HOST_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
dst_stages_query, 0, WRITE_BARRIER);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
});
std::scoped_lock lk(flush_guard);
@@ -949,7 +942,7 @@ private:
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([dst_buffer = current_bank->GetBuffer(),
@@ -1482,40 +1475,22 @@ void QueryCacheRuntime::Barriers(bool is_prebarrier) {
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
};
// Query results may be read by host or used in shaders/indirect commands
static constexpr VkMemoryBarrier WRITE_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_HOST_READ_BIT | VK_ACCESS_SHADER_READ_BIT |
VK_ACCESS_INDIRECT_COMMAND_READ_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
impl->scheduler.RequestOutsideRenderPassOperationContext();
if (is_prebarrier) {
impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
// Query data can be written by graphics or compute stages
const VkPipelineStageFlags src_stages_query =
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_query,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
});
} else {
impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
// Query results may be read by host or used in shaders/indirect commands
const VkPipelineStageFlags dst_stages_query =
VK_PIPELINE_STAGE_HOST_BIT |
VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT |
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
dst_stages_query, 0, WRITE_BARRIER);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
});
}
}
@@ -586,23 +586,14 @@ void RasterizerVulkan::DispatchCompute() {
}
const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
scheduler.RequestOutsideRenderPassOperationContext();
// Compute shader reads from storage buffers, uniforms, and images
static constexpr VkMemoryBarrier READ_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT,
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
};
scheduler.Record([](vk::CommandBuffer cmdbuf) {
// Memory writes can come from graphics or compute stages
const VkPipelineStageFlags src_stages =
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
cmdbuf.PipelineBarrier(src_stages, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, READ_BARRIER);
});
scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
0, READ_BARRIER); });
scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
// Log compute dispatch
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
@@ -44,19 +44,13 @@ using VideoCore::Surface::SurfaceType;
}
VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat format,
VkSampleCountFlagBits samples,
bool is_depth_stencil) {
VkSampleCountFlagBits samples) {
using MaxwellToVK::SurfaceFormat;
const SurfaceType surface_type = GetSurfaceType(format);
const bool has_stencil = surface_type == SurfaceType::DepthStencil ||
surface_type == SurfaceType::Stencil;
// Use optimal layouts for attachments - this allows drivers to optimize tiling and access patterns
const VkImageLayout attachment_layout = is_depth_stencil
? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
return {
.flags = {},
.format = SurfaceFormat(device, FormatType::Optimal, true, format).format,
@@ -67,8 +61,8 @@ using VideoCore::Surface::SurfaceType;
: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = has_stencil ? VK_ATTACHMENT_STORE_OP_STORE
: VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = attachment_layout,
.finalLayout = attachment_layout,
.initialLayout = VK_IMAGE_LAYOUT_GENERAL,
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
};
}
} // Anonymous namespace
@@ -90,10 +84,10 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
const bool is_valid{format != PixelFormat::Invalid};
references[index] = VkAttachmentReference{
.attachment = is_valid ? num_colors : VK_ATTACHMENT_UNUSED,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.layout = VK_IMAGE_LAYOUT_GENERAL,
};
if (is_valid) {
descriptions.push_back(AttachmentDescription(*device, format, key.samples, false));
descriptions.push_back(AttachmentDescription(*device, format, key.samples));
num_attachments = static_cast<u32>(index + 1);
++num_colors;
}
@@ -103,9 +97,9 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
if (key.depth_format != PixelFormat::Invalid) {
depth_reference = VkAttachmentReference{
.attachment = num_colors,
.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
.layout = VK_IMAGE_LAYOUT_GENERAL,
};
descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples, true));
descriptions.push_back(AttachmentDescription(*device, key.depth_format, key.samples));
}
const VkSubpassDescription subpass{
.flags = 0,
+22 -72
View File
@@ -264,26 +264,14 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
const u64 signal_value = master_semaphore->NextTick();
RecordWithUploadBuffer([signal_semaphore, wait_semaphore, signal_value,
this](vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) {
// Specific access flags for upload destinations: vertex input, uniforms, storage, index, textures
static constexpr VkMemoryBarrier WRITE_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT |
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
// Specify exact stages that need the transfer results instead of ALL_COMMANDS_BIT
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
0, WRITE_BARRIER);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
upload_cmdbuf.End();
cmdbuf.End();
@@ -344,11 +332,11 @@ void Scheduler::EndRenderPass()
query_cache->CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, false);
query_cache->NotifySegment(false);
Record([num_images = num_renderpass_images,
images = renderpass_images,
ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
std::array<VkImageMemoryBarrier, 9> barriers;
VkPipelineStageFlags src_stages = 0;
for (size_t i = 0; i < num_images; ++i) {
const VkImageSubresourceRange& range = ranges[i];
const bool is_color = (range.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
@@ -356,76 +344,38 @@ void Scheduler::EndRenderPass()
& (VK_IMAGE_ASPECT_DEPTH_BIT
| VK_IMAGE_ASPECT_STENCIL_BIT)) !=0;
// Determine optimal destination layout based on image usage
// After render pass, images may be used as shader resources or as attachments again
// Use optimal layouts to allow driver optimizations
VkAccessFlags src_access = 0;
VkAccessFlags dst_access = 0;
VkPipelineStageFlags this_stage = 0;
VkImageLayout new_layout;
if (is_color) {
// Color attachments can be read as textures or used as attachments again
src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
this_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
dst_access = VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
} else if (is_depth_stencil) {
// Depth attachments can be read as textures or used as attachments again
src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
this_stage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
| VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
dst_access = VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
} else {
// Fallback to GENERAL for unknown usage
src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
this_stage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
| VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
new_layout = VK_IMAGE_LAYOUT_GENERAL;
dst_access = VK_ACCESS_SHADER_READ_BIT
| VK_ACCESS_SHADER_WRITE_BIT
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
}
src_stages |= this_stage;
if (is_color)
src_access |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
else if (is_depth_stencil)
src_access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
else
src_access |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
barriers[i] = VkImageMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = src_access,
.dstAccessMask = dst_access,
.oldLayout = is_color ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
: (is_depth_stencil ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
: VK_IMAGE_LAYOUT_GENERAL),
.newLayout = new_layout,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = images[i],
.subresourceRange = range,
};
}
cmdbuf.EndRenderPass();
// Use specific pipeline stages instead of ALL_COMMANDS_BIT for better parallelism
// The destination stages depend on how the images will be used next
const VkPipelineStageFlags dst_stages =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
cmdbuf.PipelineBarrier(src_stages,
dst_stages,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0,
nullptr,
nullptr,
@@ -810,8 +810,7 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -823,8 +822,7 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -833,26 +831,11 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
.subresourceRange = subresource_range,
},
};
// Use specific pipeline stages for better parallelism
const VkPipelineStageFlags src_stages =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages, VK_PIPELINE_STAGE_TRANSFER_BIT,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, nullptr, nullptr, read_barriers);
cmdbuf.BlitImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regions, vk_filter);
// After transfer, images may be used in graphics, compute, or as attachments
const VkPipelineStageFlags dst_stages =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, nullptr, nullptr, write_barriers);
});
}
@@ -1038,12 +1021,11 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT,
};
// After reinterpret, buffers used as transfer source, uniforms, or storage
static constexpr VkMemoryBarrier WRITE_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
const std::array pre_barriers{
VkImageMemoryBarrier{
@@ -1110,39 +1092,18 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
.subresourceRange = dst_range.SubresourceRange(dst_aspect_mask),
},
};
const VkPipelineStageFlags src_stages_transfer =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_transfer, VK_PIPELINE_STAGE_TRANSFER_BIT,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, {}, {}, pre_barriers);
cmdbuf.CopyImageToBuffer(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, copy_buffer,
vk_in_copies);
const VkPipelineStageFlags dst_stages_transfer =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_transfer,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, WRITE_BARRIER, nullptr, middle_in_barrier);
cmdbuf.PipelineBarrier(src_stages_transfer, VK_PIPELINE_STAGE_TRANSFER_BIT,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, READ_BARRIER, {}, middle_out_barrier);
cmdbuf.CopyBufferToImage(copy_buffer, dst_image, VK_IMAGE_LAYOUT_GENERAL, vk_out_copies);
// After reinterpret copy, image may be used in shaders or as attachment
const VkPipelineStageFlags dst_stages_post =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_post,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, {}, {}, post_barriers);
});
}
@@ -1269,12 +1230,7 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
const VkPipelineStageFlags src_stages_resolve =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_resolve, VK_PIPELINE_STAGE_TRANSFER_BIT,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, nullptr, nullptr, read_barriers);
if (is_resolve) {
cmdbuf.ResolveImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image,
@@ -1287,14 +1243,7 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
MakeImageBlit(dst_region, src_region, dst_layers, src_layers), vk_filter);
}
// After blit/resolve, image may be used in shaders or as attachment
const VkPipelineStageFlags dst_stages_blit =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_blit,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, write_barrier);
});
}
@@ -1872,14 +1821,7 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
const VkPipelineStageFlags src_stages_download =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_download, VK_PIPELINE_STAGE_TRANSFER_BIT,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, read_barrier);
for (size_t index = 0; index < buffers.size(); index++) {
@@ -1887,20 +1829,17 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
vk_copies[index]);
}
// Host will read the downloaded data
const VkMemoryBarrier memory_write_barrier{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
};
// Image will be used for shaders or as attachment
const VkImageMemoryBarrier image_write_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -1914,13 +1853,7 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
const VkPipelineStageFlags dst_stages_download =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_download,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, memory_write_barrier, nullptr, image_write_barrier);
});
return;
@@ -1955,15 +1888,7 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
// Image writes can come from graphics or compute stages
const VkPipelineStageFlags src_stages_img =
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(src_stages_img, VK_PIPELINE_STAGE_TRANSFER_BIT,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, read_barrier);
for (size_t index = 0; index < buffers.size(); index++) {
@@ -1995,14 +1920,7 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
// After download, image may be used in shaders or as attachment
const VkPipelineStageFlags dst_stages_img =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_img,
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, memory_write_barrier, nullptr, image_write_barrier);
});
}
@@ -2556,9 +2474,7 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_NONE,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
@@ -2574,16 +2490,8 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) {
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([barrier](vk::CommandBuffer cmdbuf) {
// After layout transition, image may be used in shaders or as attachment
const VkPipelineStageFlags dst_stages_layout =
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT |
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_TRANSFER_BIT;
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
dst_stages_layout, 0, barrier);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
});
}
}

Some files were not shown because too many files have changed in this diff Show More