mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-30 12:16:18 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 213a9a7813 |
+4
-5
@@ -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_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")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R6/lib")
|
||||||
elseif (PLATFORM_NETBSD)
|
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_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_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")
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R7/lib")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R7/lib")
|
||||||
endif()
|
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)
|
option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF)
|
||||||
|
|
||||||
set(DEFAULT_ENABLE_OPENSSL ON)
|
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.
|
# - Windows defaults to the Schannel backend.
|
||||||
# - macOS defaults to the SecureTransport backend.
|
# - macOS defaults to the SecureTransport backend.
|
||||||
# - Android currently has no SSL backend as the NDK doesn't include any SSL
|
# - 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.
|
# 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
|
# But you can force builds for those platforms to use OpenSSL if you have
|
||||||
# your own copy of it.
|
# your own copy of it.
|
||||||
set(DEFAULT_ENABLE_OPENSSL OFF)
|
set(DEFAULT_ENABLE_OPENSSL OFF)
|
||||||
@@ -264,7 +263,7 @@ endif()
|
|||||||
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
|
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
|
||||||
|
|
||||||
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL OFF)
|
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)
|
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -85,8 +85,6 @@ If you have `quazip1_qt6_devel`, uninstall it. It may call `Core5Compat` on CMak
|
|||||||
|
|
||||||
## OpenBSD
|
## 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`.
|
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.
|
`-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
@@ -277,7 +277,7 @@ For NetBSD +10.1: `pkgin install git cmake boost fmtlib SDL2 catch2 libjwt spirv
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkg_add -u
|
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).
|
[Caveats](./Caveats.md#openbsd).
|
||||||
|
|||||||
+5
-5
@@ -82,15 +82,15 @@ You may additionally need the `Qt Extension Pack` extension if building Qt.
|
|||||||
|
|
||||||
# Build speedup
|
# 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
|
```sh
|
||||||
mkdir /tmp/ramdisk
|
sudo mkdir /tmp/ramdisk
|
||||||
chmod 777 /tmp/ramdisk
|
sudo chmod 777 /tmp/ramdisk
|
||||||
# about 8GB needed
|
# 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 -B /tmp/ramdisk
|
||||||
cmake --build /tmp/ramdisk -- -j32
|
cmake --build /tmp/ramdisk -- -j32
|
||||||
umount /tmp/ramdisk
|
sudo umount /tmp/ramdisk
|
||||||
```
|
```
|
||||||
|
|
||||||
# Assets and large files
|
# Assets and large files
|
||||||
|
|||||||
@@ -614,23 +614,4 @@ object NativeLibrary {
|
|||||||
* Updates the device power state to global variables
|
* Updates the device power state to global variables
|
||||||
*/
|
*/
|
||||||
external fun updatePowerState(percentage: Int, isCharging: Boolean, hasBattery: Boolean)
|
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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
@@ -10,7 +10,6 @@ import android.view.LayoutInflater
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
|
import org.yuzu.yuzu_emu.databinding.ListItemAddonBinding
|
||||||
import org.yuzu.yuzu_emu.model.Patch
|
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.model.AddonViewModel
|
||||||
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
import org.yuzu.yuzu_emu.viewholder.AbstractViewHolder
|
||||||
|
|
||||||
@@ -32,12 +31,7 @@ class AddonAdapter(val addonViewModel: AddonViewModel) :
|
|||||||
binding.addonSwitch.isChecked = model.enabled
|
binding.addonSwitch.isChecked = model.enabled
|
||||||
|
|
||||||
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
|
binding.addonSwitch.setOnCheckedChangeListener { _, checked ->
|
||||||
if (PatchType.from(model.type) == PatchType.Update && checked) {
|
model.enabled = checked
|
||||||
addonViewModel.enableOnlyThisUpdate(model)
|
|
||||||
notifyDataSetChanged()
|
|
||||||
} else {
|
|
||||||
model.enabled = checked
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val deleteAction = {
|
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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -10,10 +7,8 @@ import android.net.Uri
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import org.yuzu.yuzu_emu.R
|
|
||||||
import org.yuzu.yuzu_emu.databinding.CardFolderBinding
|
import org.yuzu.yuzu_emu.databinding.CardFolderBinding
|
||||||
import org.yuzu.yuzu_emu.fragments.GameFolderPropertiesDialogFragment
|
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.GameDir
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.ViewUtils.marquee
|
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.text = Uri.parse(model.uriString).path
|
||||||
path.marquee()
|
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 {
|
buttonEdit.setOnClickListener {
|
||||||
GameFolderPropertiesDialogFragment.newInstance(model)
|
GameFolderPropertiesDialogFragment.newInstance(model)
|
||||||
.show(
|
.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+8
-19
@@ -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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -9,13 +6,11 @@ package org.yuzu.yuzu_emu.fragments
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
|
import org.yuzu.yuzu_emu.databinding.DialogFolderPropertiesBinding
|
||||||
import org.yuzu.yuzu_emu.model.DirectoryType
|
|
||||||
import org.yuzu.yuzu_emu.model.GameDir
|
import org.yuzu.yuzu_emu.model.GameDir
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
@@ -30,18 +25,14 @@ class GameFolderPropertiesDialogFragment : DialogFragment() {
|
|||||||
val binding = DialogFolderPropertiesBinding.inflate(layoutInflater)
|
val binding = DialogFolderPropertiesBinding.inflate(layoutInflater)
|
||||||
val gameDir = requireArguments().parcelable<GameDir>(GAME_DIR)!!
|
val gameDir = requireArguments().parcelable<GameDir>(GAME_DIR)!!
|
||||||
|
|
||||||
// Hide deepScan for external content, do automatically
|
// Restore checkbox state
|
||||||
if (gameDir.type == DirectoryType.EXTERNAL_CONTENT) {
|
binding.deepScanSwitch.isChecked =
|
||||||
binding.deepScanSwitch.visibility = View.GONE
|
savedInstanceState?.getBoolean(DEEP_SCAN) ?: gameDir.deepScan
|
||||||
} else {
|
|
||||||
// Restore checkbox state for game dirs
|
|
||||||
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
|
deepScan = binding.deepScanSwitch.isChecked
|
||||||
binding.deepScanSwitch.setOnClickListener {
|
|
||||||
deepScan = binding.deepScanSwitch.isChecked
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(requireContext())
|
return MaterialAlertDialogBuilder(requireContext())
|
||||||
@@ -50,10 +41,8 @@ class GameFolderPropertiesDialogFragment : DialogFragment() {
|
|||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||||
val folderIndex = gamesViewModel.folders.value.indexOf(gameDir)
|
val folderIndex = gamesViewModel.folders.value.indexOf(gameDir)
|
||||||
if (folderIndex != -1) {
|
if (folderIndex != -1) {
|
||||||
if (gameDir.type == DirectoryType.GAME) {
|
gamesViewModel.folders.value[folderIndex].deepScan =
|
||||||
gamesViewModel.folders.value[folderIndex].deepScan =
|
binding.deepScanSwitch.isChecked
|
||||||
binding.deepScanSwitch.isChecked
|
|
||||||
}
|
|
||||||
gamesViewModel.updateGameDirs()
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
@@ -15,14 +15,11 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.adapters.FolderAdapter
|
import org.yuzu.yuzu_emu.adapters.FolderAdapter
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentFoldersBinding
|
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.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||||
@@ -76,25 +73,7 @@ class GameFoldersFragment : Fragment() {
|
|||||||
|
|
||||||
val mainActivity = requireActivity() as MainActivity
|
val mainActivity = requireActivity() as MainActivity
|
||||||
binding.buttonAdd.setOnClickListener {
|
binding.buttonAdd.setOnClickListener {
|
||||||
// Show a model to choose between Game and External Content
|
mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setInsets()
|
setInsets()
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// 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
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
import android.Manifest
|
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(
|
add(
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.gpu_driver_manager,
|
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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -51,68 +48,16 @@ class AddonViewModel : ViewModel() {
|
|||||||
?: emptyArray()
|
?: emptyArray()
|
||||||
).toMutableList()
|
).toMutableList()
|
||||||
patchList.sortBy { it.name }
|
patchList.sortBy { it.name }
|
||||||
|
|
||||||
// Ensure only one update is enabled
|
|
||||||
ensureSingleUpdateEnabled(patchList)
|
|
||||||
|
|
||||||
removeDuplicates(patchList)
|
|
||||||
|
|
||||||
_patchList.value = patchList
|
_patchList.value = patchList
|
||||||
isRefreshing.set(false)
|
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?) {
|
fun setAddonToDelete(patch: Patch?) {
|
||||||
_addonToDelete.value = 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) {
|
fun onDeleteAddon(patch: Patch) {
|
||||||
when (PatchType.from(patch.type)) {
|
when (PatchType.from(patch.type)) {
|
||||||
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
|
PatchType.Update -> NativeLibrary.removeUpdate(patch.programId)
|
||||||
@@ -127,27 +72,13 @@ class AddonViewModel : ViewModel() {
|
|||||||
return
|
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(
|
NativeConfig.setDisabledAddons(
|
||||||
game!!.programId,
|
game!!.programId,
|
||||||
_patchList.value.mapNotNull {
|
_patchList.value.mapNotNull {
|
||||||
if (it.enabled) {
|
if (it.enabled) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
if (PatchType.from(it.type) == PatchType.Update) {
|
it.name
|
||||||
if (it.name.contains("(NAND)") || it.name.contains("(SDMC)")) {
|
|
||||||
it.name
|
|
||||||
} else if (hasMultipleUpdates) {
|
|
||||||
"Update@${it.numericVersion}"
|
|
||||||
} else {
|
|
||||||
it.name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
it.name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.toTypedArray()
|
}.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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -12,14 +9,5 @@ import kotlinx.parcelize.Parcelize
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
data class GameDir(
|
data class GameDir(
|
||||||
val uriString: String,
|
val uriString: String,
|
||||||
var deepScan: Boolean,
|
var deepScan: Boolean
|
||||||
val type: DirectoryType = DirectoryType.GAME
|
) : Parcelable
|
||||||
) : Parcelable {
|
|
||||||
// Needed for JNI backward compatability
|
|
||||||
constructor(uriString: String, deepScan: Boolean) : this(uriString, deepScan, DirectoryType.GAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class DirectoryType {
|
|
||||||
GAME,
|
|
||||||
EXTERNAL_CONTENT
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.model
|
package org.yuzu.yuzu_emu.model
|
||||||
@@ -56,7 +56,7 @@ class GamesViewModel : ViewModel() {
|
|||||||
// Ensure keys are loaded so that ROM metadata can be decrypted.
|
// Ensure keys are loaded so that ROM metadata can be decrypted.
|
||||||
NativeLibrary.reloadKeys()
|
NativeLibrary.reloadKeys()
|
||||||
|
|
||||||
getGameDirsAndExternalContent()
|
getGameDirs()
|
||||||
reloadGames(directoriesChanged = false, firstStartup = true)
|
reloadGames(directoriesChanged = false, firstStartup = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,19 +144,11 @@ class GamesViewModel : ViewModel() {
|
|||||||
fun addFolder(gameDir: GameDir, savedFromGameFragment: Boolean) =
|
fun addFolder(gameDir: GameDir, savedFromGameFragment: Boolean) =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
when (gameDir.type) {
|
NativeConfig.addGameDir(gameDir)
|
||||||
DirectoryType.GAME -> {
|
val isFirstTimeSetup = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
NativeConfig.addGameDir(gameDir)
|
.getBoolean(org.yuzu.yuzu_emu.features.settings.model.Settings.PREF_FIRST_APP_LAUNCH, true)
|
||||||
val isFirstTimeSetup = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
|
||||||
.getBoolean(org.yuzu.yuzu_emu.features.settings.model.Settings.PREF_FIRST_APP_LAUNCH, true)
|
getGameDirs(!isFirstTimeSetup)
|
||||||
getGameDirsAndExternalContent(!isFirstTimeSetup)
|
|
||||||
}
|
|
||||||
DirectoryType.EXTERNAL_CONTENT -> {
|
|
||||||
addExternalContentDir(gameDir.uriString)
|
|
||||||
NativeConfig.saveGlobalConfig()
|
|
||||||
getGameDirsAndExternalContent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedFromGameFragment) {
|
if (savedFromGameFragment) {
|
||||||
@@ -176,15 +168,8 @@ class GamesViewModel : ViewModel() {
|
|||||||
val removedDirIndex = gameDirs.indexOf(gameDir)
|
val removedDirIndex = gameDirs.indexOf(gameDir)
|
||||||
if (removedDirIndex != -1) {
|
if (removedDirIndex != -1) {
|
||||||
gameDirs.removeAt(removedDirIndex)
|
gameDirs.removeAt(removedDirIndex)
|
||||||
when (gameDir.type) {
|
NativeConfig.setGameDirs(gameDirs.toTypedArray())
|
||||||
DirectoryType.GAME -> {
|
getGameDirs()
|
||||||
NativeConfig.setGameDirs(gameDirs.filter { it.type == DirectoryType.GAME }.toTypedArray())
|
|
||||||
}
|
|
||||||
DirectoryType.EXTERNAL_CONTENT -> {
|
|
||||||
removeExternalContentDir(gameDir.uriString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getGameDirsAndExternalContent()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,16 +177,15 @@ class GamesViewModel : ViewModel() {
|
|||||||
fun updateGameDirs() =
|
fun updateGameDirs() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val gameDirs = _folders.value.filter { it.type == DirectoryType.GAME }
|
NativeConfig.setGameDirs(_folders.value.toTypedArray())
|
||||||
NativeConfig.setGameDirs(gameDirs.toTypedArray())
|
getGameDirs()
|
||||||
getGameDirsAndExternalContent()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onOpenGameFoldersFragment() =
|
fun onOpenGameFoldersFragment() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
getGameDirsAndExternalContent()
|
getGameDirs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,36 +193,16 @@ class GamesViewModel : ViewModel() {
|
|||||||
NativeConfig.saveGlobalConfig()
|
NativeConfig.saveGlobalConfig()
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
getGameDirsAndExternalContent(true)
|
getGameDirs(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getGameDirsAndExternalContent(reloadList: Boolean = false) {
|
private fun getGameDirs(reloadList: Boolean = false) {
|
||||||
val gameDirs = NativeConfig.getGameDirs().toMutableList()
|
val gameDirs = NativeConfig.getGameDirs()
|
||||||
val externalContentDirs = NativeConfig.getExternalContentDirs().map {
|
_folders.value = gameDirs.toMutableList()
|
||||||
GameDir(it, false, DirectoryType.EXTERNAL_CONTENT)
|
|
||||||
}
|
|
||||||
gameDirs.addAll(externalContentDirs)
|
|
||||||
_folders.value = gameDirs
|
|
||||||
if (reloadList) {
|
if (reloadList) {
|
||||||
reloadGames(true)
|
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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -15,6 +12,5 @@ data class Patch(
|
|||||||
val version: String,
|
val version: String,
|
||||||
val type: Int,
|
val type: Int,
|
||||||
val programId: String,
|
val programId: String,
|
||||||
val titleId: String,
|
val titleId: String
|
||||||
val numericVersion: Long = 0
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import androidx.documentfile.provider.DocumentFile
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), ThemeProvider {
|
class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
@@ -200,7 +199,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
var artifact = tag
|
var artifact = tag
|
||||||
// Nightly builds have a slightly different format
|
// Nightly builds have a slightly different format
|
||||||
if (NativeLibrary.isNightlyBuild()) {
|
if (NativeLibrary.isNightlyBuild()) {
|
||||||
artifact = tag.substringAfter('.', tag)
|
val splitTag = tag.split('.')
|
||||||
|
artifact = splitTag.subList(1, splitTag.size - 1).joinToString(".")
|
||||||
}
|
}
|
||||||
downloadAndInstallUpdate(tag, artifact)
|
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) {
|
fun processGamesDir(result: Uri, calledFromGameFragment: Boolean = false) {
|
||||||
contentResolver.takePersistableUriPermission(
|
contentResolver.takePersistableUriPermission(
|
||||||
result,
|
result,
|
||||||
@@ -417,27 +410,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
.show(supportFragmentManager, AddGameFolderDialogFragment.TAG)
|
.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 ->
|
val getProdKey = registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
processKey(result, "keys")
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
@@ -25,7 +25,6 @@ object DirectoryInitialization {
|
|||||||
initializeInternalStorage()
|
initializeInternalStorage()
|
||||||
NativeLibrary.initializeSystem(false)
|
NativeLibrary.initializeSystem(false)
|
||||||
NativeConfig.initializeGlobalConfig()
|
NativeConfig.initializeGlobalConfig()
|
||||||
NativeLibrary.reloadProfiles()
|
|
||||||
migrateSettings()
|
migrateSettings()
|
||||||
areDirectoriesReady = true
|
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-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// 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
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
@@ -49,17 +49,6 @@ object GameHelper {
|
|||||||
// Remove previous filesystem provider information so we can get up to date version info
|
// Remove previous filesystem provider information so we can get up to date version info
|
||||||
NativeLibrary.clearFilesystemProvider()
|
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>()
|
val badDirs = mutableListOf<Int>()
|
||||||
gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
|
gameDirs.forEachIndexed { index: Int, gameDir: GameDir ->
|
||||||
val gameDirUri = gameDir.uriString.toUri()
|
val gameDirUri = gameDir.uriString.toUri()
|
||||||
@@ -99,33 +88,6 @@ object GameHelper {
|
|||||||
return games.toList()
|
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(
|
private fun addGamesRecursive(
|
||||||
games: MutableList<Game>,
|
games: MutableList<Game>,
|
||||||
files: Array<MinimalDocumentFile>,
|
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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
@@ -204,12 +204,4 @@ object NativeConfig {
|
|||||||
external fun getSdmcDir(): String
|
external fun getSdmcDir(): String
|
||||||
@Synchronized
|
@Synchronized
|
||||||
external fun setSdmcDir(path: String)
|
external fun setSdmcDir(path: String)
|
||||||
|
|
||||||
/**
|
|
||||||
* External Content Provider
|
|
||||||
*/
|
|
||||||
@Synchronized
|
|
||||||
external fun getExternalContentDirs(): Array<String>
|
|
||||||
@Synchronized
|
|
||||||
external fun setExternalContentDirs(dirs: Array<String>)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <common/fs/path_util.h>
|
#include <common/fs/path_util.h>
|
||||||
#include <common/logging/log.h>
|
#include <common/logging/log.h>
|
||||||
#include <common/settings.h>
|
|
||||||
#include <input_common/main.h>
|
#include <input_common/main.h>
|
||||||
#include "android_config.h"
|
#include "android_config.h"
|
||||||
#include "android_settings.h"
|
#include "android_settings.h"
|
||||||
@@ -70,18 +69,6 @@ void AndroidConfig::ReadPathValues() {
|
|||||||
}
|
}
|
||||||
EndArray();
|
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"));
|
const auto nand_dir_setting = ReadStringSetting(std::string("nand_directory"));
|
||||||
if (!nand_dir_setting.empty()) {
|
if (!nand_dir_setting.empty()) {
|
||||||
Common::FS::SetEdenPath(Common::FS::EdenPath::NANDDir, nand_dir_setting);
|
Common::FS::SetEdenPath(Common::FS::EdenPath::NANDDir, nand_dir_setting);
|
||||||
@@ -254,14 +241,6 @@ void AndroidConfig::SavePathValues() {
|
|||||||
}
|
}
|
||||||
EndArray();
|
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
|
// Save custom NAND directory
|
||||||
const auto nand_path = Common::FS::GetEdenPathString(Common::FS::EdenPath::NANDDir);
|
const auto nand_path = Common::FS::GetEdenPathString(Common::FS::EdenPath::NANDDir);
|
||||||
WriteStringSetting(std::string("nand_directory"), nand_path,
|
WriteStringSetting(std::string("nand_directory"), nand_path,
|
||||||
|
|||||||
@@ -49,17 +49,12 @@
|
|||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "frontend_common/play_time_manager.h"
|
#include "frontend_common/play_time_manager.h"
|
||||||
#include "core/constants.h"
|
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/cpu_manager.h"
|
#include "core/cpu_manager.h"
|
||||||
#include "core/crypto/key_manager.h"
|
#include "core/crypto/key_manager.h"
|
||||||
#include "core/file_sys/card_image.h"
|
#include "core/file_sys/card_image.h"
|
||||||
#include "core/file_sys/content_archive.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/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/submission_package.h"
|
||||||
#include "core/file_sys/vfs/vfs.h"
|
#include "core/file_sys/vfs/vfs.h"
|
||||||
#include "core/file_sys/vfs/vfs_real.h"
|
#include "core/file_sys/vfs/vfs_real.h"
|
||||||
@@ -215,109 +210,6 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
|
|||||||
return;
|
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);
|
auto loader = Loader::GetLoader(m_system, file);
|
||||||
if (!loader) {
|
if (!loader) {
|
||||||
return;
|
return;
|
||||||
@@ -334,6 +226,17 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath)
|
|||||||
m_manual_provider->AddEntry(FileSys::TitleType::Application,
|
m_manual_provider->AddEntry(FileSys::TitleType::Application,
|
||||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||||
program_id, file);
|
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.name),
|
||||||
Common::Android::ToJString(env, patch.version), static_cast<jint>(patch.type),
|
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.program_id)),
|
||||||
Common::Android::ToJString(env, std::to_string(patch.title_id)),
|
Common::Android::ToJString(env, std::to_string(patch.title_id)));
|
||||||
static_cast<jlong>(patch.numeric_version));
|
|
||||||
env->SetObjectArrayElement(jpatchArray, i, jpatch);
|
env->SetObjectArrayElement(jpatchArray, i, jpatch);
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
@@ -1776,7 +1678,7 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
|||||||
#endif
|
#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/{}/{}",
|
const std::string url = fmt::format("{}/{}/releases/download/{}/{}",
|
||||||
std::string{Common::g_build_auto_update_website},
|
std::string{Common::g_build_auto_update_website},
|
||||||
std::string{Common::g_build_auto_update_repo},
|
std::string{Common::g_build_auto_update_repo},
|
||||||
@@ -1784,7 +1686,6 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
|||||||
apk_filename);
|
apk_filename);
|
||||||
|
|
||||||
env->ReleaseStringUTFChars(tag, version_str);
|
env->ReleaseStringUTFChars(tag, version_str);
|
||||||
env->ReleaseStringUTFChars(artifact, artifact_str);
|
|
||||||
env->ReleaseStringUTFChars(packageId, package_id_str);
|
env->ReleaseStringUTFChars(packageId, package_id_str);
|
||||||
return env->NewStringUTF(url.c_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);
|
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"
|
} // 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);
|
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"
|
} // extern "C"
|
||||||
|
|||||||
@@ -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
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="horizontal"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:layout_gravity="center_vertical">
|
android:layout_gravity="center_vertical">
|
||||||
|
|
||||||
@@ -23,25 +23,12 @@
|
|||||||
android:layout_gravity="center_vertical|start"
|
android:layout_gravity="center_vertical|start"
|
||||||
android:requiresFadingEdge="horizontal"
|
android:requiresFadingEdge="horizontal"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/type_indicator"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/button_layout"
|
app:layout_constraintEnd_toStartOf="@+id/button_layout"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="@string/select_gpu_driver_default" />
|
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
|
<LinearLayout
|
||||||
android:id="@+id/button_layout"
|
android:id="@+id/button_layout"
|
||||||
android:layout_width="wrap_content"
|
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
|
<action
|
||||||
android:id="@+id/action_homeSettingsFragment_to_gameFoldersFragment"
|
android:id="@+id/action_homeSettingsFragment_to_gameFoldersFragment"
|
||||||
app:destination="@id/gameFoldersFragment" />
|
app:destination="@id/gameFoldersFragment" />
|
||||||
<action
|
|
||||||
android:id="@+id/action_homeSettingsFragment_to_profileManagerFragment"
|
|
||||||
app:destination="@id/profileManagerFragment" />
|
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
@@ -189,17 +186,5 @@
|
|||||||
app:nullable="true"
|
app:nullable="true"
|
||||||
android:defaultValue="@null" />
|
android:defaultValue="@null" />
|
||||||
</fragment>
|
</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>
|
</navigation>
|
||||||
|
|||||||
@@ -381,9 +381,9 @@
|
|||||||
<string name="qlaunch_description">Launch applications from the system home screen</string>
|
<string name="qlaunch_description">Launch applications from the system home screen</string>
|
||||||
<string name="applets">Applet launcher</string>
|
<string name="applets">Applet launcher</string>
|
||||||
<string name="applets_description">Launch system applets using installed firmware</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_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">Album</string>
|
||||||
<string name="album_applet_description">See images stored in the user screenshots folder with the system photo viewer</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>
|
<string name="mii_edit_applet">Mii edit</string>
|
||||||
@@ -1239,35 +1239,6 @@
|
|||||||
<string name="enable_overlay">Enable Overlay Applet</string>
|
<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>
|
<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 -->
|
<!-- Licenses screen strings -->
|
||||||
<string name="licenses">Licenses</string>
|
<string name="licenses">Licenses</string>
|
||||||
<string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</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
|
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.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<string name="external_content">External Content</string>
|
|
||||||
<string name="add_folders">Add Folder</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
@@ -516,7 +516,7 @@ namespace Common::Android {
|
|||||||
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
|
s_patch_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_class));
|
||||||
s_patch_constructor = env->GetMethodID(
|
s_patch_constructor = env->GetMethodID(
|
||||||
patch_class, "<init>",
|
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_enabled_field = env->GetFieldID(patch_class, "enabled", "Z");
|
||||||
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
|
s_patch_name_field = env->GetFieldID(patch_class, "name", "Ljava/lang/String;");
|
||||||
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/String;");
|
s_patch_version_field = env->GetFieldID(patch_class, "version", "Ljava/lang/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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cstdlib>
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ namespace {
|
|||||||
|
|
||||||
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
|
/// @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 :)
|
/// 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) {
|
const auto rfind = [source](const std::string_view match) {
|
||||||
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
|
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.
|
/// @brief Interface for logging backends.
|
||||||
struct Backend {
|
struct Backend {
|
||||||
virtual ~Backend() noexcept = default;
|
virtual ~Backend() = default;
|
||||||
virtual void Write(const Entry& entry) noexcept = 0;
|
virtual void Write(const Entry& entry) = 0;
|
||||||
virtual void EnableForStacktrace() noexcept= 0;
|
virtual void EnableForStacktrace() = 0;
|
||||||
virtual void Flush() noexcept = 0;
|
virtual void Flush() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief Backend that writes to stderr and with color
|
/// @brief Backend that writes to stderr and with color
|
||||||
struct ColorConsoleBackend final : public Backend {
|
struct ColorConsoleBackend final : public Backend {
|
||||||
explicit ColorConsoleBackend() noexcept = default;
|
explicit ColorConsoleBackend() = default;
|
||||||
~ColorConsoleBackend() noexcept override = default;
|
~ColorConsoleBackend() override = default;
|
||||||
|
|
||||||
void Write(const Entry& entry) noexcept override {
|
void Write(const Entry& entry) override {
|
||||||
if (enabled.load(std::memory_order_relaxed))
|
if (enabled.load(std::memory_order_relaxed))
|
||||||
PrintColoredMessage(entry);
|
PrintColoredMessage(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flush() noexcept override {
|
void Flush() override {
|
||||||
// stderr shouldn't be buffered
|
// stderr shouldn't be buffered
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnableForStacktrace() noexcept override {
|
void EnableForStacktrace() override {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetEnabled(bool enabled_) noexcept {
|
void SetEnabled(bool enabled_) {
|
||||||
enabled = enabled_;
|
enabled = enabled_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,10 +83,9 @@ private:
|
|||||||
std::atomic_bool enabled{false};
|
std::atomic_bool enabled{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef __OPENORBIS__
|
|
||||||
/// @brief Backend that writes to a file passed into the constructor
|
/// @brief Backend that writes to a file passed into the constructor
|
||||||
struct FileBackend final : public Backend {
|
struct FileBackend final : public Backend {
|
||||||
explicit FileBackend(const std::filesystem::path& filename) noexcept {
|
explicit FileBackend(const std::filesystem::path& filename) {
|
||||||
auto old_filename = filename;
|
auto old_filename = filename;
|
||||||
old_filename += ".old.txt";
|
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);
|
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)
|
if (!enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -149,11 +147,11 @@ struct FileBackend final : public Backend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flush() noexcept override {
|
void Flush() override {
|
||||||
file->Flush();
|
file->Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnableForStacktrace() noexcept override {
|
void EnableForStacktrace() override {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
bytes_written = 0;
|
bytes_written = 0;
|
||||||
}
|
}
|
||||||
@@ -163,30 +161,29 @@ private:
|
|||||||
std::size_t bytes_written = 0;
|
std::size_t bytes_written = 0;
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
/// @brief Backend that writes to Visual Studio's output window
|
/// @brief Backend that writes to Visual Studio's output window
|
||||||
struct DebuggerBackend final : public Backend {
|
struct DebuggerBackend final : public Backend {
|
||||||
explicit DebuggerBackend() noexcept = default;
|
explicit DebuggerBackend() = default;
|
||||||
~DebuggerBackend() noexcept override = default;
|
~DebuggerBackend() override = default;
|
||||||
void Write(const Entry& entry) noexcept override {
|
void Write(const Entry& entry) override {
|
||||||
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
|
||||||
}
|
}
|
||||||
void Flush() noexcept override {}
|
void Flush() override {}
|
||||||
void EnableForStacktrace() noexcept override {}
|
void EnableForStacktrace() override {}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
/// @brief Backend that writes to the Android logcat
|
/// @brief Backend that writes to the Android logcat
|
||||||
struct LogcatBackend : public Backend {
|
struct LogcatBackend : public Backend {
|
||||||
explicit LogcatBackend() noexcept = default;
|
explicit LogcatBackend() = default;
|
||||||
~LogcatBackend() noexcept override = default;
|
~LogcatBackend() override = default;
|
||||||
void Write(const Entry& entry) noexcept override {
|
void Write(const Entry& entry) override {
|
||||||
PrintMessageToLogcat(entry);
|
PrintMessageToLogcat(entry);
|
||||||
}
|
}
|
||||||
void Flush() noexcept override {}
|
void Flush() override {}
|
||||||
void EnableForStacktrace() noexcept override {}
|
void EnableForStacktrace() override {}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -195,13 +192,13 @@ bool initialization_in_progress_suppress_logging = true;
|
|||||||
/// @brief Static state as a singleton.
|
/// @brief Static state as a singleton.
|
||||||
class Impl {
|
class Impl {
|
||||||
public:
|
public:
|
||||||
static Impl& Instance() noexcept {
|
static Impl& Instance() {
|
||||||
if (!instance)
|
if (!instance)
|
||||||
std::abort();
|
throw std::runtime_error("Using Logging instance before its initialization");
|
||||||
return *instance;
|
return *instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Initialize() noexcept {
|
static void Initialize() {
|
||||||
if (instance) {
|
if (instance) {
|
||||||
LOG_WARNING(Log, "Reinitializing logging backend");
|
LOG_WARNING(Log, "Reinitializing logging backend");
|
||||||
return;
|
return;
|
||||||
@@ -215,25 +212,25 @@ public:
|
|||||||
initialization_in_progress_suppress_logging = false;
|
initialization_in_progress_suppress_logging = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Start() noexcept {
|
static void Start() {
|
||||||
instance->StartBackendThread();
|
instance->StartBackendThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Stop() noexcept {
|
static void Stop() {
|
||||||
instance->StopBackendThread();
|
instance->StopBackendThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
Impl(const Impl&) noexcept = delete;
|
Impl(const Impl&) = delete;
|
||||||
Impl& operator=(const Impl&) noexcept = delete;
|
Impl& operator=(const Impl&) = delete;
|
||||||
|
|
||||||
Impl(Impl&&) noexcept = delete;
|
Impl(Impl&&) = delete;
|
||||||
Impl& operator=(Impl&&) noexcept = delete;
|
Impl& operator=(Impl&&) = delete;
|
||||||
|
|
||||||
void SetGlobalFilter(const Filter& f) noexcept {
|
void SetGlobalFilter(const Filter& f) {
|
||||||
filter = f;
|
filter = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetColorConsoleBackendEnabled(bool enabled) noexcept {
|
void SetColorConsoleBackendEnabled(bool enabled) {
|
||||||
color_console_backend.SetEnabled(enabled);
|
color_console_backend.SetEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,21 +238,19 @@ public:
|
|||||||
return filter.CheckMessage(log_class, log_level);
|
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 {
|
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||||
message_queue.EmplaceWait(CreateEntry(log_class, log_level, TrimSourcePath(filename), line_num, function, std::move(message)));
|
const char* function, std::string&& message) noexcept {
|
||||||
|
message_queue.EmplaceWait(
|
||||||
|
CreateEntry(log_class, log_level, TrimSourcePath(filename), line_num, function, std::move(message)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_) noexcept :
|
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
|
||||||
filter{filter_}
|
: filter{filter_}, file_backend{file_backend_filename} {}
|
||||||
#ifndef __OPENORBIS__
|
|
||||||
, file_backend{file_backend_filename}
|
|
||||||
#endif
|
|
||||||
{}
|
|
||||||
|
|
||||||
~Impl() noexcept = default;
|
~Impl() = default;
|
||||||
|
|
||||||
void StartBackendThread() noexcept {
|
void StartBackendThread() {
|
||||||
backend_thread = std::jthread([this](std::stop_token stop_token) {
|
backend_thread = std::jthread([this](std::stop_token stop_token) {
|
||||||
Common::SetCurrentThreadName("Logger");
|
Common::SetCurrentThreadName("Logger");
|
||||||
Entry entry;
|
Entry entry;
|
||||||
@@ -276,14 +271,15 @@ private:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void StopBackendThread() noexcept {
|
void StopBackendThread() {
|
||||||
backend_thread.request_stop();
|
backend_thread.request_stop();
|
||||||
if (backend_thread.joinable())
|
if (backend_thread.joinable())
|
||||||
backend_thread.join();
|
backend_thread.join();
|
||||||
ForEachBackend([](Backend& backend) { backend.Flush(); });
|
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::duration_cast;
|
||||||
using std::chrono::microseconds;
|
using std::chrono::microseconds;
|
||||||
using std::chrono::steady_clock;
|
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));
|
lambda(static_cast<Backend&>(color_console_backend));
|
||||||
#ifndef __OPENORBIS__
|
|
||||||
lambda(static_cast<Backend&>(file_backend));
|
lambda(static_cast<Backend&>(file_backend));
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
lambda(static_cast<Backend&>(debugger_backend));
|
lambda(static_cast<Backend&>(debugger_backend));
|
||||||
#endif
|
#endif
|
||||||
@@ -311,7 +305,7 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Deleter(Impl* ptr) noexcept {
|
static void Deleter(Impl* ptr) {
|
||||||
delete ptr;
|
delete ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,9 +313,7 @@ private:
|
|||||||
|
|
||||||
Filter filter;
|
Filter filter;
|
||||||
ColorConsoleBackend color_console_backend{};
|
ColorConsoleBackend color_console_backend{};
|
||||||
#ifndef __OPENORBIS__
|
|
||||||
FileBackend file_backend;
|
FileBackend file_backend;
|
||||||
#endif
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
DebuggerBackend debugger_backend{};
|
DebuggerBackend debugger_backend{};
|
||||||
#endif
|
#endif
|
||||||
@@ -359,7 +351,9 @@ void SetColorConsoleBackendEnabled(bool enabled) {
|
|||||||
Impl::Instance().SetColorConsoleBackendEnabled(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) {
|
if (!initialization_in_progress_suppress_logging) {
|
||||||
auto& instance = Impl::Instance();
|
auto& instance = Impl::Instance();
|
||||||
if (instance.CanPushEntry(log_class, log_level))
|
if (instance.CanPushEntry(log_class, log_level))
|
||||||
|
|||||||
@@ -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-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -12,15 +9,17 @@
|
|||||||
|
|
||||||
namespace Common::Log {
|
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 {
|
struct Entry {
|
||||||
std::chrono::microseconds timestamp;
|
std::chrono::microseconds timestamp;
|
||||||
Class log_class{};
|
Class log_class{};
|
||||||
Level log_level{};
|
Level log_level{};
|
||||||
const char* filename = nullptr;
|
const char* filename = nullptr;
|
||||||
unsigned int line_num = 0;
|
unsigned int line_num = 0;
|
||||||
const char* function = nullptr;
|
std::string function;
|
||||||
std::string message;
|
std::string message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,26 +24,24 @@
|
|||||||
namespace Common::Log {
|
namespace Common::Log {
|
||||||
|
|
||||||
// Some IDEs prefer <file>:<line> instead, so let's just do that :)
|
// 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 "";
|
if (!entry.filename) return "";
|
||||||
|
|
||||||
auto const time_seconds = uint32_t(entry.timestamp.count() / 1000000);
|
auto const time_seconds = uint32_t(entry.timestamp.count() / 1000000);
|
||||||
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
|
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
|
||||||
auto const class_name = GetLogClassName(entry.log_class);
|
char const* class_name = GetLogClassName(entry.log_class);
|
||||||
auto const level_name = GetLevelName(entry.log_level);
|
char const* level_name = GetLevelName(entry.log_level);
|
||||||
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional,
|
return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional,
|
||||||
class_name, level_name, entry.filename, entry.line_num, entry.function,
|
class_name, level_name, entry.filename, entry.line_num, entry.function,
|
||||||
entry.message);
|
entry.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Formats and prints a log entry to stderr.
|
void PrintMessage(const Entry& entry) {
|
||||||
static void PrintMessage(const Entry& entry) noexcept {
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
auto const str = FormatLogMessage(entry).append(1, '\n');
|
auto const str = FormatLogMessage(entry).append(1, '\n');
|
||||||
fwrite(str.c_str(), 1, str.size(), stderr);
|
|
||||||
#else
|
#else
|
||||||
#define ESC "\x1b"
|
#define ESC "\x1b"
|
||||||
auto const color_str = [&entry]() -> const char* {
|
auto str = std::string{[&entry]() -> const char* {
|
||||||
switch (entry.log_level) {
|
switch (entry.log_level) {
|
||||||
case Level::Debug: return ESC "[0;36m"; // Cyan
|
case Level::Debug: return ESC "[0;36m"; // Cyan
|
||||||
case Level::Info: return ESC "[0;37m"; // Bright gray
|
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
|
case Level::Critical: return ESC "[1;35m"; // Bright magenta
|
||||||
default: return ESC "[1;30m"; // Grey
|
default: return ESC "[1;30m"; // Grey
|
||||||
}
|
}
|
||||||
}();
|
}()};
|
||||||
auto const time_seconds = uint32_t(entry.timestamp.count() / 1000000);
|
str.append(FormatLogMessage(entry));
|
||||||
auto const time_fractional = uint32_t(entry.timestamp.count() % 1000000);
|
str.append(ESC "[0m\n");
|
||||||
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());
|
|
||||||
#undef ESC
|
#undef ESC
|
||||||
#endif
|
#endif
|
||||||
|
fwrite(str.c_str(), 1, str.size(), stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintColoredMessage(const Entry& entry) noexcept {
|
void PrintColoredMessage(const Entry& entry) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
|
HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE);
|
||||||
if (console_handle == INVALID_HANDLE_VALUE)
|
if (console_handle == INVALID_HANDLE_VALUE)
|
||||||
@@ -90,7 +84,7 @@ void PrintColoredMessage(const Entry& entry) noexcept {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintMessageToLogcat(const Entry& entry) noexcept {
|
void PrintMessageToLogcat(const Entry& entry) {
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
android_LogPriority android_log_priority = [&]() {
|
android_LogPriority android_log_priority = [&]() {
|
||||||
switch (entry.log_level) {
|
switch (entry.log_level) {
|
||||||
|
|||||||
@@ -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-FileCopyrightText: 2014 Citra Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -13,9 +10,11 @@ namespace Common::Log {
|
|||||||
struct Entry;
|
struct Entry;
|
||||||
|
|
||||||
/// Formats a log entry into the provided text buffer.
|
/// 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.
|
/// 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.
|
/// Formats and prints a log entry to the android logcat.
|
||||||
void PrintMessageToLogcat(const Entry& entry) noexcept;
|
void PrintMessageToLogcat(const Entry& entry);
|
||||||
} // namespace Common::Log
|
} // namespace Common::Log
|
||||||
|
|||||||
+34
-32
@@ -5,6 +5,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <version>
|
#include <version>
|
||||||
|
#include "common/settings_enums.h"
|
||||||
#if __cpp_lib_chrono >= 201907L
|
#if __cpp_lib_chrono >= 201907L
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
@@ -16,10 +17,8 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <deque>
|
|
||||||
#include <fmt/core.h>
|
#include <fmt/core.h>
|
||||||
|
|
||||||
#include "common/settings_enums.h"
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/fs/fs_util.h"
|
#include "common/fs/fs_util.h"
|
||||||
#include "common/fs/path_util.h"
|
#include "common/fs/path_util.h"
|
||||||
@@ -111,38 +110,36 @@ std::string GetTimeZoneString(TimeZone time_zone) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LogSettings() {
|
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 (auto& [category, settings] : values.linkage.by_category) {
|
||||||
for (const auto& setting : settings) {
|
for (const auto& setting : settings) {
|
||||||
// Hide the token secret, for security reasons.
|
if (setting->Id() == values.eden_token.Id()) {
|
||||||
if (setting->Id() != values.eden_token.Id()) {
|
// Hide the token secret, for security reasons.
|
||||||
auto const is_default = setting->ToString() == setting->DefaultToString();
|
continue;
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto name = fmt::format(
|
||||||
|
"{:c}{:c} {}.{}", setting->ToString() == setting->DefaultToString() ? '-' : 'M',
|
||||||
|
setting->UsingGlobal() ? '-' : 'C', TranslateCategory(category),
|
||||||
|
setting->GetLabel());
|
||||||
|
|
||||||
|
log_setting(name, setting->Canonicalize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log_path("DataStorage_CacheDir", Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir));
|
||||||
std::string settings_str{};
|
log_path("DataStorage_ConfigDir", Common::FS::GetEdenPath(Common::FS::EdenPath::ConfigDir));
|
||||||
for (auto const& e : settings_list)
|
log_path("DataStorage_LoadDir", Common::FS::GetEdenPath(Common::FS::EdenPath::LoadDir));
|
||||||
settings_str += e;
|
log_path("DataStorage_NANDDir", Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir));
|
||||||
LOG_INFO(Config, "Eden Configuration:\n{}", settings_str);
|
log_path("DataStorage_SaveDir", Common::FS::GetEdenPath(Common::FS::EdenPath::SaveDir));
|
||||||
#define LOG_PATH(NAME) \
|
log_path("DataStorage_SDMCDir", Common::FS::GetEdenPath(Common::FS::EdenPath::SDMCDir));
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getDebugKnobAt(u8 i) {
|
bool getDebugKnobAt(u8 i) {
|
||||||
@@ -174,10 +171,12 @@ bool IsDMALevelSafe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IsFastmemEnabled() {
|
bool IsFastmemEnabled() {
|
||||||
if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging)
|
if (values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Debugging) {
|
||||||
return bool(values.cpuopt_fastmem);
|
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);
|
return bool(values.cpuopt_unsafe_host_mmu);
|
||||||
|
}
|
||||||
#if !defined(__APPLE__) && !defined(__linux__) && !defined(__ANDROID__) && !defined(_WIN32)
|
#if !defined(__APPLE__) && !defined(__linux__) && !defined(__ANDROID__) && !defined(_WIN32)
|
||||||
return false;
|
return false;
|
||||||
#else
|
#else
|
||||||
@@ -342,7 +341,10 @@ void TranslateResolutionInfo(ResolutionSetup setup, ResolutionScalingInfo& info)
|
|||||||
info.down_shift = 0;
|
info.down_shift = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
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.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;
|
info.down_factor = static_cast<f32>(1U << info.down_shift) / info.up_scale;
|
||||||
|
|||||||
@@ -715,7 +715,6 @@ struct Values {
|
|||||||
Category::DataStorage};
|
Category::DataStorage};
|
||||||
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
|
Setting<std::string> gamecard_path{linkage, std::string(), "gamecard_path",
|
||||||
Category::DataStorage};
|
Category::DataStorage};
|
||||||
std::vector<std::string> external_content_dirs;
|
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
bool record_frame_times;
|
bool record_frame_times;
|
||||||
@@ -795,7 +794,6 @@ struct Values {
|
|||||||
|
|
||||||
// Per-game overrides
|
// Per-game overrides
|
||||||
bool use_squashed_iterated_blend;
|
bool use_squashed_iterated_blend;
|
||||||
bool enable_global_overrides{true};
|
|
||||||
|
|
||||||
Setting<bool> enable_overlay{linkage, false, "enable_overlay", Category::Core};
|
Setting<bool> enable_overlay{linkage, false, "enable_overlay", Category::Core};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
|||||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||||
ENUM(GameListMode, TreeView, GridView);
|
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
inline std::string_view CanonicalizeEnum(Type id) {
|
inline std::string_view CanonicalizeEnum(Type id) {
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ add_library(core STATIC
|
|||||||
constants.h
|
constants.h
|
||||||
core.cpp
|
core.cpp
|
||||||
core.h
|
core.h
|
||||||
game_overrides.cpp
|
|
||||||
game_overrides.h
|
|
||||||
game_settings.cpp
|
game_settings.cpp
|
||||||
game_settings.h
|
game_settings.h
|
||||||
core_timing.cpp
|
core_timing.cpp
|
||||||
|
|||||||
+153
-326
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
@@ -20,13 +20,9 @@ namespace Core::NCE {
|
|||||||
Patcher::Patcher(Patcher&& other) noexcept
|
Patcher::Patcher(Patcher&& other) noexcept
|
||||||
: patch_cache(std::move(other.patch_cache)),
|
: patch_cache(std::move(other.patch_cache)),
|
||||||
m_patch_instructions(std::move(other.m_patch_instructions)),
|
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(m_patch_instructions),
|
||||||
c_pre(m_patch_instructions_pre),
|
|
||||||
m_save_context(other.m_save_context),
|
m_save_context(other.m_save_context),
|
||||||
m_load_context(other.m_load_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),
|
mode(other.mode),
|
||||||
total_program_size(other.total_program_size),
|
total_program_size(other.total_program_size),
|
||||||
m_relocate_module_index(other.m_relocate_module_index),
|
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 size_t MaxRelativeBranch = 128_MiB;
|
||||||
constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
|
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 {}",
|
LOG_WARNING(Core_ARM, "Patcher initialized with LRU cache {}",
|
||||||
patch_cache.isEnabled() ? "enabled" : "disabled");
|
patch_cache.isEnabled() ? "enabled" : "disabled");
|
||||||
// The first word of the patch section is always a branch to the first instruction of the
|
// The first word of the patch section is always a branch to the first instruction of the
|
||||||
// module.
|
// module.
|
||||||
c.dw(0);
|
c.dw(0);
|
||||||
c_pre.dw(0);
|
|
||||||
|
|
||||||
// Write save context helper function.
|
// Write save context helper function.
|
||||||
c.l(m_save_context);
|
c.l(m_save_context);
|
||||||
WriteSaveContext();
|
WriteSaveContext();
|
||||||
c_pre.l(m_save_context_pre);
|
|
||||||
WriteSaveContext(c_pre);
|
|
||||||
|
|
||||||
// Write load context helper function.
|
// Write load context helper function.
|
||||||
c.l(m_load_context);
|
c.l(m_load_context);
|
||||||
WriteLoadContext();
|
WriteLoadContext();
|
||||||
c_pre.l(m_load_context_pre);
|
|
||||||
WriteLoadContext(c_pre);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Patcher::~Patcher() = default;
|
Patcher::~Patcher() = default;
|
||||||
@@ -73,16 +64,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
|||||||
const Kernel::CodeSet::Segment& code) {
|
const Kernel::CodeSet::Segment& code) {
|
||||||
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
|
// 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();
|
const size_t image_size = program_image.size();
|
||||||
|
if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) {
|
||||||
// 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) {
|
|
||||||
return false;
|
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
|
// The first word of the patch section is always a branch to the first instruction of the
|
||||||
// module.
|
// module.
|
||||||
if (use_split) {
|
curr_patch->m_branch_to_module_relocations.push_back({0, 0});
|
||||||
// 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});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve text segment data.
|
// Retrieve text segment data.
|
||||||
const auto text = std::span{program_image}.subspan(code.offset, code.size);
|
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++) {
|
for (u32 i = ModuleCodeIndex; i < static_cast<u32>(text_words.size()); i++) {
|
||||||
const u32 inst = text_words[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 this_offset = i * sizeof(u32);
|
||||||
const uintptr_t next_offset = this_offset + sizeof(u32);
|
const uintptr_t next_offset = this_offset + sizeof(u32);
|
||||||
|
|
||||||
pre_buffer = use_split && (this_offset < MaxRelativeBranch);
|
// Relocate from here to patch.
|
||||||
|
this->BranchToPatch(this_offset);
|
||||||
// Relocate to pre- or post-patch
|
|
||||||
if (pre_buffer) {
|
|
||||||
this->BranchToPatchPre(this_offset);
|
|
||||||
} else {
|
|
||||||
this->BranchToPatch(this_offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relocate from patch to next instruction.
|
// Relocate from patch to next instruction.
|
||||||
return next_offset;
|
return next_offset;
|
||||||
@@ -127,13 +98,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
|||||||
|
|
||||||
// SVC
|
// SVC
|
||||||
if (auto svc = SVC{inst}; svc.Verify()) {
|
if (auto svc = SVC{inst}; svc.Verify()) {
|
||||||
bool pre_buffer = false;
|
WriteSvcTrampoline(AddRelocations(), svc.GetValue());
|
||||||
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);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,25 +109,13 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
|||||||
const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0
|
const auto src_reg = mrs.GetSystemReg() == TpidrroEl0 ? oaknut::SystemReg::TPIDRRO_EL0
|
||||||
: oaknut::SystemReg::TPIDR_EL0;
|
: oaknut::SystemReg::TPIDR_EL0;
|
||||||
const auto dest_reg = oaknut::XReg{static_cast<int>(mrs.GetRt())};
|
const auto dest_reg = oaknut::XReg{static_cast<int>(mrs.GetRt())};
|
||||||
bool pre_buffer = false;
|
WriteMrsHandler(AddRelocations(), dest_reg, src_reg);
|
||||||
auto ret = AddRelocations(pre_buffer);
|
|
||||||
if (pre_buffer) {
|
|
||||||
WriteMrsHandler(ret, dest_reg, src_reg, c_pre);
|
|
||||||
} else {
|
|
||||||
WriteMrsHandler(ret, dest_reg, src_reg, c);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MRS Xn, CNTPCT_EL0
|
// MRS Xn, CNTPCT_EL0
|
||||||
if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntpctEl0) {
|
if (auto mrs = MRS{inst}; mrs.Verify() && mrs.GetSystemReg() == CntpctEl0) {
|
||||||
bool pre_buffer = false;
|
WriteCntpctHandler(AddRelocations(), oaknut::XReg{static_cast<int>(mrs.GetRt())});
|
||||||
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);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,13 +126,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
|||||||
|
|
||||||
// MSR TPIDR_EL0, Xn
|
// MSR TPIDR_EL0, Xn
|
||||||
if (auto msr = MSR{inst}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) {
|
if (auto msr = MSR{inst}; msr.Verify() && msr.GetSystemReg() == TpidrEl0) {
|
||||||
bool pre_buffer = false;
|
WriteMsrHandler(AddRelocations(), oaknut::XReg{static_cast<int>(msr.GetRt())});
|
||||||
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);
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,11 +137,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
|||||||
|
|
||||||
// Determine patching mode for the final relocation step
|
// Determine patching mode for the final relocation step
|
||||||
total_program_size += image_size;
|
total_program_size += image_size;
|
||||||
if (use_split) {
|
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
|
||||||
this->mode = PatchMode::Split;
|
|
||||||
} else {
|
|
||||||
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,9 +146,7 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
|||||||
Kernel::PhysicalMemory& program_image,
|
Kernel::PhysicalMemory& program_image,
|
||||||
EntryTrampolines* out_trampolines) {
|
EntryTrampolines* out_trampolines) {
|
||||||
const size_t patch_size = GetSectionSize();
|
const size_t patch_size = GetSectionSize();
|
||||||
const size_t pre_patch_size = GetPreSectionSize();
|
const size_t image_size = program_image.size();
|
||||||
|
|
||||||
const size_t image_size = (mode == PatchMode::Split) ? program_image.size() - pre_patch_size : program_image.size();
|
|
||||||
|
|
||||||
// Retrieve text segment data.
|
// Retrieve text segment data.
|
||||||
const auto text = std::span{program_image}.subspan(code.offset, code.size);
|
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) {
|
const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) {
|
||||||
oaknut::CodeGenerator rc{target};
|
oaknut::CodeGenerator rc{target};
|
||||||
if (mode == PatchMode::PreText) {
|
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) {
|
const auto RebasePatch = [&](ptrdiff_t patch_offset) {
|
||||||
if (mode == PatchMode::PreText) {
|
if (mode == PatchMode::PreText) {
|
||||||
return GetInteger(load_base) + patch_offset;
|
return GetInteger(load_base) + patch_offset;
|
||||||
@@ -261,87 +182,28 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
|||||||
const auto RebasePc = [&](uintptr_t module_offset) {
|
const auto RebasePc = [&](uintptr_t module_offset) {
|
||||||
if (mode == PatchMode::PreText) {
|
if (mode == PatchMode::PreText) {
|
||||||
return GetInteger(load_base) + patch_size + module_offset;
|
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!
|
// We are now ready to relocate!
|
||||||
auto& patch = modules[m_relocate_module_index++];
|
auto& patch = modules[m_relocate_module_index++];
|
||||||
|
for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
|
||||||
if (mode == PatchMode::Split) {
|
ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
|
||||||
for (const Relocation& rel : patch.m_branch_to_pre_patch_relocations) {
|
}
|
||||||
ApplyBranchToPatchRelocationPre(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),
|
||||||
LOG_DEBUG(Core_ARM, "applied Pre: {}", patch.m_branch_to_pre_patch_relocations.size());
|
rel);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode != PatchMode::Split) {
|
// Rewrite PC constants and record post trampolines
|
||||||
for (const Trampoline& rel : patch.m_trampolines) {
|
for (const Relocation& rel : patch.m_write_module_pc_relocations) {
|
||||||
out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)});
|
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.
|
// 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);
|
ASSERT(image_size == total_program_size);
|
||||||
std::memcpy(program_image.data(), m_patch_instructions.data(),
|
std::memcpy(program_image.data(), m_patch_instructions.data(),
|
||||||
m_patch_instructions.size() * sizeof(u32));
|
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 {
|
} else {
|
||||||
program_image.resize(image_size + patch_size);
|
program_image.resize(image_size + patch_size);
|
||||||
std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
|
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);
|
return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Patcher::GetPreSectionSize() const noexcept {
|
void Patcher::WriteLoadContext() {
|
||||||
return Common::AlignUp(m_patch_instructions_pre.size() * sizeof(u32), Core::Memory::YUZU_PAGESIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Patcher::WriteLoadContext(oaknut::VectorCodeGenerator& cg) {
|
|
||||||
// This function was called, which modifies X30, so use that as a scratch register.
|
// 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
|
// SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes
|
||||||
// of stack.
|
// of stack.
|
||||||
cg.STR(X30, SP, 8);
|
c.STR(X30, SP, 8);
|
||||||
cg.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
|
c.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
|
||||||
cg.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
|
c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
|
||||||
|
|
||||||
// Load system registers.
|
// Load system registers.
|
||||||
cg.LDR(W0, X30, offsetof(GuestContext, fpsr));
|
c.LDR(W0, X30, offsetof(GuestContext, fpsr));
|
||||||
cg.MSR(oaknut::SystemReg::FPSR, X0);
|
c.MSR(oaknut::SystemReg::FPSR, X0);
|
||||||
cg.LDR(W0, X30, offsetof(GuestContext, fpcr));
|
c.LDR(W0, X30, offsetof(GuestContext, fpcr));
|
||||||
cg.MSR(oaknut::SystemReg::FPCR, X0);
|
c.MSR(oaknut::SystemReg::FPCR, X0);
|
||||||
cg.LDR(W0, X30, offsetof(GuestContext, nzcv));
|
c.LDR(W0, X30, offsetof(GuestContext, nzcv));
|
||||||
cg.MSR(oaknut::SystemReg::NZCV, X0);
|
c.MSR(oaknut::SystemReg::NZCV, X0);
|
||||||
|
|
||||||
// Load all vector registers.
|
// Load all vector registers.
|
||||||
static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
|
static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
|
||||||
for (int i = 0; i <= 30; i += 2) {
|
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.
|
// Load all general-purpose registers except X30.
|
||||||
for (int i = 0; i <= 28; i += 2) {
|
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.
|
// Reload our return X30 from the stack and return.
|
||||||
// The patch code will reload the guest X30 for us.
|
// The patch code will reload the guest X30 for us.
|
||||||
cg.LDR(X30, SP, 8);
|
c.LDR(X30, SP, 8);
|
||||||
cg.RET();
|
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.
|
// 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
|
// SP contains the guest X30, so save our X30 to SP + 8, since we have allocated 16 bytes of
|
||||||
// stack.
|
// stack.
|
||||||
cg.STR(X30, SP, 8);
|
c.STR(X30, SP, 8);
|
||||||
cg.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
|
c.MRS(X30, oaknut::SystemReg::TPIDR_EL0);
|
||||||
cg.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
|
c.LDR(X30, X30, offsetof(NativeExecutionParameters, native_context));
|
||||||
|
|
||||||
// Store all general-purpose registers except X30.
|
// Store all general-purpose registers except X30.
|
||||||
for (int i = 0; i <= 28; i += 2) {
|
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.
|
// Store all vector registers.
|
||||||
static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
|
static constexpr size_t VEC_OFF = offsetof(GuestContext, vector_registers);
|
||||||
for (int i = 0; i <= 30; i += 2) {
|
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.
|
// Store guest system registers, X30 and SP, using X0 as a scratch register.
|
||||||
cg.STR(X0, SP, PRE_INDEXED, -16);
|
c.STR(X0, SP, PRE_INDEXED, -16);
|
||||||
cg.LDR(X0, SP, 16);
|
c.LDR(X0, SP, 16);
|
||||||
cg.STR(X0, X30, 8 * 30);
|
c.STR(X0, X30, 8 * 30);
|
||||||
cg.ADD(X0, SP, 32);
|
c.ADD(X0, SP, 32);
|
||||||
cg.STR(X0, X30, offsetof(GuestContext, sp));
|
c.STR(X0, X30, offsetof(GuestContext, sp));
|
||||||
cg.MRS(X0, oaknut::SystemReg::FPSR);
|
c.MRS(X0, oaknut::SystemReg::FPSR);
|
||||||
cg.STR(W0, X30, offsetof(GuestContext, fpsr));
|
c.STR(W0, X30, offsetof(GuestContext, fpsr));
|
||||||
cg.MRS(X0, oaknut::SystemReg::FPCR);
|
c.MRS(X0, oaknut::SystemReg::FPCR);
|
||||||
cg.STR(W0, X30, offsetof(GuestContext, fpcr));
|
c.STR(W0, X30, offsetof(GuestContext, fpcr));
|
||||||
cg.MRS(X0, oaknut::SystemReg::NZCV);
|
c.MRS(X0, oaknut::SystemReg::NZCV);
|
||||||
cg.STR(W0, X30, offsetof(GuestContext, nzcv));
|
c.STR(W0, X30, offsetof(GuestContext, nzcv));
|
||||||
cg.LDR(X0, SP, POST_INDEXED, 16);
|
c.LDR(X0, SP, POST_INDEXED, 16);
|
||||||
|
|
||||||
// Reload our return X30 from the stack, and return.
|
// Reload our return X30 from the stack, and return.
|
||||||
cg.LDR(X30, SP, 8);
|
c.LDR(X30, SP, 8);
|
||||||
cg.RET();
|
c.RET();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id, oaknut::VectorCodeGenerator& cg, oaknut::Label& save_ctx, oaknut::Label& load_ctx) {
|
void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
|
||||||
// Determine if we're writing to the pre-patch buffer
|
|
||||||
const bool is_pre = (&cg == &c_pre);
|
|
||||||
|
|
||||||
// We are about to start saving state, so we need to lock the context.
|
// 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.
|
// 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.
|
// This will save all registers except PC, but we know PC at patch time.
|
||||||
cg.STR(X30, SP, PRE_INDEXED, -16);
|
c.STR(X30, SP, PRE_INDEXED, -16);
|
||||||
cg.BL(save_ctx);
|
c.BL(m_save_context);
|
||||||
cg.LDR(X30, SP, POST_INDEXED, 16);
|
c.LDR(X30, SP, POST_INDEXED, 16);
|
||||||
|
|
||||||
// Now that we've saved all registers, we can use any registers as scratch.
|
// 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.
|
// Store PC + 4 to arm interface, since we know the instruction offset from the entry point.
|
||||||
oaknut::Label pc_after_svc;
|
oaknut::Label pc_after_svc;
|
||||||
cg.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
|
c.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
|
||||||
cg.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
|
c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
|
||||||
cg.LDR(X2, pc_after_svc);
|
c.LDR(X2, pc_after_svc);
|
||||||
cg.STR(X2, X1, offsetof(GuestContext, pc));
|
c.STR(X2, X1, offsetof(GuestContext, pc));
|
||||||
|
|
||||||
// Store SVC number to execute when we return
|
// Store SVC number to execute when we return
|
||||||
cg.MOV(X2, svc_id);
|
c.MOV(X2, svc_id);
|
||||||
cg.STR(W2, X1, offsetof(GuestContext, svc));
|
c.STR(W2, X1, offsetof(GuestContext, svc));
|
||||||
|
|
||||||
// We are calling a SVC. Clear esr_el1 and return it.
|
// We are calling a SVC. Clear esr_el1 and return it.
|
||||||
static_assert(std::is_same_v<std::underlying_type_t<HaltReason>, u64>);
|
static_assert(std::is_same_v<std::underlying_type_t<HaltReason>, u64>);
|
||||||
oaknut::Label retry;
|
oaknut::Label retry;
|
||||||
cg.ADD(X2, X1, offsetof(GuestContext, esr_el1));
|
c.ADD(X2, X1, offsetof(GuestContext, esr_el1));
|
||||||
cg.l(retry);
|
c.l(retry);
|
||||||
cg.LDAXR(X0, X2);
|
c.LDAXR(X0, X2);
|
||||||
cg.STLXR(W3, XZR, X2);
|
c.STLXR(W3, XZR, X2);
|
||||||
cg.CBNZ(W3, retry);
|
c.CBNZ(W3, retry);
|
||||||
|
|
||||||
// Add "calling SVC" flag. Since this is X0, this is now our return value.
|
// 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.
|
// Offset the GuestContext pointer to the HostContext member.
|
||||||
// STP has limited range of [-512, 504] which we can't reach otherwise
|
// 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.
|
// 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.
|
// Reload host TPIDR_EL0 and SP.
|
||||||
static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0));
|
static_assert(offsetof(HostContext, host_sp) + 8 == offsetof(HostContext, host_tpidr_el0));
|
||||||
cg.LDP(X2, X3, X1, offsetof(HostContext, host_sp));
|
c.LDP(X2, X3, X1, offsetof(HostContext, host_sp));
|
||||||
cg.MOV(SP, X2);
|
c.MOV(SP, X2);
|
||||||
cg.MSR(oaknut::SystemReg::TPIDR_EL0, X3);
|
c.MSR(oaknut::SystemReg::TPIDR_EL0, X3);
|
||||||
|
|
||||||
// Load callee-saved host registers and return to host.
|
// 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_REGS_OFF = offsetof(HostContext, host_saved_regs);
|
||||||
static constexpr size_t HOST_VREGS_OFF = offsetof(HostContext, host_saved_vregs);
|
static constexpr size_t HOST_VREGS_OFF = offsetof(HostContext, host_saved_vregs);
|
||||||
cg.LDP(X19, X20, X1, HOST_REGS_OFF);
|
c.LDP(X19, X20, X1, HOST_REGS_OFF);
|
||||||
cg.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64));
|
c.LDP(X21, X22, X1, HOST_REGS_OFF + 2 * sizeof(u64));
|
||||||
cg.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64));
|
c.LDP(X23, X24, X1, HOST_REGS_OFF + 4 * sizeof(u64));
|
||||||
cg.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64));
|
c.LDP(X25, X26, X1, HOST_REGS_OFF + 6 * sizeof(u64));
|
||||||
cg.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64));
|
c.LDP(X27, X28, X1, HOST_REGS_OFF + 8 * sizeof(u64));
|
||||||
cg.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64));
|
c.LDP(X29, X30, X1, HOST_REGS_OFF + 10 * sizeof(u64));
|
||||||
cg.LDP(Q8, Q9, X1, HOST_VREGS_OFF);
|
c.LDP(Q8, Q9, X1, HOST_VREGS_OFF);
|
||||||
cg.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128));
|
c.LDP(Q10, Q11, X1, HOST_VREGS_OFF + 2 * sizeof(u128));
|
||||||
cg.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128));
|
c.LDP(Q12, Q13, X1, HOST_VREGS_OFF + 4 * sizeof(u128));
|
||||||
cg.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128));
|
c.LDP(Q14, Q15, X1, HOST_VREGS_OFF + 6 * sizeof(u128));
|
||||||
cg.RET();
|
c.RET();
|
||||||
|
|
||||||
// Write the post-SVC trampoline address, which will jump back to the guest after restoring its
|
// Write the post-SVC trampoline address, which will jump back to the guest after restoring its
|
||||||
// state.
|
// state.
|
||||||
if (is_pre) {
|
curr_patch->m_trampolines.push_back({c.offset(), module_dest});
|
||||||
curr_patch->m_trampolines_pre.push_back({cg.offset(), module_dest});
|
|
||||||
} else {
|
|
||||||
curr_patch->m_trampolines.push_back({cg.offset(), module_dest});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host called this location. Save the return address so we can
|
// Host called this location. Save the return address so we can
|
||||||
// unwind the stack properly when jumping back.
|
// unwind the stack properly when jumping back.
|
||||||
cg.MRS(X2, oaknut::SystemReg::TPIDR_EL0);
|
c.MRS(X2, oaknut::SystemReg::TPIDR_EL0);
|
||||||
cg.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context));
|
c.LDR(X2, X2, offsetof(NativeExecutionParameters, native_context));
|
||||||
cg.ADD(X0, X2, offsetof(GuestContext, host_ctx));
|
c.ADD(X0, X2, offsetof(GuestContext, host_ctx));
|
||||||
cg.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64));
|
c.STR(X30, X0, offsetof(HostContext, host_saved_regs) + 11 * sizeof(u64));
|
||||||
|
|
||||||
// Reload all guest registers except X30 and PC.
|
// Reload all guest registers except X30 and PC.
|
||||||
// The function also expects 16 bytes of stack already allocated.
|
// The function also expects 16 bytes of stack already allocated.
|
||||||
cg.STR(X30, SP, PRE_INDEXED, -16);
|
c.STR(X30, SP, PRE_INDEXED, -16);
|
||||||
cg.BL(load_ctx);
|
c.BL(m_load_context);
|
||||||
cg.LDR(X30, SP, POST_INDEXED, 16);
|
c.LDR(X30, SP, POST_INDEXED, 16);
|
||||||
|
|
||||||
// Use X1 as a scratch register to restore X30.
|
// Use X1 as a scratch register to restore X30.
|
||||||
cg.STR(X1, SP, PRE_INDEXED, -16);
|
c.STR(X1, SP, PRE_INDEXED, -16);
|
||||||
cg.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
|
c.MRS(X1, oaknut::SystemReg::TPIDR_EL0);
|
||||||
cg.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
|
c.LDR(X1, X1, offsetof(NativeExecutionParameters, native_context));
|
||||||
cg.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30);
|
c.LDR(X30, X1, offsetof(GuestContext, cpu_registers) + sizeof(u64) * 30);
|
||||||
cg.LDR(X1, SP, POST_INDEXED, 16);
|
c.LDR(X1, SP, POST_INDEXED, 16);
|
||||||
|
|
||||||
// Unlock the context.
|
// Unlock the context.
|
||||||
this->UnlockContext(cg);
|
this->UnlockContext();
|
||||||
|
|
||||||
// Jump back to the instruction after the emulated SVC.
|
// Jump back to the instruction after the emulated SVC.
|
||||||
if (&cg == &c_pre)
|
this->BranchToModule(module_dest);
|
||||||
this->BranchToModulePre(module_dest);
|
|
||||||
else
|
|
||||||
this->BranchToModule(module_dest);
|
|
||||||
|
|
||||||
// Store PC after call.
|
// Store PC after call.
|
||||||
cg.l(pc_after_svc);
|
c.l(pc_after_svc);
|
||||||
if (&cg == &c_pre)
|
this->WriteModulePc(module_dest);
|
||||||
this->WriteModulePcPre(module_dest);
|
|
||||||
else
|
|
||||||
this->WriteModulePc(module_dest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Patcher::WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg,
|
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.
|
// 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) {
|
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 {
|
} 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.
|
// Jump back to the instruction after the emulated MRS.
|
||||||
if (&cg == &c_pre)
|
this->BranchToModule(module_dest);
|
||||||
this->BranchToModulePre(module_dest);
|
|
||||||
else
|
|
||||||
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;
|
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.
|
// Save guest value to NativeExecutionParameters::tpidr_el0.
|
||||||
cg.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0);
|
c.MRS(scratch_reg, oaknut::SystemReg::TPIDR_EL0);
|
||||||
cg.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0));
|
c.STR(src_reg, scratch_reg, offsetof(NativeExecutionParameters, tpidr_el0));
|
||||||
|
|
||||||
// Restore scratch register.
|
// 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.
|
// Jump back to the instruction after the emulated MSR.
|
||||||
if (&cg == &c_pre)
|
this->BranchToModule(module_dest);
|
||||||
this->BranchToModulePre(module_dest);
|
|
||||||
else
|
|
||||||
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{};
|
static Common::Arm64::NativeClock clock{};
|
||||||
const auto factor = clock.GetGuestCNTFRQFactor();
|
const auto factor = clock.GetGuestCNTFRQFactor();
|
||||||
const auto raw_factor = std::bit_cast<std::array<u64, 2>>(factor);
|
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;
|
oaknut::Label factorhi;
|
||||||
|
|
||||||
// Save scratches.
|
// Save scratches.
|
||||||
cg.STP(scratch0, scratch1, SP, PRE_INDEXED, -16);
|
c.STP(scratch0, scratch1, SP, PRE_INDEXED, -16);
|
||||||
|
|
||||||
// Load counter value.
|
// Load counter value.
|
||||||
cg.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0);
|
c.MRS(dest_reg, oaknut::SystemReg::CNTVCT_EL0);
|
||||||
|
|
||||||
// Load scaling factor.
|
// Load scaling factor.
|
||||||
cg.LDR(scratch0, factorlo);
|
c.LDR(scratch0, factorlo);
|
||||||
cg.LDR(scratch1, factorhi);
|
c.LDR(scratch1, factorhi);
|
||||||
|
|
||||||
// Multiply low bits and get result.
|
// 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.
|
// 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.
|
// 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.
|
// Jump back to the instruction after the emulated MRS.
|
||||||
if (&cg == &c_pre)
|
this->BranchToModule(module_dest);
|
||||||
this->BranchToModulePre(module_dest);
|
|
||||||
else
|
|
||||||
this->BranchToModule(module_dest);
|
|
||||||
|
|
||||||
// Scaling factor constant values.
|
// Scaling factor constant values.
|
||||||
cg.l(factorlo);
|
c.l(factorlo);
|
||||||
cg.dx(raw_factor[0]);
|
c.dx(raw_factor[0]);
|
||||||
cg.l(factorhi);
|
c.l(factorhi);
|
||||||
cg.dx(raw_factor[1]);
|
c.dx(raw_factor[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Patcher::LockContext(oaknut::VectorCodeGenerator& cg) {
|
void Patcher::LockContext() {
|
||||||
oaknut::Label retry;
|
oaknut::Label retry;
|
||||||
|
|
||||||
// Save scratches.
|
// Save scratches.
|
||||||
cg.STP(X0, X1, SP, PRE_INDEXED, -16);
|
c.STP(X0, X1, SP, PRE_INDEXED, -16);
|
||||||
|
|
||||||
// Reload lock pointer.
|
// Reload lock pointer.
|
||||||
cg.l(retry);
|
c.l(retry);
|
||||||
cg.CLREX();
|
c.CLREX();
|
||||||
cg.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
|
c.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
|
||||||
cg.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
|
c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
|
||||||
|
|
||||||
static_assert(SpinLockLocked == 0);
|
static_assert(SpinLockLocked == 0);
|
||||||
|
|
||||||
// Load-linked with acquire ordering.
|
// Load-linked with acquire ordering.
|
||||||
cg.LDAXR(W1, X0);
|
c.LDAXR(W1, X0);
|
||||||
|
|
||||||
// If the value was SpinLockLocked, clear monitor and retry.
|
// If the value was SpinLockLocked, clear monitor and retry.
|
||||||
cg.CBZ(W1, retry);
|
c.CBZ(W1, retry);
|
||||||
|
|
||||||
// Store-conditional SpinLockLocked with relaxed ordering.
|
// Store-conditional SpinLockLocked with relaxed ordering.
|
||||||
cg.STXR(W1, WZR, X0);
|
c.STXR(W1, WZR, X0);
|
||||||
|
|
||||||
// If we failed to store, retry.
|
// If we failed to store, retry.
|
||||||
cg.CBNZ(W1, retry);
|
c.CBNZ(W1, retry);
|
||||||
|
|
||||||
// We succeeded! Reload scratches.
|
// 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.
|
// Save scratches.
|
||||||
cg.STP(X0, X1, SP, PRE_INDEXED, -16);
|
c.STP(X0, X1, SP, PRE_INDEXED, -16);
|
||||||
|
|
||||||
// Load lock pointer.
|
// Load lock pointer.
|
||||||
cg.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
|
c.MRS(X0, oaknut::SystemReg::TPIDR_EL0);
|
||||||
cg.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
|
c.ADD(X0, X0, offsetof(NativeExecutionParameters, lock));
|
||||||
|
|
||||||
// Load SpinLockUnlocked.
|
// Load SpinLockUnlocked.
|
||||||
cg.MOV(W1, SpinLockUnlocked);
|
c.MOV(W1, SpinLockUnlocked);
|
||||||
|
|
||||||
// Store value with release ordering.
|
// Store value with release ordering.
|
||||||
cg.STLR(W1, X0);
|
c.STLR(W1, X0);
|
||||||
|
|
||||||
// Load scratches.
|
// Load scratches.
|
||||||
cg.LDP(X0, X1, SP, POST_INDEXED, 16);
|
c.LDP(X0, X1, SP, POST_INDEXED, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core::NCE
|
} // namespace Core::NCE
|
||||||
|
|||||||
+10
-44
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -41,7 +41,6 @@ enum class PatchMode : u32 {
|
|||||||
None,
|
None,
|
||||||
PreText, ///< Patch section is inserted before .text
|
PreText, ///< Patch section is inserted before .text
|
||||||
PostData, ///< Patch section is inserted after .data
|
PostData, ///< Patch section is inserted after .data
|
||||||
Split, ///< Patch sections are inserted before .text and after .data
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using ModuleTextAddress = u64;
|
using ModuleTextAddress = u64;
|
||||||
@@ -64,7 +63,6 @@ public:
|
|||||||
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
|
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
|
||||||
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
|
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
|
||||||
size_t GetSectionSize() const noexcept;
|
size_t GetSectionSize() const noexcept;
|
||||||
size_t GetPreSectionSize() const noexcept;
|
|
||||||
|
|
||||||
[[nodiscard]] PatchMode GetPatchMode() const noexcept {
|
[[nodiscard]] PatchMode GetPatchMode() const noexcept {
|
||||||
return mode;
|
return mode;
|
||||||
@@ -78,25 +76,15 @@ private:
|
|||||||
uintptr_t module_offset;
|
uintptr_t module_offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Core implementations with explicit code generator
|
void WriteLoadContext();
|
||||||
void WriteLoadContext(oaknut::VectorCodeGenerator& code);
|
void WriteSaveContext();
|
||||||
void WriteSaveContext(oaknut::VectorCodeGenerator& code);
|
void LockContext();
|
||||||
void LockContext(oaknut::VectorCodeGenerator& code);
|
void UnlockContext();
|
||||||
void UnlockContext(oaknut::VectorCodeGenerator& code);
|
void WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id);
|
||||||
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,
|
||||||
void WriteMrsHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::SystemReg src_reg, oaknut::VectorCodeGenerator& code);
|
oaknut::SystemReg src_reg);
|
||||||
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, oaknut::VectorCodeGenerator& code);
|
void WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg);
|
||||||
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& code);
|
void WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg);
|
||||||
|
|
||||||
// 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); }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t CACHE_SIZE = 16384; // Cache size for patch entries
|
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) {
|
void BranchToModule(uintptr_t module_dest) {
|
||||||
curr_patch->m_branch_to_module_relocations.push_back({c.offset(), module_dest});
|
curr_patch->m_branch_to_module_relocations.push_back({c.offset(), module_dest});
|
||||||
c.dw(0);
|
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) {
|
void WriteModulePc(uintptr_t module_dest) {
|
||||||
curr_patch->m_write_module_pc_relocations.push_back({c.offset(), module_dest});
|
curr_patch->m_write_module_pc_relocations.push_back({c.offset(), module_dest});
|
||||||
c.dx(0);
|
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:
|
private:
|
||||||
// List of patch instructions we have generated.
|
// List of patch instructions we have generated.
|
||||||
std::vector<u32> m_patch_instructions{};
|
std::vector<u32> m_patch_instructions{};
|
||||||
std::vector<u32> m_patch_instructions_pre{};
|
|
||||||
|
|
||||||
// Relocation type for relative branch from module to patch.
|
// Relocation type for relative branch from module to patch.
|
||||||
struct Relocation {
|
struct Relocation {
|
||||||
@@ -160,22 +133,15 @@ private:
|
|||||||
|
|
||||||
struct ModulePatch {
|
struct ModulePatch {
|
||||||
std::vector<Trampoline> m_trampolines;
|
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_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{};
|
||||||
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{};
|
||||||
std::vector<Relocation> m_write_module_pc_relocations_pre{};
|
|
||||||
std::vector<ModuleTextAddress> m_exclusives{};
|
std::vector<ModuleTextAddress> m_exclusives{};
|
||||||
};
|
};
|
||||||
|
|
||||||
oaknut::VectorCodeGenerator c;
|
oaknut::VectorCodeGenerator c;
|
||||||
oaknut::VectorCodeGenerator c_pre;
|
|
||||||
oaknut::Label m_save_context{};
|
oaknut::Label m_save_context{};
|
||||||
oaknut::Label m_load_context{};
|
oaknut::Label m_load_context{};
|
||||||
oaknut::Label m_save_context_pre{};
|
|
||||||
oaknut::Label m_load_context_pre{};
|
|
||||||
PatchMode mode{PatchMode::None};
|
PatchMode mode{PatchMode::None};
|
||||||
size_t total_program_size{};
|
size_t total_program_size{};
|
||||||
size_t m_relocate_module_index{};
|
size_t m_relocate_module_index{};
|
||||||
|
|||||||
+5
-5
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -142,7 +142,6 @@ struct System::Impl {
|
|||||||
|
|
||||||
void ReinitializeIfNecessary(System& system) {
|
void ReinitializeIfNecessary(System& system) {
|
||||||
const bool must_reinitialize =
|
const bool must_reinitialize =
|
||||||
!device_memory.has_value() ||
|
|
||||||
is_multicore != Settings::values.use_multi_core.GetValue() ||
|
is_multicore != Settings::values.use_multi_core.GetValue() ||
|
||||||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
|
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
|
||||||
Settings::MemoryLayout::Memory_4Gb);
|
Settings::MemoryLayout::Memory_4Gb);
|
||||||
@@ -371,8 +370,7 @@ struct System::Impl {
|
|||||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||||
}
|
}
|
||||||
|
|
||||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer(),
|
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||||
Settings::values.enable_global_overrides);
|
|
||||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
Network::GameInfo game_info;
|
Network::GameInfo game_info;
|
||||||
game_info.name = name;
|
game_info.name = name;
|
||||||
@@ -386,6 +384,9 @@ struct System::Impl {
|
|||||||
void ShutdownMainProcess() {
|
void ShutdownMainProcess() {
|
||||||
SetShuttingDown(true);
|
SetShuttingDown(true);
|
||||||
|
|
||||||
|
// Reset per-game flags
|
||||||
|
Settings::values.use_squashed_iterated_blend = false;
|
||||||
|
|
||||||
is_powered_on = false;
|
is_powered_on = false;
|
||||||
exit_locked = false;
|
exit_locked = false;
|
||||||
exit_requested = false;
|
exit_requested = false;
|
||||||
@@ -413,7 +414,6 @@ struct System::Impl {
|
|||||||
cpu_manager.Shutdown();
|
cpu_manager.Shutdown();
|
||||||
debugger.reset();
|
debugger.reset();
|
||||||
kernel.Shutdown();
|
kernel.Shutdown();
|
||||||
device_memory.reset();
|
|
||||||
stop_event = {};
|
stop_event = {};
|
||||||
Network::RestartSocketOperations();
|
Network::RestartSocketOperations();
|
||||||
|
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
@@ -137,127 +137,12 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
|||||||
return exefs;
|
return exefs;
|
||||||
|
|
||||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||||
|
const auto update_disabled =
|
||||||
bool update_disabled = true;
|
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Game Updates
|
// Game Updates
|
||||||
std::unique_ptr<NCA> update = nullptr;
|
const auto update_tid = GetUpdateTitleID(title_id);
|
||||||
|
const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
|
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) {
|
||||||
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
|
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
|
||||||
@@ -562,103 +447,21 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs
|
|||||||
|
|
||||||
// Game Updates
|
// Game Updates
|
||||||
const auto update_tid = GetUpdateTitleID(title_id);
|
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];
|
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||||
|
const auto update_disabled =
|
||||||
bool update_disabled = true;
|
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!update_disabled && update_raw != nullptr && base_nca != nullptr) {
|
if (!update_disabled && update_raw != nullptr && base_nca != nullptr) {
|
||||||
const auto new_nca = std::make_shared<NCA>(update_raw, base_nca);
|
const auto new_nca = std::make_shared<NCA>(update_raw, base_nca);
|
||||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||||
new_nca->GetRomFS() != nullptr) {
|
new_nca->GetRomFS() != nullptr) {
|
||||||
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
|
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
|
||||||
enabled_version.has_value() ? FormatTitleVersion(*enabled_version) :
|
|
||||||
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
|
FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)));
|
||||||
romfs = new_nca->GetRomFS();
|
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) {
|
} else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) {
|
||||||
const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca);
|
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
|
// Game Updates
|
||||||
const auto update_tid = GetUpdateTitleID(title_id);
|
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 (nacp != nullptr) {
|
||||||
|
update_patch.version = nacp->GetVersionString();
|
||||||
if (content_union) {
|
out.push_back(update_patch);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
PatchManager update{update_tid, fs_controller, content_provider};
|
if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) {
|
||||||
const auto metadata = update.GetControlMetadata();
|
const auto meta_ver = content_provider.GetEntryVersion(update_tid);
|
||||||
const auto& nacp = metadata.first;
|
if (meta_ver.value_or(0) == 0) {
|
||||||
|
out.push_back(update_patch);
|
||||||
bool update_disabled =
|
} else {
|
||||||
std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend();
|
update_patch.version = FormatTitleVersion(*meta_ver);
|
||||||
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;
|
|
||||||
out.push_back(update_patch);
|
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",
|
.version = "Cheats",
|
||||||
.type = PatchType::Mod,
|
.type = PatchType::Mod,
|
||||||
.program_id = title_id,
|
.program_id = title_id,
|
||||||
.title_id = title_id,
|
.title_id = title_id
|
||||||
.source = PatchSource::Unknown
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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(),
|
} else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(),
|
||||||
file->GetName()) != EXEFS_FILE_NAMES.end()) {
|
file->GetName()) != EXEFS_FILE_NAMES.end()) {
|
||||||
layeredfs = true;
|
layeredfs = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ips)
|
if (ips)
|
||||||
@@ -922,8 +579,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||||||
.version = types,
|
.version = types,
|
||||||
.type = PatchType::Mod,
|
.type = PatchType::Mod,
|
||||||
.program_id = title_id,
|
.program_id = title_id,
|
||||||
.title_id = title_id,
|
.title_id = title_id});
|
||||||
.source = PatchSource::Unknown});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -937,7 +593,7 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||||||
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs")) ||
|
if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfs")) ||
|
||||||
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) {
|
IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "romfslite"))) {
|
||||||
AppendCommaIfNotEmpty(types, "LayeredFS");
|
AppendCommaIfNotEmpty(types, "LayeredFS");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!types.empty()) {
|
if (!types.empty()) {
|
||||||
const auto mod_disabled =
|
const auto mod_disabled =
|
||||||
@@ -947,44 +603,21 @@ std::vector<Patch> PatchManager::GetPatches(VirtualFile update_raw) const {
|
|||||||
.version = types,
|
.version = types,
|
||||||
.type = PatchType::Mod,
|
.type = PatchType::Mod,
|
||||||
.program_id = title_id,
|
.program_id = title_id,
|
||||||
.title_id = title_id,
|
.title_id = title_id});
|
||||||
.source = PatchSource::Unknown});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DLC
|
// DLC
|
||||||
const auto dlc_entries =
|
const auto dlc_entries =
|
||||||
content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
|
content_provider.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
|
||||||
|
|
||||||
std::vector<ContentProviderEntry> dlc_match;
|
std::vector<ContentProviderEntry> dlc_match;
|
||||||
dlc_match.reserve(dlc_entries.size());
|
dlc_match.reserve(dlc_entries.size());
|
||||||
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||||
[this](const ContentProviderEntry& entry) {
|
[this](const ContentProviderEntry& entry) {
|
||||||
const auto base_tid = GetBaseTitleID(entry.title_id);
|
return GetBaseTitleID(entry.title_id) == title_id &&
|
||||||
const bool matches_base = base_tid == title_id;
|
content_provider.GetEntry(entry)->GetStatus() ==
|
||||||
|
Loader::ResultStatus::Success;
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!dlc_match.empty()) {
|
if (!dlc_match.empty()) {
|
||||||
// Ensure sorted so DLC IDs show in order.
|
// Ensure sorted so DLC IDs show in order.
|
||||||
std::sort(dlc_match.begin(), dlc_match.end());
|
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),
|
.version = std::move(list),
|
||||||
.type = PatchType::DLC,
|
.type = PatchType::DLC,
|
||||||
.program_id = title_id,
|
.program_id = title_id,
|
||||||
.title_id = dlc_match.back().title_id,
|
.title_id = dlc_match.back().title_id});
|
||||||
.source = PatchSource::Unknown});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@@ -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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -31,13 +28,6 @@ class NACP;
|
|||||||
|
|
||||||
enum class PatchType { Update, DLC, Mod };
|
enum class PatchType { Update, DLC, Mod };
|
||||||
|
|
||||||
enum class PatchSource {
|
|
||||||
Unknown,
|
|
||||||
NAND,
|
|
||||||
External,
|
|
||||||
Packed,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Patch {
|
struct Patch {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
std::string name;
|
std::string name;
|
||||||
@@ -45,8 +35,6 @@ struct Patch {
|
|||||||
PatchType type;
|
PatchType type;
|
||||||
u64 program_id;
|
u64 program_id;
|
||||||
u64 title_id;
|
u64 title_id;
|
||||||
PatchSource source;
|
|
||||||
u32 numeric_version{0};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A centralized class to manage patches to games.
|
// A centralized class to manage patches to games.
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
@@ -13,15 +13,12 @@
|
|||||||
#include "common/hex_util.h"
|
#include "common/hex_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "core/crypto/key_manager.h"
|
#include "core/crypto/key_manager.h"
|
||||||
#include "core/file_sys/card_image.h"
|
#include "core/file_sys/card_image.h"
|
||||||
#include "core/file_sys/common_funcs.h"
|
#include "core/file_sys/common_funcs.h"
|
||||||
#include "core/file_sys/content_archive.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/nca_metadata.h"
|
||||||
#include "core/file_sys/registered_cache.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/submission_package.h"
|
||||||
#include "core/file_sys/vfs/vfs_concat.h"
|
#include "core/file_sys/vfs/vfs_concat.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
@@ -977,22 +974,6 @@ std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry(
|
|||||||
return iter->first;
|
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;
|
ManualContentProvider::~ManualContentProvider() = default;
|
||||||
|
|
||||||
void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
|
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);
|
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() {
|
void ManualContentProvider::ClearAllEntries() {
|
||||||
entries.clear();
|
entries.clear();
|
||||||
multi_version_entries.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ManualContentProvider::Refresh() {}
|
void ManualContentProvider::Refresh() {}
|
||||||
@@ -1098,459 +1036,4 @@ std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter(
|
|||||||
out.erase(std::unique(out.begin(), out.end()), out.end());
|
out.erase(std::unique(out.begin(), out.end()), out.end());
|
||||||
return out;
|
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
|
} // namespace FileSys
|
||||||
|
|||||||
@@ -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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -17,8 +14,7 @@
|
|||||||
#include "core/file_sys/vfs/vfs.h"
|
#include "core/file_sys/vfs/vfs.h"
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
class ExternalContentProvider;
|
class CNMT;
|
||||||
class CNMT;
|
|
||||||
class NCA;
|
class NCA;
|
||||||
class NSP;
|
class NSP;
|
||||||
class XCI;
|
class XCI;
|
||||||
@@ -52,13 +48,6 @@ struct ContentProviderEntry {
|
|||||||
std::string DebugInfo() const;
|
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) {
|
constexpr u64 GetUpdateTitleID(u64 base_title_id) {
|
||||||
return base_title_id | 0x800;
|
return base_title_id | 0x800;
|
||||||
}
|
}
|
||||||
@@ -219,7 +208,6 @@ enum class ContentProviderUnionSlot {
|
|||||||
UserNAND, ///< User NAND
|
UserNAND, ///< User NAND
|
||||||
SDMC, ///< SD Card
|
SDMC, ///< SD Card
|
||||||
FrontendManual, ///< Frontend-defined game list or similar
|
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.
|
// 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<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||||
std::optional<u64> title_id) const override;
|
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::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> ListEntriesFilterOrigin(
|
||||||
std::optional<ContentProviderUnionSlot> origin = {},
|
std::optional<ContentProviderUnionSlot> origin = {},
|
||||||
std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
|
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,
|
void AddEntry(TitleType title_type, ContentRecordType content_type, u64 title_id,
|
||||||
VirtualFile file);
|
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 ClearAllEntries();
|
||||||
|
|
||||||
void Refresh() override;
|
void Refresh() override;
|
||||||
@@ -275,46 +258,8 @@ public:
|
|||||||
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
|
||||||
std::optional<u64> title_id) const override;
|
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:
|
private:
|
||||||
std::map<std::tuple<TitleType, ContentRecordType, u64>, VirtualFile> entries;
|
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
|
} // namespace FileSys
|
||||||
|
|||||||
@@ -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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// 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}] =
|
ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] =
|
||||||
std::move(next_nca);
|
std::move(next_nca);
|
||||||
} else {
|
} 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);
|
ncas[cnmt.GetTitleID()][{cnmt.GetType(), rec.type}] = std::move(next_nca);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,615 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#include "core/game_overrides.h"
|
|
||||||
#include "core/game_settings.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <cctype>
|
|
||||||
#include <charconv>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include "common/fs/path_util.h"
|
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "common/settings.h"
|
|
||||||
#include "video_core/renderer_base.h"
|
|
||||||
|
|
||||||
namespace Core::GameOverrides {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::string Trim(std::string_view str) {
|
|
||||||
const auto start = str.find_first_not_of(" \t\r\n");
|
|
||||||
if (start == std::string_view::npos) return "";
|
|
||||||
const auto end = str.find_last_not_of(" \t\r\n");
|
|
||||||
return std::string(str.substr(start, end - start + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ToLower(std::string str) {
|
|
||||||
std::transform(str.begin(), str.end(), str.begin(),
|
|
||||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ToBool(const std::string& value) {
|
|
||||||
return value == "true" || value == "1" || value == "yes" || value == "on";
|
|
||||||
}
|
|
||||||
|
|
||||||
GameSettings::OS DetectOS() {
|
|
||||||
#if defined(_WIN32)
|
|
||||||
return GameSettings::OS::Windows;
|
|
||||||
#elif defined(__FIREOS__)
|
|
||||||
return GameSettings::OS::FireOS;
|
|
||||||
#elif defined(__ANDROID__)
|
|
||||||
return GameSettings::OS::Android;
|
|
||||||
#elif defined(__OHOS__)
|
|
||||||
return GameSettings::OS::HarmonyOS;
|
|
||||||
#elif defined(__HAIKU__)
|
|
||||||
return GameSettings::OS::HaikuOS;
|
|
||||||
#elif defined(__DragonFly__)
|
|
||||||
return GameSettings::OS::DragonFlyBSD;
|
|
||||||
#elif defined(__NetBSD__)
|
|
||||||
return GameSettings::OS::NetBSD;
|
|
||||||
#elif defined(__OpenBSD__)
|
|
||||||
return GameSettings::OS::OpenBSD;
|
|
||||||
#elif defined(_AIX)
|
|
||||||
return GameSettings::OS::AIX;
|
|
||||||
#elif defined(__managarm__)
|
|
||||||
return GameSettings::OS::Managarm;
|
|
||||||
#elif defined(__redox__)
|
|
||||||
return GameSettings::OS::RedoxOS;
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
return GameSettings::OS::MacOS;
|
|
||||||
#elif defined(__FreeBSD__)
|
|
||||||
return GameSettings::OS::FreeBSD;
|
|
||||||
#elif defined(__sun) && defined(__SVR4)
|
|
||||||
return GameSettings::OS::Solaris;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
return GameSettings::OS::Linux;
|
|
||||||
#else
|
|
||||||
return GameSettings::OS::Unknown;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
GameSettings::GPUVendor DetectGPUVendor(const std::string& vendor_string) {
|
|
||||||
std::string gpu = vendor_string;
|
|
||||||
std::transform(gpu.begin(), gpu.end(), gpu.begin(),
|
|
||||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
|
||||||
|
|
||||||
if (gpu.find("nvidia") != std::string::npos || gpu.find("geforce") != std::string::npos) {
|
|
||||||
return GameSettings::GPUVendor::Nvidia;
|
|
||||||
}
|
|
||||||
if (gpu.find("amd") != std::string::npos || gpu.find("radeon") != std::string::npos ||
|
|
||||||
gpu.find("radv") != std::string::npos) {
|
|
||||||
return GameSettings::GPUVendor::AMD;
|
|
||||||
}
|
|
||||||
if (gpu.find("intel") != std::string::npos) {
|
|
||||||
return GameSettings::GPUVendor::Intel;
|
|
||||||
}
|
|
||||||
if (gpu.find("apple") != std::string::npos || gpu.find("molten") != std::string::npos) {
|
|
||||||
return GameSettings::GPUVendor::Apple;
|
|
||||||
}
|
|
||||||
if (gpu.find("qualcomm") != std::string::npos || gpu.find("adreno") != std::string::npos ||
|
|
||||||
gpu.find("turnip") != std::string::npos) {
|
|
||||||
return GameSettings::GPUVendor::Qualcomm;
|
|
||||||
}
|
|
||||||
if (gpu.find("mali") != std::string::npos) {
|
|
||||||
return GameSettings::GPUVendor::ARM;
|
|
||||||
}
|
|
||||||
if (gpu.find("powervr") != std::string::npos || gpu.find("pvr") != std::string::npos) {
|
|
||||||
return GameSettings::GPUVendor::Imagination;
|
|
||||||
}
|
|
||||||
if (gpu.find("microsoft") != std::string::npos) {
|
|
||||||
return GameSettings::GPUVendor::Microsoft;
|
|
||||||
}
|
|
||||||
return GameSettings::GPUVendor::Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string OSToString(GameSettings::OS os) {
|
|
||||||
switch (os) {
|
|
||||||
case GameSettings::OS::Windows: return "windows";
|
|
||||||
case GameSettings::OS::Linux: return "linux";
|
|
||||||
case GameSettings::OS::MacOS: return "macos";
|
|
||||||
case GameSettings::OS::Android: return "android";
|
|
||||||
case GameSettings::OS::FireOS: return "fireos";
|
|
||||||
case GameSettings::OS::HarmonyOS: return "harmonyos";
|
|
||||||
case GameSettings::OS::FreeBSD: return "freebsd";
|
|
||||||
case GameSettings::OS::DragonFlyBSD: return "dragonflybsd";
|
|
||||||
case GameSettings::OS::NetBSD: return "netbsd";
|
|
||||||
case GameSettings::OS::OpenBSD: return "openbsd";
|
|
||||||
case GameSettings::OS::HaikuOS: return "haikuos";
|
|
||||||
case GameSettings::OS::AIX: return "aix";
|
|
||||||
case GameSettings::OS::Managarm: return "managarm";
|
|
||||||
case GameSettings::OS::RedoxOS: return "redoxos";
|
|
||||||
case GameSettings::OS::Solaris: return "solaris";
|
|
||||||
default: return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string VendorToString(GameSettings::GPUVendor vendor) {
|
|
||||||
switch (vendor) {
|
|
||||||
case GameSettings::GPUVendor::Nvidia: return "nvidia";
|
|
||||||
case GameSettings::GPUVendor::AMD: return "amd";
|
|
||||||
case GameSettings::GPUVendor::Intel: return "intel";
|
|
||||||
case GameSettings::GPUVendor::Apple: return "apple";
|
|
||||||
case GameSettings::GPUVendor::Qualcomm: return "qualcomm";
|
|
||||||
case GameSettings::GPUVendor::ARM: return "arm";
|
|
||||||
case GameSettings::GPUVendor::Imagination: return "imagination";
|
|
||||||
case GameSettings::GPUVendor::Microsoft: return "microsoft";
|
|
||||||
default: return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SET_OVERRIDE(setting, new_value) \
|
|
||||||
do { \
|
|
||||||
s.setting.SetGlobal(false); \
|
|
||||||
s.setting.SetValue(new_value); \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
void ApplySetting(const std::string& key, const std::string& value) {
|
|
||||||
const std::string k = ToLower(key);
|
|
||||||
const std::string v = ToLower(value);
|
|
||||||
auto& s = Settings::values;
|
|
||||||
|
|
||||||
if (k == "backend" || k == "renderer_backend") {
|
|
||||||
if (v == "vulkan") {
|
|
||||||
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::Vulkan);
|
|
||||||
} else if (v == "opengl" || v == "opengl_glsl") {
|
|
||||||
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::OpenGL_GLSL);
|
|
||||||
} else if (v == "opengl_glasm") {
|
|
||||||
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::OpenGL_GLASM);
|
|
||||||
} else if (v == "opengl_spirv") {
|
|
||||||
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::OpenGL_SPIRV);
|
|
||||||
} else if (v == "null") {
|
|
||||||
SET_OVERRIDE(renderer_backend, Settings::RendererBackend::Null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k == "vsync") {
|
|
||||||
SET_OVERRIDE(vsync_mode, ToBool(v) ? Settings::VSyncMode::Fifo : Settings::VSyncMode::Immediate);
|
|
||||||
}
|
|
||||||
else if (k == "vsync_mode") {
|
|
||||||
if (v == "immediate" || v == "off") {
|
|
||||||
SET_OVERRIDE(vsync_mode, Settings::VSyncMode::Immediate);
|
|
||||||
} else if (v == "fifo" || v == "on") {
|
|
||||||
SET_OVERRIDE(vsync_mode, Settings::VSyncMode::Fifo);
|
|
||||||
} else if (v == "fifo_relaxed") {
|
|
||||||
SET_OVERRIDE(vsync_mode, Settings::VSyncMode::FifoRelaxed);
|
|
||||||
} else if (v == "mailbox") {
|
|
||||||
SET_OVERRIDE(vsync_mode, Settings::VSyncMode::Mailbox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k == "gpu_accuracy" || k == "accuracy_level") {
|
|
||||||
if (v == "low" || v == "fast") {
|
|
||||||
SET_OVERRIDE(gpu_accuracy, Settings::GpuAccuracy::Low);
|
|
||||||
} else if (v == "medium" || v == "balanced" || v == "normal") {
|
|
||||||
SET_OVERRIDE(gpu_accuracy, Settings::GpuAccuracy::Medium);
|
|
||||||
} else if (v == "high" || v == "accurate") {
|
|
||||||
SET_OVERRIDE(gpu_accuracy, Settings::GpuAccuracy::High);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k == "cpu_accuracy") {
|
|
||||||
if (v == "auto") {
|
|
||||||
SET_OVERRIDE(cpu_accuracy, Settings::CpuAccuracy::Auto);
|
|
||||||
} else if (v == "accurate") {
|
|
||||||
SET_OVERRIDE(cpu_accuracy, Settings::CpuAccuracy::Accurate);
|
|
||||||
} else if (v == "unsafe") {
|
|
||||||
SET_OVERRIDE(cpu_accuracy, Settings::CpuAccuracy::Unsafe);
|
|
||||||
} else if (v == "paranoid") {
|
|
||||||
SET_OVERRIDE(cpu_accuracy, Settings::CpuAccuracy::Paranoid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k == "astc_decode_mode" || k == "accelerate_astc") {
|
|
||||||
if (v == "cpu") {
|
|
||||||
SET_OVERRIDE(accelerate_astc, Settings::AstcDecodeMode::Cpu);
|
|
||||||
} else if (v == "gpu") {
|
|
||||||
SET_OVERRIDE(accelerate_astc, Settings::AstcDecodeMode::Gpu);
|
|
||||||
} else if (v == "cpu_async") {
|
|
||||||
SET_OVERRIDE(accelerate_astc, Settings::AstcDecodeMode::CpuAsynchronous);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k == "fast_gpu_time") {
|
|
||||||
if (v == "off" || v == "false" || v == "0" || v == "normal") {
|
|
||||||
SET_OVERRIDE(fast_gpu_time, Settings::GpuOverclock::Normal);
|
|
||||||
} else if (v == "medium" || v == "true" || v == "1") {
|
|
||||||
SET_OVERRIDE(fast_gpu_time, Settings::GpuOverclock::Medium);
|
|
||||||
} else if (v == "high") {
|
|
||||||
SET_OVERRIDE(fast_gpu_time, Settings::GpuOverclock::High);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k == "resolution" || k == "resolution_setup") {
|
|
||||||
if (v == "0.25x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res1_4X);
|
|
||||||
} else if (v == "0.5x" || v == "half") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res1_2X);
|
|
||||||
} else if (v == "0.75x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res3_4X);
|
|
||||||
} else if (v == "1x" || v == "native") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res1X);
|
|
||||||
} else if (v == "1.25x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res5_4X);
|
|
||||||
} else if (v == "1.5x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res3_2X);
|
|
||||||
} else if (v == "2x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res2X);
|
|
||||||
} else if (v == "3x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res3X);
|
|
||||||
} else if (v == "4x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res4X);
|
|
||||||
} else if (v == "5x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res5X);
|
|
||||||
} else if (v == "6x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res6X);
|
|
||||||
} else if (v == "7x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res7X);
|
|
||||||
} else if (v == "8x") {
|
|
||||||
SET_OVERRIDE(resolution_setup, Settings::ResolutionSetup::Res8X);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k == "async_gpu" || k == "use_asynchronous_gpu_emulation") {
|
|
||||||
SET_OVERRIDE(use_asynchronous_gpu_emulation, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "reactive_flushing" || k == "use_reactive_flushing") {
|
|
||||||
SET_OVERRIDE(use_reactive_flushing, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "multicore" || k == "use_multi_core") {
|
|
||||||
SET_OVERRIDE(use_multi_core, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "async_shaders" || k == "use_asynchronous_shaders") {
|
|
||||||
SET_OVERRIDE(use_asynchronous_shaders, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "pipeline_cache" || k == "use_vulkan_driver_pipeline_cache") {
|
|
||||||
SET_OVERRIDE(use_vulkan_driver_pipeline_cache, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "compute_pipelines" || k == "enable_compute_pipelines") {
|
|
||||||
SET_OVERRIDE(enable_compute_pipelines, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "use_disk_shader_cache") {
|
|
||||||
SET_OVERRIDE(use_disk_shader_cache, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "barrier_feedback_loops") {
|
|
||||||
SET_OVERRIDE(barrier_feedback_loops, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "async_presentation") {
|
|
||||||
SET_OVERRIDE(async_presentation, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "use_squashed_iterated_blend") {
|
|
||||||
s.use_squashed_iterated_blend = ToBool(v);
|
|
||||||
}
|
|
||||||
else if (k == "smo" || k == "sync_memory_operations") {
|
|
||||||
SET_OVERRIDE(sync_memory_operations, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "nvdec" || k == "nvdec_emulation") {
|
|
||||||
if (v == "off" || v == "disabled") {
|
|
||||||
SET_OVERRIDE(nvdec_emulation, Settings::NvdecEmulation::Off);
|
|
||||||
} else if (v == "cpu") {
|
|
||||||
SET_OVERRIDE(nvdec_emulation, Settings::NvdecEmulation::Cpu);
|
|
||||||
} else if (v == "gpu") {
|
|
||||||
SET_OVERRIDE(nvdec_emulation, Settings::NvdecEmulation::Gpu);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k == "enable_buffer_history" || k == "buffer_history") {
|
|
||||||
SET_OVERRIDE(enable_buffer_history, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "fix_bloom_effects" || k == "fix_bloom") {
|
|
||||||
SET_OVERRIDE(fix_bloom_effects, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "dma_accuracy") {
|
|
||||||
if (v == "default") {
|
|
||||||
SET_OVERRIDE(dma_accuracy, Settings::DmaAccuracy::Default);
|
|
||||||
} else if (v == "unsafe" || v == "fast") {
|
|
||||||
SET_OVERRIDE(dma_accuracy, Settings::DmaAccuracy::Unsafe);
|
|
||||||
} else if (v == "safe" || v == "stable") {
|
|
||||||
SET_OVERRIDE(dma_accuracy, Settings::DmaAccuracy::Safe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (k == "airplane_mode" || k == "airplane") {
|
|
||||||
SET_OVERRIDE(airplane_mode, ToBool(v));
|
|
||||||
}
|
|
||||||
else if (k == "controller_applet_mode" || k == "controller_applet") {
|
|
||||||
if (v == "lle" || v == "real") {
|
|
||||||
SET_OVERRIDE(controller_applet_mode, Settings::AppletMode::LLE);
|
|
||||||
} else if (v == "hle" || v == "custom") {
|
|
||||||
SET_OVERRIDE(controller_applet_mode, Settings::AppletMode::HLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOG_WARNING(Core, "Unknown game override setting: {}={}", key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef SET_OVERRIDE
|
|
||||||
|
|
||||||
std::optional<Section> ParseSectionHeader(std::string_view header) {
|
|
||||||
if (header.size() < 2 || header.front() != '[' || header.back() != ']') {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
header = header.substr(1, header.size() - 2);
|
|
||||||
|
|
||||||
Section section{};
|
|
||||||
|
|
||||||
const auto pipe_pos = header.find('|');
|
|
||||||
std::string_view title_part = header;
|
|
||||||
std::string_view conditions_part;
|
|
||||||
|
|
||||||
if (pipe_pos != std::string_view::npos) {
|
|
||||||
title_part = header.substr(0, pipe_pos);
|
|
||||||
conditions_part = header.substr(pipe_pos + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string title_str = Trim(title_part);
|
|
||||||
if (title_str.starts_with("0x") || title_str.starts_with("0X")) {
|
|
||||||
title_str = title_str.substr(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint64_t title_id = 0;
|
|
||||||
auto [ptr, ec] = std::from_chars(title_str.data(), title_str.data() + title_str.size(),
|
|
||||||
title_id, 16);
|
|
||||||
if (ec != std::errc{}) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
section.title_id = title_id;
|
|
||||||
|
|
||||||
if (!conditions_part.empty()) {
|
|
||||||
std::string cond_str(conditions_part);
|
|
||||||
std::istringstream stream(cond_str);
|
|
||||||
std::string token;
|
|
||||||
|
|
||||||
while (std::getline(stream, token, ',')) {
|
|
||||||
token = Trim(token);
|
|
||||||
const auto colon_pos = token.find(':');
|
|
||||||
if (colon_pos == std::string::npos) continue;
|
|
||||||
|
|
||||||
std::string cond_key = ToLower(Trim(token.substr(0, colon_pos)));
|
|
||||||
std::string cond_value = ToLower(Trim(token.substr(colon_pos + 1)));
|
|
||||||
|
|
||||||
if (cond_key == "vendor") {
|
|
||||||
section.condition.vendor = cond_value;
|
|
||||||
} else if (cond_key == "os") {
|
|
||||||
section.condition.os.push_back(cond_value);
|
|
||||||
} else if (cond_key == "cpu" || cond_key == "cpu_backend") {
|
|
||||||
section.condition.cpu_backend = cond_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return section;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CpuBackendToString(Settings::CpuBackend backend) {
|
|
||||||
switch (backend) {
|
|
||||||
case Settings::CpuBackend::Dynarmic: return "jit";
|
|
||||||
case Settings::CpuBackend::Nce: return "nce";
|
|
||||||
default: return "unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConditionMatches(const Condition& cond, const GameSettings::EnvironmentInfo& env) {
|
|
||||||
if (cond.vendor.has_value() && cond.vendor.value() != VendorToString(env.vendor)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!cond.os.empty()) {
|
|
||||||
const std::string current_os = OSToString(env.os);
|
|
||||||
bool os_match = std::any_of(cond.os.begin(), cond.os.end(),
|
|
||||||
[¤t_os](const std::string& os) { return os == current_os; });
|
|
||||||
if (!os_match) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cond.cpu_backend.has_value()) {
|
|
||||||
const std::string current_cpu = CpuBackendToString(Settings::values.cpu_backend.GetValue());
|
|
||||||
const std::string& required = cond.cpu_backend.value();
|
|
||||||
if (required == "jit" || required == "dynarmic") {
|
|
||||||
if (current_cpu != "jit") return false;
|
|
||||||
} else if (required == "nce") {
|
|
||||||
if (current_cpu != "nce") return false;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Section> ParseOverridesFile(const std::filesystem::path& path) {
|
|
||||||
std::vector<Section> sections;
|
|
||||||
std::ifstream file(path);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
return sections;
|
|
||||||
}
|
|
||||||
|
|
||||||
Section* current_section = nullptr;
|
|
||||||
std::string line;
|
|
||||||
|
|
||||||
while (std::getline(file, line)) {
|
|
||||||
line = Trim(line);
|
|
||||||
|
|
||||||
if (line.empty() || line.front() == ';' || line.front() == '#') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.front() == '[' && line.back() == ']') {
|
|
||||||
auto section = ParseSectionHeader(line);
|
|
||||||
if (section) {
|
|
||||||
sections.push_back(std::move(*section));
|
|
||||||
current_section = §ions.back();
|
|
||||||
} else {
|
|
||||||
current_section = nullptr;
|
|
||||||
LOG_WARNING(Core, "Invalid section header in overrides.ini: {}", line);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current_section) {
|
|
||||||
const auto eq_pos = line.find('=');
|
|
||||||
if (eq_pos != std::string::npos) {
|
|
||||||
std::string setting_key = Trim(line.substr(0, eq_pos));
|
|
||||||
std::string setting_value = Trim(line.substr(eq_pos + 1));
|
|
||||||
current_section->settings.emplace_back(std::move(setting_key), std::move(setting_value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sections;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsEarlySetting(const std::string& key) {
|
|
||||||
std::array early_settings = {
|
|
||||||
"backend",
|
|
||||||
"renderer_backend",
|
|
||||||
"multicore",
|
|
||||||
"use_multi_core",
|
|
||||||
};
|
|
||||||
const std::string k = ToLower(key);
|
|
||||||
return std::ranges::any_of(early_settings,
|
|
||||||
[&k](const char* s) { return k == s; });
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EarlyConditionMatches(const Condition& cond, GameSettings::OS current_os,
|
|
||||||
const std::optional<GameSettings::GPUVendor>& detected_vendor) {
|
|
||||||
if (!cond.os.empty()) {
|
|
||||||
const std::string current_os_str = OSToString(current_os);
|
|
||||||
bool os_match = std::any_of(cond.os.begin(), cond.os.end(),
|
|
||||||
[¤t_os_str](const std::string& os) { return os == current_os_str; });
|
|
||||||
if (!os_match) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cond.vendor.has_value()) {
|
|
||||||
if (!detected_vendor.has_value()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (cond.vendor.value() != VendorToString(*detected_vendor)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cond.cpu_backend.has_value()) {
|
|
||||||
const std::string current_cpu = CpuBackendToString(Settings::values.cpu_backend.GetValue());
|
|
||||||
const std::string& required = cond.cpu_backend.value();
|
|
||||||
if (required == "jit" || required == "dynarmic") {
|
|
||||||
if (current_cpu != "jit") return false;
|
|
||||||
} else if (required == "nce" /* || required == "native" */) {
|
|
||||||
if (current_cpu != "nce") return false;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GetSpecificity(const Section& s) {
|
|
||||||
int score = 0;
|
|
||||||
if (s.condition.vendor.has_value()) score++;
|
|
||||||
if (!s.condition.os.empty()) score++;
|
|
||||||
if (s.condition.cpu_backend.has_value()) score++;
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // anonymous namespace
|
|
||||||
|
|
||||||
std::filesystem::path GetOverridesPath() {
|
|
||||||
return Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "overrides.ini";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OverridesFileExists() {
|
|
||||||
return std::filesystem::exists(GetOverridesPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::uint32_t> GetOverridesFileVersion() {
|
|
||||||
const auto path = GetOverridesPath();
|
|
||||||
std::ifstream file(path);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
if (std::getline(file, line)) {
|
|
||||||
line = Trim(line);
|
|
||||||
if (line.starts_with(kVersionPrefix)) {
|
|
||||||
const auto version_str = line.substr(std::strlen(kVersionPrefix));
|
|
||||||
std::uint32_t version = 0;
|
|
||||||
auto [ptr, ec] = std::from_chars(version_str.data(),
|
|
||||||
version_str.data() + version_str.size(), version);
|
|
||||||
if (ec == std::errc{}) {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyEarlyOverrides(std::uint64_t program_id, const std::string& gpu_vendor) {
|
|
||||||
const auto path = GetOverridesPath();
|
|
||||||
if (!std::filesystem::exists(path)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto current_os = DetectOS();
|
|
||||||
std::optional<GameSettings::GPUVendor> detected_vendor;
|
|
||||||
if (!gpu_vendor.empty()) {
|
|
||||||
detected_vendor = DetectGPUVendor(gpu_vendor);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sections = ParseOverridesFile(path);
|
|
||||||
|
|
||||||
// Filter matching sections
|
|
||||||
std::vector<Section> matching;
|
|
||||||
for (auto& section : sections) {
|
|
||||||
if (section.title_id != program_id) continue;
|
|
||||||
if (!EarlyConditionMatches(section.condition, current_os, detected_vendor)) continue;
|
|
||||||
matching.push_back(std::move(section));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by base first, then os, then vendor, then both
|
|
||||||
std::sort(matching.begin(), matching.end(),
|
|
||||||
[](const Section& a, const Section& b) {
|
|
||||||
return GetSpecificity(a) < GetSpecificity(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply only early settings, only apply before renderer overrides
|
|
||||||
for (const auto& section : matching) {
|
|
||||||
for (const auto& [setting_key, setting_value] : section.settings) {
|
|
||||||
if (IsEarlySetting(setting_key)) {
|
|
||||||
LOG_INFO(Core, "Game override {:016X}: {}={}",
|
|
||||||
program_id, setting_key, setting_value);
|
|
||||||
ApplySetting(setting_key, setting_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyLateOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer) {
|
|
||||||
const auto path = GetOverridesPath();
|
|
||||||
if (!std::filesystem::exists(path)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto env = GameSettings::DetectEnvironment(renderer);
|
|
||||||
auto sections = ParseOverridesFile(path);
|
|
||||||
|
|
||||||
// Filter matching sections
|
|
||||||
std::vector<Section> matching;
|
|
||||||
for (auto& section : sections) {
|
|
||||||
if (section.title_id != program_id) continue;
|
|
||||||
if (!ConditionMatches(section.condition, env)) continue;
|
|
||||||
matching.push_back(std::move(section));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by base first, then single condition, then multiple
|
|
||||||
std::sort(matching.begin(), matching.end(),
|
|
||||||
[](const Section& a, const Section& b) {
|
|
||||||
return GetSpecificity(a) < GetSpecificity(b);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply settings (skip early ones, they were already applied, like renderr_backend)
|
|
||||||
for (const auto& section : matching) {
|
|
||||||
for (const auto& [setting_key, setting_value] : section.settings) {
|
|
||||||
if (!IsEarlySetting(setting_key)) {
|
|
||||||
LOG_INFO(Core, "Game override {:016X}: {}={}",
|
|
||||||
program_id, setting_key, setting_value);
|
|
||||||
ApplySetting(setting_key, setting_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace Core::GameOverrides
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace VideoCore {
|
|
||||||
class RendererBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Core::GameOverrides {
|
|
||||||
// used to check against GitHub Release Version
|
|
||||||
// repo at: https://github.com/eden-emulator/eden-overrides
|
|
||||||
inline constexpr std::uint32_t kOverridesVersion = 1;
|
|
||||||
inline constexpr const char* kVersionPrefix = "; version=";
|
|
||||||
|
|
||||||
struct Condition {
|
|
||||||
std::optional<std::string> vendor;
|
|
||||||
std::vector<std::string> os;
|
|
||||||
std::optional<std::string> cpu_backend;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Section {
|
|
||||||
std::uint64_t title_id{};
|
|
||||||
Condition condition;
|
|
||||||
std::vector<std::pair<std::string, std::string>> settings;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::filesystem::path GetOverridesPath();
|
|
||||||
bool OverridesFileExists();
|
|
||||||
std::optional<std::uint32_t> GetOverridesFileVersion();
|
|
||||||
|
|
||||||
void ApplyEarlyOverrides(std::uint64_t program_id, const std::string& gpu_vendor);
|
|
||||||
void ApplyLateOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer);
|
|
||||||
} // namespace Core::GameOverrides
|
|
||||||
+17
-23
@@ -1,8 +1,7 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "core/game_settings.h"
|
#include "core/game_settings.h"
|
||||||
#include "core/game_overrides.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -61,26 +60,15 @@ static GPUVendor GetGPU(const std::string& gpu_vendor_string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// legacy (shouldn't be needed anymore, but just in case)
|
||||||
std::string gpu = gpu_vendor_string;
|
std::string gpu = gpu_vendor_string;
|
||||||
std::transform(gpu.begin(), gpu.end(), gpu.begin(), [](unsigned char c){ return (char)std::tolower(c); });
|
std::transform(gpu.begin(), gpu.end(), gpu.begin(), [](unsigned char c){ return (char)std::tolower(c); });
|
||||||
if (gpu.find("geforce") != std::string::npos) {
|
if (gpu.find("geforce") != std::string::npos) {
|
||||||
return GPUVendor::Nvidia;
|
return GPUVendor::Nvidia;
|
||||||
}
|
}
|
||||||
if (gpu.find("amd") != std::string::npos || gpu.find("radeon") != std::string::npos || gpu.find("ati") != std::string::npos) {
|
if (gpu.find("radeon") != std::string::npos || gpu.find("ati") != std::string::npos) {
|
||||||
return GPUVendor::AMD;
|
return GPUVendor::AMD;
|
||||||
}
|
}
|
||||||
if (gpu.find("intel") != std::string::npos) {
|
|
||||||
return GPUVendor::Intel;
|
|
||||||
}
|
|
||||||
if (gpu.find("apple") != std::string::npos) {
|
|
||||||
return GPUVendor::Apple;
|
|
||||||
}
|
|
||||||
if (gpu.find("qualcomm") != std::string::npos || gpu.find("adreno") != std::string::npos) {
|
|
||||||
return GPUVendor::Qualcomm;
|
|
||||||
}
|
|
||||||
if (gpu.find("mali") != std::string::npos) {
|
|
||||||
return GPUVendor::ARM;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GPUVendor::Unknown;
|
return GPUVendor::Unknown;
|
||||||
}
|
}
|
||||||
@@ -131,16 +119,22 @@ EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer) {
|
|||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadEarlyOverrides(std::uint64_t program_id, const std::string& gpu_vendor, bool enabled) {
|
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer) {
|
||||||
if (enabled && GameOverrides::OverridesFileExists()) {
|
const auto env = DetectEnvironment(renderer);
|
||||||
GameOverrides::ApplyEarlyOverrides(program_id, gpu_vendor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer, bool enabled) {
|
switch (static_cast<TitleID>(program_id)) {
|
||||||
if (enabled && GameOverrides::OverridesFileExists()) {
|
case TitleID::NinjaGaidenRagebound:
|
||||||
GameOverrides::ApplyLateOverrides(program_id, renderer);
|
Settings::values.use_squashed_iterated_blend = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_INFO(Core, "Applied game settings for title ID {:016X} on OS {}, GPU vendor {} ({})",
|
||||||
|
program_id,
|
||||||
|
static_cast<int>(env.os),
|
||||||
|
static_cast<int>(env.vendor),
|
||||||
|
env.vendor_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core::GameSettings
|
} // namespace Core::GameSettings
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -55,8 +55,6 @@ struct EnvironmentInfo {
|
|||||||
|
|
||||||
EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer);
|
EnvironmentInfo DetectEnvironment(const VideoCore::RendererBase& renderer);
|
||||||
|
|
||||||
void LoadEarlyOverrides(std::uint64_t program_id, const std::string& gpu_vendor, bool enabled = true);
|
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer);
|
||||||
|
|
||||||
void LoadOverrides(std::uint64_t program_id, const VideoCore::RendererBase& renderer, bool enabled = true);
|
|
||||||
|
|
||||||
} // namespace Core::GameSettings
|
} // namespace Core::GameSettings
|
||||||
|
|||||||
@@ -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-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -86,14 +83,6 @@ struct CodeSet final {
|
|||||||
const Segment& PatchSegment() const {
|
const Segment& PatchSegment() const {
|
||||||
return patch_segment;
|
return patch_segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
Segment& PostPatchSegment() {
|
|
||||||
return post_patch_segment;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Segment& PostPatchSegment() const {
|
|
||||||
return post_patch_segment;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// The overall data that backs this code set.
|
/// The overall data that backs this code set.
|
||||||
@@ -104,7 +93,6 @@ struct CodeSet final {
|
|||||||
|
|
||||||
#ifdef HAS_NCE
|
#ifdef HAS_NCE
|
||||||
Segment patch_segment;
|
Segment patch_segment;
|
||||||
Segment post_patch_segment;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// The entry point address for this code set.
|
/// The entry point address for this code set.
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -1258,7 +1258,6 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
|
|||||||
|
|
||||||
#ifdef HAS_NCE
|
#ifdef HAS_NCE
|
||||||
const auto& patch = code_set.PatchSegment();
|
const auto& patch = code_set.PatchSegment();
|
||||||
const auto& post_patch = code_set.PostPatchSegment();
|
|
||||||
if (this->IsApplication() && Settings::IsNceEnabled() && patch.size != 0) {
|
if (this->IsApplication() && Settings::IsNceEnabled() && patch.size != 0) {
|
||||||
auto& buffer = m_kernel.System().DeviceMemory().buffer;
|
auto& buffer = m_kernel.System().DeviceMemory().buffer;
|
||||||
const auto& code = code_set.CodeSegment();
|
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);
|
Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
|
||||||
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,
|
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,
|
||||||
Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
|
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);
|
ReprotectSegment(code_set.PatchSegment(), Svc::MemoryPermission::None);
|
||||||
if (post_patch.size != 0) {
|
|
||||||
ReprotectSegment(code_set.PostPatchSegment(), Svc::MemoryPermission::None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||||
@@ -36,7 +36,7 @@ IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_,
|
|||||||
{100, nullptr, "SetApplicationCoreUsageMode"},
|
{100, nullptr, "SetApplicationCoreUsageMode"},
|
||||||
{300, D<&IAppletCommonFunctions::GetCurrentApplicationId>, "GetCurrentApplicationId"},
|
{300, D<&IAppletCommonFunctions::GetCurrentApplicationId>, "GetCurrentApplicationId"},
|
||||||
{310, nullptr, "IsSystemAppletHomeMenu"}, //19.0.0+
|
{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+
|
{321, nullptr, "SetGpuTimeSliceBoostDueToApplication"}, //19.0.0+
|
||||||
{350, D<&IAppletCommonFunctions::Unknown350>, "Unknown350"} //20.0.0+
|
{350, D<&IAppletCommonFunctions::Unknown350>, "Unknown350"} //20.0.0+
|
||||||
};
|
};
|
||||||
@@ -82,11 +82,6 @@ Result IAppletCommonFunctions::GetCurrentApplicationId(Out<u64> out_application_
|
|||||||
R_SUCCEED();
|
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) {
|
Result IAppletCommonFunctions::Unknown350(Out<u16> out_unknown) {
|
||||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||||
*out_unknown = 0;
|
*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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||||
@@ -24,7 +24,6 @@ private:
|
|||||||
Result SetDisplayMagnification(f32 x, f32 y, f32 width, f32 height);
|
Result SetDisplayMagnification(f32 x, f32 y, f32 width, f32 height);
|
||||||
Result SetCpuBoostRequestPriority(s32 priority);
|
Result SetCpuBoostRequestPriority(s32 priority);
|
||||||
Result GetCurrentApplicationId(Out<u64> out_application_id);
|
Result GetCurrentApplicationId(Out<u64> out_application_id);
|
||||||
Result SetGpuTimeSliceBoost(s64 time_span);
|
|
||||||
Result Unknown350(Out<u16> out_unknown);
|
Result Unknown350(Out<u16> out_unknown);
|
||||||
|
|
||||||
const std::shared_ptr<Applet> applet;
|
const std::shared_ptr<Applet> applet;
|
||||||
|
|||||||
@@ -179,14 +179,7 @@ Result ILibraryAppletSelfAccessor::GetMainAppletStorageId(Out<FileSys::StorageId
|
|||||||
|
|
||||||
Result ILibraryAppletSelfAccessor::ExitProcessAndReturn() {
|
Result ILibraryAppletSelfAccessor::ExitProcessAndReturn() {
|
||||||
LOG_INFO(Service_AM, "called");
|
LOG_INFO(Service_AM, "called");
|
||||||
|
m_applet->process->Terminate();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/fs/fs.h"
|
#include "common/fs/fs.h"
|
||||||
#include "common/fs/path_util.h"
|
#include "common/fs/path_util.h"
|
||||||
#include "common/logging/log.h"
|
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/file_sys/bis_factory.h"
|
#include "core/file_sys/bis_factory.h"
|
||||||
@@ -508,10 +507,6 @@ FileSys::RegisteredCache* FileSystemController::GetSDMCContents() const {
|
|||||||
return sdmc_factory->GetSDMCContents();
|
return sdmc_factory->GetSDMCContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSys::ExternalContentProvider* FileSystemController::GetExternalContentProvider() const {
|
|
||||||
return external_provider.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSys::PlaceholderCache* FileSystemController::GetSystemNANDPlaceholder() const {
|
FileSys::PlaceholderCache* FileSystemController::GetSystemNANDPlaceholder() const {
|
||||||
LOG_TRACE(Service_FS, "Opening System NAND Placeholder");
|
LOG_TRACE(Service_FS, "Opening System NAND Placeholder");
|
||||||
|
|
||||||
@@ -689,7 +684,6 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
|||||||
if (overwrite) {
|
if (overwrite) {
|
||||||
bis_factory = nullptr;
|
bis_factory = nullptr;
|
||||||
sdmc_factory = nullptr;
|
sdmc_factory = nullptr;
|
||||||
external_provider = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using EdenPath = Common::FS::EdenPath;
|
using EdenPath = Common::FS::EdenPath;
|
||||||
@@ -722,36 +716,6 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
|||||||
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
|
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
|
||||||
sdmc_factory->GetSDMCContents());
|
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() {
|
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-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -20,7 +17,6 @@ class System;
|
|||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
class BISFactory;
|
class BISFactory;
|
||||||
class ExternalContentProvider;
|
|
||||||
class NCA;
|
class NCA;
|
||||||
class RegisteredCache;
|
class RegisteredCache;
|
||||||
class RegisteredCacheUnion;
|
class RegisteredCacheUnion;
|
||||||
@@ -121,8 +117,6 @@ public:
|
|||||||
|
|
||||||
FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
|
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
|
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
|
||||||
// above is called.
|
// above is called.
|
||||||
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
|
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
|
||||||
@@ -144,8 +138,6 @@ private:
|
|||||||
std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
|
std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
|
||||||
std::unique_ptr<FileSys::BISFactory> bis_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::XCI> gamecard;
|
||||||
std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;
|
std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;
|
||||||
std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder;
|
std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder;
|
||||||
|
|||||||
+4
-32
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// 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);
|
auto* patch = &patches->operator[](patch_index);
|
||||||
if (patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
|
if (patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
|
||||||
return patch->GetSectionSize();
|
return patch->GetSectionSize();
|
||||||
} else if (patch->GetPatchMode() == Core::NCE::PatchMode::Split) {
|
|
||||||
return patch->GetPreSectionSize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -180,26 +178,12 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
|||||||
}
|
}
|
||||||
} else if (patch) {
|
} else if (patch) {
|
||||||
// Relocate code patch and copy to the program_image.
|
// 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())) {
|
if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) {
|
||||||
// Update patch section.
|
// Update patch section.
|
||||||
auto& patch_segment = codeset.PatchSegment();
|
auto& patch_segment = codeset.PatchSegment();
|
||||||
auto& post_patch_segment = codeset.PostPatchSegment();
|
patch_segment.addr =
|
||||||
const auto patch_mode = patch->GetPatchMode();
|
patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size;
|
||||||
if (patch_mode == Core::NCE::PatchMode::PreText) {
|
patch_segment.size = static_cast<u32>(patch->GetSectionSize());
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh image_size to take account the patch section if it was added by RelocateAndCopy
|
// 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 we aren't actually loading (i.e. just computing the process code layout), we are done
|
||||||
if (!load_into_process) {
|
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;
|
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::vector<int> candidates;
|
||||||
std::copy_if(order.begin(), order.end(), std::back_inserter(candidates), [&](int i) { return regs[i].MaybeAllocatable(); });
|
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
|
// TODO: LRU
|
||||||
// suggested as an improvement. However, using an incrementing index
|
std::uniform_int_distribution<size_t> dis{0, candidates.size() - 1};
|
||||||
// seems to be close enough. Determine if an LRU is still needed.
|
return candidates[dis(rand_gen)];
|
||||||
return candidates[alloc_candidate_index++ % candidates.size()];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegAlloc::SpillGpr(int index) {
|
void RegAlloc::SpillGpr(int index) {
|
||||||
|
|||||||
@@ -160,12 +160,8 @@ class RegAlloc final {
|
|||||||
public:
|
public:
|
||||||
using ArgumentInfo = std::array<Argument, IR::max_arg_count>;
|
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
|
explicit RegAlloc(oaknut::CodeGenerator& code, FpsrManager& fpsr_manager, std::vector<int> gpr_order, std::vector<int> fpr_order)
|
||||||
: code{code}
|
: code{code}, fpsr_manager{fpsr_manager}, gpr_order{gpr_order}, fpr_order{fpr_order}, rand_gen{std::random_device{}()} {}
|
||||||
, fpsr_manager{fpsr_manager}
|
|
||||||
, gpr_order{gpr_order}
|
|
||||||
, fpr_order{fpr_order}
|
|
||||||
{}
|
|
||||||
|
|
||||||
ArgumentInfo GetArgumentInfo(IR::Inst* inst);
|
ArgumentInfo GetArgumentInfo(IR::Inst* inst);
|
||||||
bool WasValueDefined(IR::Inst* inst) const;
|
bool WasValueDefined(IR::Inst* inst) const;
|
||||||
@@ -335,7 +331,7 @@ private:
|
|||||||
HostLocInfo flags;
|
HostLocInfo flags;
|
||||||
std::array<HostLocInfo, SpillCount> spills;
|
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;
|
ankerl::unordered_dense::set<const IR::Inst*> defined_insts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ add_library(frontend_common STATIC
|
|||||||
data_manager.h data_manager.cpp
|
data_manager.h data_manager.cpp
|
||||||
play_time_manager.cpp
|
play_time_manager.cpp
|
||||||
play_time_manager.h
|
play_time_manager.h
|
||||||
settings_generator.h settings_generator.cpp
|
settings_generator.h settings_generator.cpp)
|
||||||
mod_manager.h mod_manager.cpp)
|
|
||||||
|
|
||||||
if (ENABLE_UPDATE_CHECKER)
|
if (ENABLE_UPDATE_CHECKER)
|
||||||
target_link_libraries(frontend_common PRIVATE httplib::httplib)
|
target_link_libraries(frontend_common PRIVATE httplib::httplib)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -22,8 +22,6 @@ add_library(qt_common STATIC
|
|||||||
util/rom.h util/rom.cpp
|
util/rom.h util/rom.cpp
|
||||||
util/applet.h util/applet.cpp
|
util/applet.h util/applet.cpp
|
||||||
util/compress.h util/compress.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/frontend.h abstract/frontend.cpp
|
||||||
abstract/qt_progress_dialog.h abstract/qt_progress_dialog.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_string_lookup.h
|
||||||
qt_compat.h
|
qt_compat.h
|
||||||
|
|
||||||
discord/discord.h)
|
discord/discord.h
|
||||||
|
util/fs.h util/fs.cpp
|
||||||
|
)
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
target_sources(qt_common PRIVATE gui_settings.cpp gui_settings.h)
|
target_sources(qt_common PRIVATE gui_settings.cpp gui_settings.h)
|
||||||
|
|||||||
@@ -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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QLineEdit>
|
|
||||||
#include "frontend.h"
|
#include "frontend.h"
|
||||||
#include "qt_common/qt_common.h"
|
#include "qt_common/qt_common.h"
|
||||||
|
|
||||||
@@ -9,9 +8,6 @@
|
|||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <QAbstractButton>
|
|
||||||
#include <QInputDialog>
|
|
||||||
|
|
||||||
namespace QtCommon::Frontend {
|
namespace QtCommon::Frontend {
|
||||||
|
|
||||||
StandardButton ShowMessage(
|
StandardButton ShowMessage(
|
||||||
@@ -54,25 +50,4 @@ const QString GetExistingDirectory(const QString& caption, const QString& dir,
|
|||||||
#endif
|
#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
|
} // namespace QtCommon::Frontend
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#ifndef FRONTEND_H
|
#ifndef FRONTEND_H
|
||||||
@@ -139,11 +139,5 @@ const QString GetExistingDirectory(const QString &caption = QString(),
|
|||||||
const QString &dir = QString(),
|
const QString &dir = QString(),
|
||||||
Options options = Option::ShowDirsOnly);
|
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
|
} // namespace QtCommon::Frontend
|
||||||
#endif // FRONTEND_H
|
#endif // FRONTEND_H
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
@@ -231,16 +231,6 @@ void QtConfig::ReadPathValues() {
|
|||||||
QString::fromStdString(ReadStringSetting(std::string("recentFiles")))
|
QString::fromStdString(ReadStringSetting(std::string("recentFiles")))
|
||||||
.split(QStringLiteral(", "), Qt::SkipEmptyParts, Qt::CaseSensitive);
|
.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);
|
ReadCategory(Settings::Category::Paths);
|
||||||
|
|
||||||
EndGroup();
|
EndGroup();
|
||||||
@@ -456,13 +446,6 @@ void QtConfig::SavePathValues() {
|
|||||||
WriteStringSetting(std::string("recentFiles"),
|
WriteStringSetting(std::string("recentFiles"),
|
||||||
UISettings::values.recent_files.join(QStringLiteral(", ")).toStdString());
|
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();
|
EndGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -443,15 +443,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
|||||||
check_for_updates,
|
check_for_updates,
|
||||||
tr("Check for updates"),
|
tr("Check for updates"),
|
||||||
tr("Whether or not to check for updates upon startup."));
|
tr("Whether or not to check for updates upon startup."));
|
||||||
INSERT(UISettings,
|
|
||||||
enable_global_overrides,
|
|
||||||
tr("Enable global game overrides"),
|
|
||||||
tr("When enabled, per-game settings from the global overrides.ini file will be applied."));
|
|
||||||
INSERT(UISettings,
|
|
||||||
auto_update_overrides,
|
|
||||||
tr("Auto-update game overrides"),
|
|
||||||
tr("Automatically download the latest game overrides list from GitHub on startup."));
|
|
||||||
INSERT(UISettings, overrides_consent_given, QString(), QString());
|
|
||||||
|
|
||||||
// Linux
|
// Linux
|
||||||
INSERT(UISettings, enable_gamemode, tr("Enable Gamemode"), QString());
|
INSERT(UISettings, enable_gamemode, tr("Enable Gamemode"), QString());
|
||||||
@@ -775,12 +766,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
|
|||||||
PAIR(ExtendedDynamicState, EDS3, tr("ExtendedDynamicState 3")),
|
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 PAIR
|
||||||
#undef CTX_PAIR
|
#undef CTX_PAIR
|
||||||
|
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||||
@@ -142,10 +142,6 @@ struct Values {
|
|||||||
|
|
||||||
Setting<bool> check_for_updates{linkage, true, "check_for_updates", Category::UiGeneral};
|
Setting<bool> check_for_updates{linkage, true, "check_for_updates", Category::UiGeneral};
|
||||||
|
|
||||||
Setting<bool> enable_global_overrides{linkage, true, "enable_global_overrides", Category::UiGeneral};
|
|
||||||
Setting<bool> auto_update_overrides{linkage, false, "auto_update_overrides", Category::UiGeneral};
|
|
||||||
Setting<bool> overrides_consent_given{linkage, false, "overrides_consent_given", Category::UiGeneral};
|
|
||||||
|
|
||||||
// Linux/MinGW may support (requires libdl support)
|
// Linux/MinGW may support (requires libdl support)
|
||||||
SwitchableSetting<bool> enable_gamemode{linkage,
|
SwitchableSetting<bool> enable_gamemode{linkage,
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
@@ -214,9 +210,6 @@ struct Values {
|
|||||||
Setting<u32> folder_icon_size{linkage, 48, "folder_icon_size", Category::UiGameList};
|
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_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<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};
|
std::atomic_bool is_game_list_reload_pending{false};
|
||||||
Setting<bool> cache_game_list{linkage, true, "cache_game_list", Category::UiGameList};
|
Setting<bool> cache_game_list{linkage, true, "cache_game_list", Category::UiGameList};
|
||||||
Setting<bool> favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList};
|
Setting<bool> favorites_expanded{linkage, true, "favorites_expanded", Category::UiGameList};
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
@@ -98,7 +98,8 @@ static const constexpr frozen::map<StringKey, frozen::string, 29> strings = {
|
|||||||
|
|
||||||
{FwCheckErrorFirmwareMissing,
|
{FwCheckErrorFirmwareMissing,
|
||||||
QT_TR_NOOP(
|
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,
|
{FwCheckErrorFirmwareCorrupted,
|
||||||
QT_TR_NOOP(
|
QT_TR_NOOP(
|
||||||
"Firmware reported as present, but was unable to be read. Check for decryption keys and "
|
"Firmware reported as present, but was unable to be read. Check for decryption keys and "
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "qt_common/util/game.h"
|
#include "qt_common/util/game.h"
|
||||||
@@ -373,23 +373,27 @@ void RemoveCacheStorage(u64 program_id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Metadata //
|
// Metadata //
|
||||||
void ResetMetadata(bool show_message) {
|
void ResetMetadata(bool show_message)
|
||||||
|
{
|
||||||
const QString title = tr("Reset Metadata Cache");
|
const QString title = tr("Reset Metadata Cache");
|
||||||
|
|
||||||
if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) /
|
if (!Common::FS::Exists(Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir)
|
||||||
"game_list/")) {
|
/ "game_list/")) {
|
||||||
if (show_message)
|
if (show_message)
|
||||||
QtCommon::Frontend::Warning(rootObject, title,
|
QtCommon::Frontend::Warning(rootObject,
|
||||||
|
title,
|
||||||
tr("The metadata cache is already empty."));
|
tr("The metadata cache is already empty."));
|
||||||
} else if (Common::FS::RemoveDirRecursively(
|
} else if (Common::FS::RemoveDirRecursively(
|
||||||
Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list")) {
|
Common::FS::GetEdenPath(Common::FS::EdenPath::CacheDir) / "game_list")) {
|
||||||
if (show_message)
|
if (show_message)
|
||||||
QtCommon::Frontend::Information(rootObject, title,
|
QtCommon::Frontend::Information(rootObject,
|
||||||
|
title,
|
||||||
tr("The operation completed successfully."));
|
tr("The operation completed successfully."));
|
||||||
UISettings::values.is_game_list_reload_pending.exchange(true);
|
UISettings::values.is_game_list_reload_pending.exchange(true);
|
||||||
} else {
|
} else {
|
||||||
if (show_message)
|
if (show_message)
|
||||||
QtCommon::Frontend::Warning(
|
QtCommon::Frontend::Warning(
|
||||||
|
|
||||||
title,
|
title,
|
||||||
tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
|
tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -14,68 +14,68 @@ set(GLSL_INCLUDES
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(SHADER_FILES
|
set(SHADER_FILES
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
|
astc_decoder.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
|
blit_color_float.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
|
block_linear_unswizzle_2d.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
|
block_linear_unswizzle_3d.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp
|
block_linear_unswizzle_3d_bcn.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag
|
convert_abgr8_to_d24s8.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag
|
convert_abgr8_to_d32f.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag
|
convert_d32f_to_abgr8.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_d24s8_to_abgr8.frag
|
convert_d24s8_to_abgr8.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_depth_to_float.frag
|
convert_depth_to_float.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_float_to_depth.frag
|
convert_float_to_depth.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_msaa_to_non_msaa.comp
|
convert_msaa_to_non_msaa.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_non_msaa_to_msaa.comp
|
convert_non_msaa_to_msaa.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/convert_s8d24_to_abgr8.frag
|
convert_s8d24_to_abgr8.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/full_screen_triangle.vert
|
full_screen_triangle.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.frag
|
fxaa.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/fxaa.vert
|
fxaa.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_convert_s8d24.comp
|
opengl_convert_s8d24.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_copy_bc4.comp
|
opengl_copy_bc4.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_fidelityfx_fsr.frag
|
opengl_fidelityfx_fsr.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_fidelityfx_fsr_easu.frag
|
opengl_fidelityfx_fsr_easu.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_fidelityfx_fsr_rcas.frag
|
opengl_fidelityfx_fsr_rcas.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_lmem_warmup.comp
|
opengl_lmem_warmup.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_present.frag
|
opengl_present.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_present.vert
|
opengl_present.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_present_scaleforce.frag
|
opengl_present_scaleforce.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/opengl_smaa.glsl
|
opengl_smaa.glsl
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/pitch_unswizzle.comp
|
pitch_unswizzle.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/present_area.frag
|
present_area.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/present_bicubic.frag
|
present_bicubic.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/present_zero_tangent.frag
|
present_zero_tangent.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/present_bspline.frag
|
present_bspline.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/present_mitchell.frag
|
present_mitchell.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/present_gaussian.frag
|
present_gaussian.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/present_lanczos.frag
|
present_lanczos.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/present_spline1.frag
|
present_spline1.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/present_mmpx.frag
|
present_mmpx.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/queries_prefix_scan_sum.comp
|
queries_prefix_scan_sum.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/queries_prefix_scan_sum_nosubgroups.comp
|
queries_prefix_scan_sum_nosubgroups.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/resolve_conditional_render.comp
|
resolve_conditional_render.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/smaa_edge_detection.vert
|
smaa_edge_detection.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/smaa_edge_detection.frag
|
smaa_edge_detection.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/smaa_blending_weight_calculation.vert
|
smaa_blending_weight_calculation.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/smaa_blending_weight_calculation.frag
|
smaa_blending_weight_calculation.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/smaa_neighborhood_blending.vert
|
smaa_neighborhood_blending.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/smaa_neighborhood_blending.frag
|
smaa_neighborhood_blending.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_blit_depth_stencil.frag
|
vulkan_blit_depth_stencil.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_color_clear.frag
|
vulkan_color_clear.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_color_clear.vert
|
vulkan_color_clear.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_depthstencil_clear.frag
|
vulkan_depthstencil_clear.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr.vert
|
vulkan_fidelityfx_fsr.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_easu_fp16.frag
|
vulkan_fidelityfx_fsr_easu_fp16.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_easu_fp32.frag
|
vulkan_fidelityfx_fsr_easu_fp32.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_rcas_fp16.frag
|
vulkan_fidelityfx_fsr_rcas_fp16.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_fidelityfx_fsr_rcas_fp32.frag
|
vulkan_fidelityfx_fsr_rcas_fp32.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present.frag
|
vulkan_present.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present.vert
|
vulkan_present.vert
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp16.frag
|
vulkan_present_scaleforce_fp16.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_present_scaleforce_fp32.frag
|
vulkan_present_scaleforce_fp32.frag
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_quad_indexed.comp
|
vulkan_quad_indexed.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_turbo_mode.comp
|
vulkan_turbo_mode.comp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/vulkan_uint8.comp
|
vulkan_uint8.comp
|
||||||
)
|
)
|
||||||
|
|
||||||
if (PLATFORM_HAIKU)
|
if (PLATFORM_HAIKU)
|
||||||
@@ -118,9 +118,9 @@ if (NOT GLSLANG_ERROR STREQUAL "")
|
|||||||
set(QUIET_FLAG "")
|
set(QUIET_FLAG "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
foreach(SOURCE_FILE IN ITEMS ${SHADER_FILES})
|
foreach(FILENAME IN ITEMS ${SHADER_FILES})
|
||||||
get_filename_component(FILENAME ${SOURCE_FILE} NAME)
|
|
||||||
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
|
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
|
||||||
|
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
|
||||||
# Skip generating source headers on Vulkan exclusive files
|
# Skip generating source headers on Vulkan exclusive files
|
||||||
if (NOT ${FILENAME} MATCHES "vulkan.*")
|
if (NOT ${FILENAME} MATCHES "vulkan.*")
|
||||||
set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
|
set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
|
||||||
|
|||||||
@@ -436,23 +436,7 @@ void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayo
|
|||||||
.layerCount = 1,
|
.layerCount = 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Blit image barrier - transition can happen at any stage
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
// 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,
|
|
||||||
0, barrier);
|
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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||||
@@ -68,16 +68,7 @@ void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayo
|
|||||||
.layerCount = 1,
|
.layerCount = 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Present path - use specific stages for better parallelism on tile-based GPUs
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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,
|
|
||||||
0, barrier);
|
0, barrier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,12 +151,11 @@ void DownloadColorImage(vk::CommandBuffer& cmdbuf, VkImage image, VkBuffer buffe
|
|||||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Host will read the downloaded data
|
|
||||||
static constexpr VkMemoryBarrier memory_write_barrier{
|
static constexpr VkMemoryBarrier memory_write_barrier{
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
};
|
};
|
||||||
const VkBufferImageCopy copy{
|
const VkBufferImageCopy copy{
|
||||||
.bufferOffset = 0,
|
.bufferOffset = 0,
|
||||||
@@ -180,20 +170,10 @@ void DownloadColorImage(vk::CommandBuffer& cmdbuf, VkImage image, VkBuffer buffe
|
|||||||
.imageOffset{.x = 0, .y = 0, .z = 0},
|
.imageOffset{.x = 0, .y = 0, .z = 0},
|
||||||
.imageExtent{extent},
|
.imageExtent{extent},
|
||||||
};
|
};
|
||||||
const VkPipelineStageFlags src_stages_copy =
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
|
||||||
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,
|
|
||||||
read_barrier);
|
read_barrier);
|
||||||
cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, copy);
|
cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, copy);
|
||||||
const VkPipelineStageFlags dst_stages_copy =
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0,
|
||||||
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,
|
|
||||||
memory_write_barrier, nullptr, image_write_barrier);
|
memory_write_barrier, nullptr, image_write_barrier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -436,27 +436,13 @@ void BufferCacheRuntime::CopyBuffer(VkBuffer dst_buffer, VkBuffer src_buffer,
|
|||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([src_buffer, dst_buffer, vk_copies, barrier](vk::CommandBuffer cmdbuf) {
|
||||||
if (barrier) {
|
if (barrier) {
|
||||||
// Buffer writes can come from vertex input, shaders, or compute
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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, 0, READ_BARRIER);
|
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
|
||||||
}
|
}
|
||||||
cmdbuf.CopyBuffer(src_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies));
|
cmdbuf.CopyBuffer(src_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies));
|
||||||
if (barrier) {
|
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,
|
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.RequestOutsideRenderPassOperationContext();
|
||||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||||
const VkPipelineStageFlags src_stages =
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
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,
|
|
||||||
0, READ_BARRIER);
|
0, READ_BARRIER);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferCacheRuntime::PostCopyBarrier() {
|
void BufferCacheRuntime::PostCopyBarrier() {
|
||||||
// Specific access flags for buffer destinations: vertex input, uniforms, storage, index
|
|
||||||
static constexpr VkMemoryBarrier WRITE_BARRIER{
|
static constexpr VkMemoryBarrier WRITE_BARRIER{
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT |
|
|
||||||
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT,
|
|
||||||
};
|
};
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||||
const VkPipelineStageFlags dst_stages =
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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);
|
0, WRITE_BARRIER);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -523,24 +494,10 @@ void BufferCacheRuntime::ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t si
|
|||||||
|
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
scheduler.Record([dest_buffer, offset, size, value](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([dest_buffer, offset, size, value](vk::CommandBuffer cmdbuf) {
|
||||||
// Buffer writes can come from vertex input, shaders, or compute
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
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,
|
|
||||||
0, READ_BARRIER);
|
0, READ_BARRIER);
|
||||||
cmdbuf.FillBuffer(dest_buffer, offset, size, value);
|
cmdbuf.FillBuffer(dest_buffer, offset, size, value);
|
||||||
// Buffer reads can go to vertex input, shaders, or compute
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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,
|
|
||||||
0, WRITE_BARRIER);
|
0, WRITE_BARRIER);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -446,26 +446,13 @@ void ConditionalRenderingResolvePass::Resolve(VkBuffer dst_buffer, VkBuffer src_
|
|||||||
const VkDescriptorSet set = descriptor_allocator.Commit();
|
const VkDescriptorSet set = descriptor_allocator.Commit();
|
||||||
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
||||||
|
|
||||||
// Memory writes can come from graphics or compute stages
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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,
|
|
||||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
|
||||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
||||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
||||||
cmdbuf.Dispatch(1, 1, 1);
|
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,
|
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();
|
const VkDescriptorSet set = descriptor_allocator.Commit();
|
||||||
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
|
||||||
|
|
||||||
// Memory writes can come from graphics or compute stages
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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,
|
|
||||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, read_barrier);
|
||||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
|
||||||
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
|
||||||
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
|
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, uniforms);
|
||||||
cmdbuf.Dispatch(1, 1, 1);
|
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,
|
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,
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// If initialized, memory writes can come from graphics or compute stages
|
cmdbuf.PipelineBarrier(is_initialized ? VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
|
||||||
const VkPipelineStageFlags src_stages_astc = is_initialized ?
|
: VK_PIPELINE_STAGE_TOP_OF_PIPE_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 |
|
|
||||||
VK_PIPELINE_STAGE_TRANSFER_BIT) :
|
|
||||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
||||||
cmdbuf.PipelineBarrier(src_stages_astc,
|
|
||||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
|
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, image_barrier);
|
||||||
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, vk_pipeline);
|
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,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
|
.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,
|
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.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,
|
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();
|
scheduler.Finish();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// 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
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {},
|
||||||
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, {},
|
|
||||||
{}, {}, pre_barriers);
|
{}, {}, pre_barriers);
|
||||||
|
|
||||||
if (blit_supported) {
|
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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
@@ -809,19 +809,12 @@ public:
|
|||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
.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.RequestOutsideRenderPassOperationContext();
|
||||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
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,
|
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);
|
std::scoped_lock lk(flush_guard);
|
||||||
@@ -949,7 +942,7 @@ private:
|
|||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
|
||||||
};
|
};
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
scheduler.Record([dst_buffer = current_bank->GetBuffer(),
|
scheduler.Record([dst_buffer = current_bank->GetBuffer(),
|
||||||
@@ -1482,40 +1475,22 @@ void QueryCacheRuntime::Barriers(bool is_prebarrier) {
|
|||||||
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_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{
|
static constexpr VkMemoryBarrier WRITE_BARRIER{
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
.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,
|
||||||
VK_ACCESS_INDIRECT_COMMAND_READ_BIT,
|
|
||||||
};
|
};
|
||||||
impl->scheduler.RequestOutsideRenderPassOperationContext();
|
impl->scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
if (is_prebarrier) {
|
if (is_prebarrier) {
|
||||||
impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
||||||
// Query data can be written by graphics or compute stages
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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,
|
|
||||||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
|
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, READ_BARRIER);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
impl->scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
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,
|
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};
|
const std::array<u32, 3> dim{qmd.grid_dim_x, qmd.grid_dim_y, qmd.grid_dim_z};
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
// Compute shader reads from storage buffers, uniforms, and images
|
|
||||||
static constexpr VkMemoryBarrier READ_BARRIER{
|
static constexpr VkMemoryBarrier READ_BARRIER{
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT,
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
|
||||||
};
|
};
|
||||||
scheduler.Record([](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||||
// Memory writes can come from graphics or compute stages
|
0, READ_BARRIER); });
|
||||||
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([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
|
scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
|
||||||
|
|
||||||
// Log compute dispatch
|
// 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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
@@ -44,19 +44,13 @@ using VideoCore::Surface::SurfaceType;
|
|||||||
}
|
}
|
||||||
|
|
||||||
VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat format,
|
VkAttachmentDescription AttachmentDescription(const Device& device, PixelFormat format,
|
||||||
VkSampleCountFlagBits samples,
|
VkSampleCountFlagBits samples) {
|
||||||
bool is_depth_stencil) {
|
|
||||||
using MaxwellToVK::SurfaceFormat;
|
using MaxwellToVK::SurfaceFormat;
|
||||||
|
|
||||||
const SurfaceType surface_type = GetSurfaceType(format);
|
const SurfaceType surface_type = GetSurfaceType(format);
|
||||||
const bool has_stencil = surface_type == SurfaceType::DepthStencil ||
|
const bool has_stencil = surface_type == SurfaceType::DepthStencil ||
|
||||||
surface_type == SurfaceType::Stencil;
|
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 {
|
return {
|
||||||
.flags = {},
|
.flags = {},
|
||||||
.format = SurfaceFormat(device, FormatType::Optimal, true, format).format,
|
.format = SurfaceFormat(device, FormatType::Optimal, true, format).format,
|
||||||
@@ -67,8 +61,8 @@ using VideoCore::Surface::SurfaceType;
|
|||||||
: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||||
.stencilStoreOp = has_stencil ? VK_ATTACHMENT_STORE_OP_STORE
|
.stencilStoreOp = has_stencil ? VK_ATTACHMENT_STORE_OP_STORE
|
||||||
: VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
: VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||||
.initialLayout = attachment_layout,
|
.initialLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.finalLayout = attachment_layout,
|
.finalLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
@@ -90,10 +84,10 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
|||||||
const bool is_valid{format != PixelFormat::Invalid};
|
const bool is_valid{format != PixelFormat::Invalid};
|
||||||
references[index] = VkAttachmentReference{
|
references[index] = VkAttachmentReference{
|
||||||
.attachment = is_valid ? num_colors : VK_ATTACHMENT_UNUSED,
|
.attachment = is_valid ? num_colors : VK_ATTACHMENT_UNUSED,
|
||||||
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
.layout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
};
|
};
|
||||||
if (is_valid) {
|
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_attachments = static_cast<u32>(index + 1);
|
||||||
++num_colors;
|
++num_colors;
|
||||||
}
|
}
|
||||||
@@ -103,9 +97,9 @@ VkRenderPass RenderPassCache::Get(const RenderPassKey& key) {
|
|||||||
if (key.depth_format != PixelFormat::Invalid) {
|
if (key.depth_format != PixelFormat::Invalid) {
|
||||||
depth_reference = VkAttachmentReference{
|
depth_reference = VkAttachmentReference{
|
||||||
.attachment = num_colors,
|
.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{
|
const VkSubpassDescription subpass{
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
|
|||||||
@@ -264,26 +264,14 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
|
|||||||
const u64 signal_value = master_semaphore->NextTick();
|
const u64 signal_value = master_semaphore->NextTick();
|
||||||
RecordWithUploadBuffer([signal_semaphore, wait_semaphore, signal_value,
|
RecordWithUploadBuffer([signal_semaphore, wait_semaphore, signal_value,
|
||||||
this](vk::CommandBuffer cmdbuf, vk::CommandBuffer upload_cmdbuf) {
|
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{
|
static constexpr VkMemoryBarrier WRITE_BARRIER{
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_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,
|
|
||||||
};
|
};
|
||||||
// Specify exact stages that need the transfer results instead of ALL_COMMANDS_BIT
|
|
||||||
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
upload_cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, WRITE_BARRIER);
|
||||||
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);
|
|
||||||
upload_cmdbuf.End();
|
upload_cmdbuf.End();
|
||||||
cmdbuf.End();
|
cmdbuf.End();
|
||||||
|
|
||||||
@@ -344,11 +332,11 @@ void Scheduler::EndRenderPass()
|
|||||||
|
|
||||||
query_cache->CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, false);
|
query_cache->CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, false);
|
||||||
query_cache->NotifySegment(false);
|
query_cache->NotifySegment(false);
|
||||||
|
|
||||||
Record([num_images = num_renderpass_images,
|
Record([num_images = num_renderpass_images,
|
||||||
images = renderpass_images,
|
images = renderpass_images,
|
||||||
ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
|
ranges = renderpass_image_ranges](vk::CommandBuffer cmdbuf) {
|
||||||
std::array<VkImageMemoryBarrier, 9> barriers;
|
std::array<VkImageMemoryBarrier, 9> barriers;
|
||||||
VkPipelineStageFlags src_stages = 0;
|
|
||||||
for (size_t i = 0; i < num_images; ++i) {
|
for (size_t i = 0; i < num_images; ++i) {
|
||||||
const VkImageSubresourceRange& range = ranges[i];
|
const VkImageSubresourceRange& range = ranges[i];
|
||||||
const bool is_color = (range.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
|
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_DEPTH_BIT
|
||||||
| VK_IMAGE_ASPECT_STENCIL_BIT)) !=0;
|
| 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 src_access = 0;
|
||||||
VkAccessFlags dst_access = 0;
|
|
||||||
VkPipelineStageFlags this_stage = 0;
|
|
||||||
VkImageLayout new_layout;
|
|
||||||
|
|
||||||
if (is_color) {
|
if (is_color)
|
||||||
// Color attachments can be read as textures or used as attachments again
|
src_access |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||||
src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
else if (is_depth_stencil)
|
||||||
this_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
src_access |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||||
new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
else
|
||||||
dst_access = VK_ACCESS_SHADER_READ_BIT
|
src_access |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
|
||||||
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
|
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_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;
|
|
||||||
barriers[i] = VkImageMemoryBarrier{
|
barriers[i] = VkImageMemoryBarrier{
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = src_access,
|
.srcAccessMask = src_access,
|
||||||
.dstAccessMask = dst_access,
|
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT
|
||||||
.oldLayout = is_color ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
|
| VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
|
||||||
: (is_depth_stencil ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
|
| VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
|
||||||
: VK_IMAGE_LAYOUT_GENERAL),
|
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
|
||||||
.newLayout = new_layout,
|
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
|
||||||
|
.oldLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
.image = images[i],
|
.image = images[i],
|
||||||
.subresourceRange = range,
|
.subresourceRange = range,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdbuf.EndRenderPass();
|
cmdbuf.EndRenderPass();
|
||||||
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||||
// Use specific pipeline stages instead of ALL_COMMANDS_BIT for better parallelism
|
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT |
|
||||||
// The destination stages depend on how the images will be used next
|
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||||
const VkPipelineStageFlags dst_stages =
|
VK_PIPELINE_STAGE_ALL_COMMANDS_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;
|
|
||||||
|
|
||||||
cmdbuf.PipelineBarrier(src_stages,
|
|
||||||
dst_stages,
|
|
||||||
0,
|
0,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
|
|||||||
@@ -810,8 +810,7 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
|||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = 0,
|
.srcAccessMask = 0,
|
||||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
|
.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT,
|
||||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
||||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.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,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
|
.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT,
|
||||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
||||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
@@ -833,26 +831,11 @@ void BlitScale(Scheduler& scheduler, VkImage src_image, VkImage dst_image, const
|
|||||||
.subresourceRange = subresource_range,
|
.subresourceRange = subresource_range,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Use specific pipeline stages for better parallelism
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
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,
|
|
||||||
0, nullptr, nullptr, read_barriers);
|
0, nullptr, nullptr, read_barriers);
|
||||||
cmdbuf.BlitImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image,
|
cmdbuf.BlitImage(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_image,
|
||||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regions, vk_filter);
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regions, vk_filter);
|
||||||
// After transfer, images may be used in graphics, compute, or as attachments
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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,
|
|
||||||
0, nullptr, nullptr, write_barriers);
|
0, nullptr, nullptr, write_barriers);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1038,12 +1021,11 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
|
|||||||
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_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{
|
static constexpr VkMemoryBarrier WRITE_BARRIER{
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
.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{
|
const std::array pre_barriers{
|
||||||
VkImageMemoryBarrier{
|
VkImageMemoryBarrier{
|
||||||
@@ -1110,39 +1092,18 @@ void TextureCacheRuntime::ReinterpretImage(Image& dst, Image& src,
|
|||||||
.subresourceRange = dst_range.SubresourceRange(dst_aspect_mask),
|
.subresourceRange = dst_range.SubresourceRange(dst_aspect_mask),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const VkPipelineStageFlags src_stages_transfer =
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_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_FRAGMENT_SHADER_BIT |
|
|
||||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
|
||||||
VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
||||||
cmdbuf.PipelineBarrier(src_stages_transfer, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
||||||
0, {}, {}, pre_barriers);
|
0, {}, {}, pre_barriers);
|
||||||
|
|
||||||
cmdbuf.CopyImageToBuffer(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, copy_buffer,
|
cmdbuf.CopyImageToBuffer(src_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, copy_buffer,
|
||||||
vk_in_copies);
|
vk_in_copies);
|
||||||
const VkPipelineStageFlags dst_stages_transfer =
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_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(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_transfer,
|
|
||||||
0, WRITE_BARRIER, nullptr, middle_in_barrier);
|
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);
|
0, READ_BARRIER, {}, middle_out_barrier);
|
||||||
cmdbuf.CopyBufferToImage(copy_buffer, dst_image, VK_IMAGE_LAYOUT_GENERAL, vk_out_copies);
|
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
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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,
|
|
||||||
0, {}, {}, post_barriers);
|
0, {}, {}, post_barriers);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1269,12 +1230,7 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
|
|||||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const VkPipelineStageFlags src_stages_resolve =
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
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,
|
|
||||||
0, nullptr, nullptr, read_barriers);
|
0, nullptr, nullptr, read_barriers);
|
||||||
if (is_resolve) {
|
if (is_resolve) {
|
||||||
cmdbuf.ResolveImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image,
|
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,
|
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);
|
MakeImageBlit(dst_region, src_region, dst_layers, src_layers), vk_filter);
|
||||||
}
|
}
|
||||||
// After blit/resolve, image may be used in shaders or as attachment
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_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;
|
|
||||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_blit,
|
|
||||||
0, write_barrier);
|
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,
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const VkPipelineStageFlags src_stages_download =
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_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_FRAGMENT_SHADER_BIT |
|
|
||||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT |
|
|
||||||
VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
||||||
cmdbuf.PipelineBarrier(src_stages_download, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
||||||
0, read_barrier);
|
0, read_barrier);
|
||||||
|
|
||||||
for (size_t index = 0; index < buffers.size(); index++) {
|
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]);
|
vk_copies[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host will read the downloaded data
|
|
||||||
const VkMemoryBarrier memory_write_barrier{
|
const VkMemoryBarrier memory_write_barrier{
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
.dstAccessMask = VK_ACCESS_HOST_READ_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{
|
const VkImageMemoryBarrier image_write_barrier{
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = 0,
|
.srcAccessMask = 0,
|
||||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
|
.dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
|
||||||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.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,
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const VkPipelineStageFlags dst_stages_download =
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_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;
|
|
||||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stages_download,
|
|
||||||
0, memory_write_barrier, nullptr, image_write_barrier);
|
0, memory_write_barrier, nullptr, image_write_barrier);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -1955,15 +1888,7 @@ void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<size_t> o
|
|||||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Image writes can come from graphics or compute stages
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||||||
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,
|
|
||||||
0, read_barrier);
|
0, read_barrier);
|
||||||
|
|
||||||
for (size_t index = 0; index < buffers.size(); index++) {
|
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,
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// After download, image may be used in shaders or as attachment
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
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,
|
|
||||||
0, memory_write_barrier, nullptr, image_write_barrier);
|
0, memory_write_barrier, nullptr, image_write_barrier);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2556,9 +2474,7 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) {
|
|||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.srcAccessMask = VK_ACCESS_NONE,
|
.srcAccessMask = VK_ACCESS_NONE,
|
||||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_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_UNDEFINED,
|
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
@@ -2574,16 +2490,8 @@ void TextureCacheRuntime::TransitionImageLayout(Image& image) {
|
|||||||
};
|
};
|
||||||
scheduler.RequestOutsideRenderPassOperationContext();
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
scheduler.Record([barrier](vk::CommandBuffer cmdbuf) {
|
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,
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||||
dst_stages_layout, 0, barrier);
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1168,6 +1168,7 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
|||||||
|
|
||||||
// Driver detection variables for workarounds in GetSuitability
|
// Driver detection variables for workarounds in GetSuitability
|
||||||
const VkDriverId driver_id = properties.driver.driverID;
|
const VkDriverId driver_id = properties.driver.driverID;
|
||||||
|
const bool is_intel_windows = driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS;
|
||||||
|
|
||||||
// VK_EXT_extended_dynamic_state2 below this will appear drivers that need workarounds.
|
// VK_EXT_extended_dynamic_state2 below this will appear drivers that need workarounds.
|
||||||
|
|
||||||
@@ -1184,14 +1185,15 @@ bool Device::GetSuitability(bool requires_swapchain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Intel Windows < 27.20.100.0: Broken VertexInputDynamicState
|
// Intel Windows < 27.20.100.0: Broken VertexInputDynamicState
|
||||||
// Same for NVIDIA Proprietary < 580.119.02, unknown when VIDS was first NOT broken
|
|
||||||
// Disable VertexInputDynamicState on old Intel Windows drivers
|
// Disable VertexInputDynamicState on old Intel Windows drivers
|
||||||
if (extensions.vertex_input_dynamic_state) {
|
if (extensions.vertex_input_dynamic_state && is_intel_windows) {
|
||||||
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
const u32 version = (properties.properties.driverVersion << 3) >> 3;
|
||||||
if ((driver_id == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS && version < VK_MAKE_API_VERSION(27, 20, 100, 0))
|
if (version < VK_MAKE_API_VERSION(27, 20, 100, 0)) {
|
||||||
|| (driver_id == VK_DRIVER_ID_NVIDIA_PROPRIETARY && version < VK_MAKE_API_VERSION(580, 119, 02, 0))) {
|
LOG_WARNING(Render_Vulkan,
|
||||||
LOG_WARNING(Render_Vulkan, "Disabling broken VK_EXT_vertex_input_dynamic_state");
|
"Intel Windows < 27.20.100.0: Disabling broken VK_EXT_vertex_input_dynamic_state");
|
||||||
RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
RemoveExtensionFeature(extensions.vertex_input_dynamic_state,
|
||||||
|
features.vertex_input_dynamic_state,
|
||||||
|
VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-16
@@ -156,14 +156,11 @@ add_executable(yuzu
|
|||||||
debugger/controller.cpp
|
debugger/controller.cpp
|
||||||
debugger/controller.h
|
debugger/controller.h
|
||||||
|
|
||||||
game/game_list.cpp
|
game_list.cpp
|
||||||
game/game_list.h
|
game_list.h
|
||||||
game/game_list_p.h
|
game_list_p.h
|
||||||
game/game_list_worker.cpp
|
game_list_worker.cpp
|
||||||
game/game_list_worker.h
|
game_list_worker.h
|
||||||
game/game_card.h
|
|
||||||
game/game_card.cpp
|
|
||||||
|
|
||||||
hotkeys.cpp
|
hotkeys.cpp
|
||||||
hotkeys.h
|
hotkeys.h
|
||||||
install_dialog.cpp
|
install_dialog.cpp
|
||||||
@@ -237,14 +234,10 @@ add_executable(yuzu
|
|||||||
data_dialog.h data_dialog.cpp data_dialog.ui
|
data_dialog.h data_dialog.cpp data_dialog.ui
|
||||||
data_widget.ui
|
data_widget.ui
|
||||||
ryujinx_dialog.h ryujinx_dialog.cpp ryujinx_dialog.ui
|
ryujinx_dialog.h ryujinx_dialog.cpp ryujinx_dialog.ui
|
||||||
main_window.h main_window.cpp main.ui
|
main_window.h main_window.cpp
|
||||||
|
|
||||||
overrides_updater.h
|
|
||||||
overrides_updater.cpp
|
|
||||||
|
|
||||||
configuration/system/new_user_dialog.h configuration/system/new_user_dialog.cpp configuration/system/new_user_dialog.ui
|
configuration/system/new_user_dialog.h configuration/system/new_user_dialog.cpp configuration/system/new_user_dialog.ui
|
||||||
configuration/system/profile_avatar_dialog.h configuration/system/profile_avatar_dialog.cpp
|
configuration/system/profile_avatar_dialog.h configuration/system/profile_avatar_dialog.cpp
|
||||||
configuration/addon/mod_select_dialog.h configuration/addon/mod_select_dialog.cpp configuration/addon/mod_select_dialog.ui
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
|
set_target_properties(yuzu PROPERTIES OUTPUT_NAME "eden")
|
||||||
@@ -364,17 +357,16 @@ target_sources(yuzu
|
|||||||
|
|
||||||
if (ENABLE_OPENSSL)
|
if (ENABLE_OPENSSL)
|
||||||
target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
target_link_libraries(yuzu PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||||
target_compile_definitions(yuzu PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
# Normal icns
|
# Normal icns
|
||||||
set(MACOSX_ICON "${CMAKE_SOURCE_DIR}/dist/eden.icns")
|
set(MACOSX_ICON "../../dist/eden.icns")
|
||||||
set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||||
target_sources(yuzu PRIVATE ${MACOSX_ICON})
|
target_sources(yuzu PRIVATE ${MACOSX_ICON})
|
||||||
|
|
||||||
# Liquid glass
|
# Liquid glass
|
||||||
set(MACOSX_LIQUID_GLASS_ICON "${CMAKE_SOURCE_DIR}/dist/Assets.car")
|
set(MACOSX_LIQUID_GLASS_ICON "../../dist/Assets.car")
|
||||||
set_source_files_properties(${MACOSX_LIQUID_GLASS_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
set_source_files_properties(${MACOSX_LIQUID_GLASS_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||||
target_sources(yuzu PRIVATE ${MACOSX_LIQUID_GLASS_ICON})
|
target_sources(yuzu PRIVATE ${MACOSX_LIQUID_GLASS_ICON})
|
||||||
|
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include "mod_select_dialog.h"
|
|
||||||
#include "ui_mod_select_dialog.h"
|
|
||||||
|
|
||||||
ModSelectDialog::ModSelectDialog(const QStringList& mods, QWidget* parent)
|
|
||||||
: QDialog(parent), ui(new Ui::ModSelectDialog) {
|
|
||||||
ui->setupUi(this);
|
|
||||||
|
|
||||||
item_model = new QStandardItemModel(ui->treeView);
|
|
||||||
ui->treeView->setModel(item_model);
|
|
||||||
|
|
||||||
// We must register all custom types with the Qt Automoc system so that we are able to use it
|
|
||||||
// with signals/slots. In this case, QList falls under the umbrella of custom types.
|
|
||||||
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
|
|
||||||
|
|
||||||
for (const auto& mod : mods) {
|
|
||||||
const auto basename = QFileInfo(mod).fileName();
|
|
||||||
|
|
||||||
auto* const first_item = new QStandardItem;
|
|
||||||
first_item->setText(basename);
|
|
||||||
first_item->setData(mod);
|
|
||||||
|
|
||||||
first_item->setCheckable(true);
|
|
||||||
first_item->setCheckState(Qt::Checked);
|
|
||||||
|
|
||||||
item_model->appendRow(first_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->treeView->expandAll();
|
|
||||||
ui->treeView->resizeColumnToContents(0);
|
|
||||||
|
|
||||||
int rows = item_model->rowCount();
|
|
||||||
int height =
|
|
||||||
ui->treeView->contentsMargins().top() * 4 + ui->treeView->contentsMargins().bottom() * 4;
|
|
||||||
int width = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < rows; ++i) {
|
|
||||||
height += ui->treeView->sizeHintForRow(i);
|
|
||||||
width = qMax(width, item_model->item(i)->sizeHint().width());
|
|
||||||
}
|
|
||||||
|
|
||||||
width += ui->treeView->contentsMargins().left() * 4 + ui->treeView->contentsMargins().right() * 4;
|
|
||||||
ui->treeView->setMinimumHeight(qMin(height, 600));
|
|
||||||
ui->treeView->setMinimumWidth(qMin(width, 700));
|
|
||||||
adjustSize();
|
|
||||||
|
|
||||||
connect(this, &QDialog::accepted, this, [this]() {
|
|
||||||
QStringList selected_mods;
|
|
||||||
|
|
||||||
for (qsizetype i = 0; i < item_model->rowCount(); ++i) {
|
|
||||||
auto* const item = item_model->item(i);
|
|
||||||
if (item->checkState() == Qt::Checked)
|
|
||||||
selected_mods << item->data().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
emit modsSelected(selected_mods);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ModSelectDialog::~ModSelectDialog() {
|
|
||||||
delete ui;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
#include <QStandardItemModel>
|
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class ModSelectDialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ModSelectDialog : public QDialog {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit ModSelectDialog(const QStringList &mods, QWidget* parent = nullptr);
|
|
||||||
~ModSelectDialog();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void modsSelected(const QStringList &mods);
|
|
||||||
private:
|
|
||||||
Ui::ModSelectDialog* ui;
|
|
||||||
|
|
||||||
QStandardItemModel* item_model;
|
|
||||||
};
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>ModSelectDialog</class>
|
|
||||||
<widget class="QDialog" name="ModSelectDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>400</width>
|
|
||||||
<height>430</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Dialog</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string>The specified folder or archive contains the following mods. Select which ones to install.</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QTreeView" name="treeView">
|
|
||||||
<property name="contextMenuPolicy">
|
|
||||||
<enum>Qt::ContextMenuPolicy::NoContextMenu</enum>
|
|
||||||
</property>
|
|
||||||
<property name="editTriggers">
|
|
||||||
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
|
||||||
</property>
|
|
||||||
<property name="alternatingRowColors">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="verticalScrollMode">
|
|
||||||
<enum>QAbstractItemView::ScrollMode::ScrollPerPixel</enum>
|
|
||||||
</property>
|
|
||||||
<property name="uniformRowHeights">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="sortingEnabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="headerHidden">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>accepted()</signal>
|
|
||||||
<receiver>ModSelectDialog</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>248</x>
|
|
||||||
<y>254</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>157</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>rejected()</signal>
|
|
||||||
<receiver>ModSelectDialog</receiver>
|
|
||||||
<slot>reject()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>316</x>
|
|
||||||
<y>260</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>286</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||||
@@ -99,8 +99,6 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(ui_tab.get(), &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged);
|
connect(ui_tab.get(), &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged);
|
||||||
connect(general_tab.get(), &ConfigureGeneral::ExternalContentDirsChanged, this,
|
|
||||||
&ConfigureDialog::ExternalContentDirsChanged);
|
|
||||||
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
|
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
|
||||||
&ConfigureDialog::UpdateVisibleTabs);
|
&ConfigureDialog::UpdateVisibleTabs);
|
||||||
|
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||||
@@ -62,7 +62,6 @@ private slots:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void LanguageChanged(const QString& locale);
|
void LanguageChanged(const QString& locale);
|
||||||
void ExternalContentDirsChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void changeEvent(QEvent* event) override;
|
void changeEvent(QEvent* event) override;
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
@@ -38,9 +38,9 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
|
|||||||
connect(ui->reset_game_list_cache, &QPushButton::pressed, this,
|
connect(ui->reset_game_list_cache, &QPushButton::pressed, this,
|
||||||
&ConfigureFilesystem::ResetMetadata);
|
&ConfigureFilesystem::ResetMetadata);
|
||||||
|
|
||||||
connect(ui->gamecard_inserted, &QCheckBox::stateChanged, this,
|
connect(ui->gamecard_inserted, &QCheckBox::STATE_CHANGED, this,
|
||||||
&ConfigureFilesystem::UpdateEnabledControls);
|
&ConfigureFilesystem::UpdateEnabledControls);
|
||||||
connect(ui->gamecard_current_game, &QCheckBox::stateChanged, this,
|
connect(ui->gamecard_current_game, &QCheckBox::STATE_CHANGED, this,
|
||||||
&ConfigureFilesystem::UpdateEnabledControls);
|
&ConfigureFilesystem::UpdateEnabledControls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +278,6 @@ void ConfigureFilesystem::UpdateEnabledControls() {
|
|||||||
!ui->gamecard_current_game->isChecked());
|
!ui->gamecard_current_game->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ConfigureFilesystem::RetranslateUI() {
|
void ConfigureFilesystem::RetranslateUI() {
|
||||||
ui->retranslateUi(this);
|
ui->retranslateUi(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||||
|
|||||||
@@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||||
@@ -7,9 +7,6 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QDir>
|
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QListWidget>
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@@ -32,15 +29,6 @@ ConfigureGeneral::ConfigureGeneral(const Core::System& system_,
|
|||||||
connect(ui->button_reset_defaults, &QPushButton::clicked, this,
|
connect(ui->button_reset_defaults, &QPushButton::clicked, this,
|
||||||
&ConfigureGeneral::ResetDefaults);
|
&ConfigureGeneral::ResetDefaults);
|
||||||
|
|
||||||
connect(ui->add_external_dir_button, &QPushButton::pressed, this,
|
|
||||||
&ConfigureGeneral::AddExternalContentDirectory);
|
|
||||||
connect(ui->remove_external_dir_button, &QPushButton::pressed, this,
|
|
||||||
&ConfigureGeneral::RemoveSelectedExternalContentDirectory);
|
|
||||||
connect(ui->external_content_list, &QListWidget::itemSelectionChanged, this, [this] {
|
|
||||||
ui->remove_external_dir_button->setEnabled(
|
|
||||||
!ui->external_content_list->selectedItems().isEmpty());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!Settings::IsConfiguringGlobal()) {
|
if (!Settings::IsConfiguringGlobal()) {
|
||||||
ui->button_reset_defaults->setVisible(false);
|
ui->button_reset_defaults->setVisible(false);
|
||||||
}
|
}
|
||||||
@@ -48,9 +36,7 @@ ConfigureGeneral::ConfigureGeneral(const Core::System& system_,
|
|||||||
|
|
||||||
ConfigureGeneral::~ConfigureGeneral() = default;
|
ConfigureGeneral::~ConfigureGeneral() = default;
|
||||||
|
|
||||||
void ConfigureGeneral::SetConfiguration() {
|
void ConfigureGeneral::SetConfiguration() {}
|
||||||
UpdateExternalContentList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
|
void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) {
|
||||||
QLayout& general_layout = *ui->general_widget->layout();
|
QLayout& general_layout = *ui->general_widget->layout();
|
||||||
@@ -115,55 +101,6 @@ void ConfigureGeneral::ApplyConfiguration() {
|
|||||||
for (const auto& func : apply_funcs) {
|
for (const auto& func : apply_funcs) {
|
||||||
func(powered_on);
|
func(powered_on);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> new_dirs;
|
|
||||||
new_dirs.reserve(ui->external_content_list->count());
|
|
||||||
for (int i = 0; i < ui->external_content_list->count(); ++i) {
|
|
||||||
new_dirs.push_back(ui->external_content_list->item(i)->text().toStdString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new_dirs != Settings::values.external_content_dirs) {
|
|
||||||
Settings::values.external_content_dirs = std::move(new_dirs);
|
|
||||||
emit ExternalContentDirsChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigureGeneral::UpdateExternalContentList() {
|
|
||||||
ui->external_content_list->clear();
|
|
||||||
for (const auto& dir : Settings::values.external_content_dirs) {
|
|
||||||
ui->external_content_list->addItem(QString::fromStdString(dir));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigureGeneral::AddExternalContentDirectory() {
|
|
||||||
const QString dir_path = QFileDialog::getExistingDirectory(
|
|
||||||
this, tr("Select External Content Directory..."), QString());
|
|
||||||
|
|
||||||
if (dir_path.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString normalized_path = QDir::toNativeSeparators(dir_path);
|
|
||||||
if (normalized_path.back() != QDir::separator()) {
|
|
||||||
normalized_path.append(QDir::separator());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < ui->external_content_list->count(); ++i) {
|
|
||||||
if (ui->external_content_list->item(i)->text() == normalized_path) {
|
|
||||||
QMessageBox::information(this, tr("Directory Already Added"),
|
|
||||||
tr("This directory is already in the list."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->external_content_list->addItem(normalized_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConfigureGeneral::RemoveSelectedExternalContentDirectory() {
|
|
||||||
auto selected = ui->external_content_list->selectedItems();
|
|
||||||
if (!selected.isEmpty()) {
|
|
||||||
qDeleteAll(ui->external_content_list->selectedItems());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureGeneral::changeEvent(QEvent* event) {
|
void ConfigureGeneral::changeEvent(QEvent* event) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user