summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--CMakeLists.txt42
-rw-r--r--externals/nx_tzdb/CMakeLists.txt2
-rw-r--r--src/android/app/build.gradle.kts16
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt117
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt186
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt75
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt29
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt41
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt158
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt97
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt228
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt112
-rw-r--r--src/android/app/src/main/res/drawable/ic_build.xml9
-rw-r--r--src/android/app/src/main/res/drawable/ic_delete.xml9
-rw-r--r--src/android/app/src/main/res/layout/card_driver_option.xml89
-rw-r--r--src/android/app/src/main/res/layout/fragment_driver_manager.xml48
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml7
-rw-r--r--src/android/app/src/main/res/values-de/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-es/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-fr/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-it/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-ja/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-ko/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-nb/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-pl/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-pt-rBR/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-pt-rPT/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-ru/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-uk/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-zh-rCN/strings.xml2
-rw-r--r--src/android/app/src/main/res/values-zh-rTW/strings.xml2
-rw-r--r--src/android/app/src/main/res/values/dimens.xml2
-rw-r--r--src/android/app/src/main/res/values/strings.xml7
-rw-r--r--src/android/build.gradle.kts4
-rw-r--r--src/common/common_funcs.h4
-rw-r--r--src/common/elf.h8
-rw-r--r--src/common/polyfill_thread.h21
-rw-r--r--src/core/hle/kernel/k_page_table.cpp5
-rw-r--r--src/core/hle/kernel/kernel.cpp16
-rw-r--r--src/core/hle/service/caps/caps.cpp2
-rw-r--r--src/core/hle/service/caps/caps_a.cpp4
-rw-r--r--src/core/hle/service/caps/caps_manager.cpp86
-rw-r--r--src/core/hle/service/caps/caps_manager.h9
-rw-r--r--src/core/hle/service/caps/caps_types.h14
-rw-r--r--src/core/hle/service/caps/caps_u.cpp74
-rw-r--r--src/core/hle/service/jit/jit_context.cpp36
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp28
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h2
-rw-r--r--src/video_core/texture_cache/texture_cache.h1
-rw-r--r--src/yuzu/game_list.cpp5
-rw-r--r--src/yuzu/game_list_worker.cpp7
-rw-r--r--src/yuzu/game_list_worker.h5
-rw-r--r--src/yuzu/main.cpp68
61 files changed, 1340 insertions, 461 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2bef9d6ed..1108e5ae6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,7 +11,6 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modul
include(DownloadExternals)
include(CMakeDependentOption)
include(CTest)
-include(FetchContent)
# Set bundled sdl2/qt as dependent options.
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
@@ -99,47 +98,8 @@ if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
DESTINATION "${vvl_lib_path}")
endif()
-# On Android, fetch and compile libcxx before doing anything else
if (ANDROID)
set(CMAKE_SKIP_INSTALL_RULES ON)
- set(LLVM_VERSION "15.0.6")
-
- # Note: even though libcxx and libcxxabi have separate releases on the project page,
- # the separated releases cannot be compiled. Only in-tree builds work. Therefore we
- # must fetch the source release for the entire llvm tree.
- FetchContent_Declare(llvm
- URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/llvm-project-${LLVM_VERSION}.src.tar.xz"
- URL_HASH SHA256=9d53ad04dc60cb7b30e810faf64c5ab8157dadef46c8766f67f286238256ff92
- TLS_VERIFY TRUE
- )
- FetchContent_MakeAvailable(llvm)
-
- # libcxx has support for most of the range library, but it's gated behind a flag:
- add_compile_definitions(_LIBCPP_ENABLE_EXPERIMENTAL)
-
- # Disable standard header inclusion
- set(ANDROID_STL "none")
-
- # libcxxabi
- set(LIBCXXABI_INCLUDE_TESTS OFF)
- set(LIBCXXABI_ENABLE_SHARED FALSE)
- set(LIBCXXABI_ENABLE_STATIC TRUE)
- set(LIBCXXABI_LIBCXX_INCLUDES "${LIBCXX_TARGET_INCLUDE_DIRECTORY}" CACHE STRING "" FORCE)
- add_subdirectory("${llvm_SOURCE_DIR}/libcxxabi" "${llvm_BINARY_DIR}/libcxxabi")
- link_libraries(cxxabi_static)
-
- # libcxx
- set(LIBCXX_ABI_NAMESPACE "__ndk1" CACHE STRING "" FORCE)
- set(LIBCXX_CXX_ABI "libcxxabi")
- set(LIBCXX_INCLUDE_TESTS OFF)
- set(LIBCXX_INCLUDE_BENCHMARKS OFF)
- set(LIBCXX_INCLUDE_DOCS OFF)
- set(LIBCXX_ENABLE_SHARED FALSE)
- set(LIBCXX_ENABLE_STATIC TRUE)
- set(LIBCXX_ENABLE_ASSERTIONS FALSE)
- add_subdirectory("${llvm_SOURCE_DIR}/libcxx" "${llvm_BINARY_DIR}/libcxx")
- set_target_properties(cxx-headers PROPERTIES INTERFACE_COMPILE_OPTIONS "-isystem${CMAKE_BINARY_DIR}/${LIBCXX_INSTALL_INCLUDE_DIR}")
- link_libraries(cxx_static cxx-headers)
endif()
if (YUZU_USE_BUNDLED_VCPKG)
@@ -329,7 +289,7 @@ find_package(Boost 1.79.0 REQUIRED context)
find_package(enet 1.3 MODULE)
find_package(fmt 9 REQUIRED)
find_package(inih 52 MODULE COMPONENTS INIReader)
-find_package(LLVM 17 MODULE COMPONENTS Demangle)
+find_package(LLVM 17.0.2 MODULE COMPONENTS Demangle)
find_package(lz4 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED)
find_package(Opus 1.3 MODULE)
diff --git a/externals/nx_tzdb/CMakeLists.txt b/externals/nx_tzdb/CMakeLists.txt
index 593786250..0fad24642 100644
--- a/externals/nx_tzdb/CMakeLists.txt
+++ b/externals/nx_tzdb/CMakeLists.txt
@@ -27,7 +27,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
set(CAN_BUILD_NX_TZDB false)
endif()
-set(NX_TZDB_VERSION "220816")
+set(NX_TZDB_VERSION "221202")
set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 84a3308b7..ac43d84b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -27,7 +27,7 @@ android {
namespace = "org.yuzu.yuzu_emu"
compileSdkVersion = "android-34"
- ndkVersion = "25.2.9519653"
+ ndkVersion = "26.1.10909125"
buildFeatures {
viewBinding = true
@@ -203,23 +203,23 @@ ktlint {
}
dependencies {
- implementation("androidx.core:core-ktx:1.10.1")
+ implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
- implementation("androidx.recyclerview:recyclerview:1.3.0")
+ implementation("androidx.recyclerview:recyclerview:1.3.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
- implementation("androidx.fragment:fragment-ktx:1.6.0")
+ implementation("androidx.fragment:fragment-ktx:1.6.1")
implementation("androidx.documentfile:documentfile:1.0.1")
implementation("com.google.android.material:material:1.9.0")
- implementation("androidx.preference:preference:1.2.0")
- implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
+ implementation("androidx.preference:preference-ktx:1.2.1")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.window:window:1.2.0-beta03")
implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
- implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
- implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
+ implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
+ implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 832c08e15..a67351727 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -28,7 +28,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:appCategory="game"
android:localeConfig="@xml/locales_config"
android:banner="@drawable/tv_banner"
- android:extractNativeLibs="true"
android:fullBackupContent="@xml/data_extraction_rules"
android:dataExtractionRules="@xml/data_extraction_rules_api_31"
android:enableOnBackInvokedCallback="true">
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index 6e39e542b..115f72710 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -15,13 +15,9 @@ import androidx.annotation.Keep
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference
-import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
-import org.yuzu.yuzu_emu.utils.FileUtil.exists
-import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
-import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
-import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
+import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
@@ -75,7 +71,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
} else {
- openContentUri(appContext, path, openmode)
+ FileUtil.openContentUri(path, openmode)
}
}
@@ -85,7 +81,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.getFileSize(path)
} else {
- getFileSize(appContext, path)
+ FileUtil.getFileSize(path)
}
}
@@ -95,7 +91,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.exists(path)
} else {
- exists(appContext, path)
+ FileUtil.exists(path)
}
}
@@ -105,7 +101,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.isDirectory(path)
} else {
- isDirectory(appContext, path)
+ FileUtil.isDirectory(path)
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
index 9561748cb..8c053670c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
@@ -47,7 +47,7 @@ class YuzuApplication : Application() {
application = this
documentsTree = DocumentsTree()
DirectoryInitialization.start()
- GpuDriverHelper.initializeDriverParameters(applicationContext)
+ GpuDriverHelper.initializeDriverParameters()
NativeLibrary.logDeviceInfo()
createNotificationChannels()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
new file mode 100644
index 000000000..0e818cab9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
@@ -0,0 +1,117 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.adapters
+
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.AsyncDifferConfig
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListAdapter
+import androidx.recyclerview.widget.RecyclerView
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
+import org.yuzu.yuzu_emu.model.DriverViewModel
+import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
+
+class DriverAdapter(private val driverViewModel: DriverViewModel) :
+ ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
+ AsyncDifferConfig.Builder(DiffCallback()).build()
+ ) {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
+ val binding =
+ CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return DriverViewHolder(binding)
+ }
+
+ override fun getItemCount(): Int = currentList.size
+
+ override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
+ holder.bind(currentList[position])
+
+ private fun onSelectDriver(position: Int) {
+ driverViewModel.setSelectedDriverIndex(position)
+ notifyItemChanged(driverViewModel.previouslySelectedDriver)
+ notifyItemChanged(driverViewModel.selectedDriver)
+ }
+
+ private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
+ if (driverViewModel.selectedDriver > position) {
+ driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
+ }
+ if (GpuDriverHelper.customDriverData == driverData.second) {
+ driverViewModel.setSelectedDriverIndex(0)
+ }
+ driverViewModel.driversToDelete.add(driverData.first)
+ driverViewModel.removeDriver(driverData)
+ notifyItemRemoved(position)
+ notifyItemChanged(driverViewModel.selectedDriver)
+ }
+
+ inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+ private lateinit var driverData: Pair<String, GpuDriverMetadata>
+
+ fun bind(driverData: Pair<String, GpuDriverMetadata>) {
+ this.driverData = driverData
+ val driver = driverData.second
+
+ binding.apply {
+ radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
+ root.setOnClickListener {
+ onSelectDriver(bindingAdapterPosition)
+ }
+ buttonDelete.setOnClickListener {
+ onDeleteDriver(driverData, bindingAdapterPosition)
+ }
+
+ // Delay marquee by 3s
+ title.postDelayed(
+ {
+ title.isSelected = true
+ title.ellipsize = TextUtils.TruncateAt.MARQUEE
+ version.isSelected = true
+ version.ellipsize = TextUtils.TruncateAt.MARQUEE
+ description.isSelected = true
+ description.ellipsize = TextUtils.TruncateAt.MARQUEE
+ },
+ 3000
+ )
+ if (driver.name == null) {
+ title.setText(R.string.system_gpu_driver)
+ description.text = ""
+ version.text = ""
+ version.visibility = View.GONE
+ description.visibility = View.GONE
+ buttonDelete.visibility = View.GONE
+ } else {
+ title.text = driver.name
+ version.text = driver.version
+ description.text = driver.description
+ version.visibility = View.VISIBLE
+ description.visibility = View.VISIBLE
+ buttonDelete.visibility = View.VISIBLE
+ }
+ }
+ }
+ }
+
+ private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
+ override fun areItemsTheSame(
+ oldItem: Pair<String, GpuDriverMetadata>,
+ newItem: Pair<String, GpuDriverMetadata>
+ ): Boolean {
+ return oldItem.first == newItem.first
+ }
+
+ override fun areContentsTheSame(
+ oldItem: Pair<String, GpuDriverMetadata>,
+ newItem: Pair<String, GpuDriverMetadata>
+ ): Boolean {
+ return oldItem.second == newItem.second
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
new file mode 100644
index 000000000..df21d74b2
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -0,0 +1,186 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.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.activity.result.contract.ActivityResultContracts
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.updatePadding
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.findNavController
+import androidx.recyclerview.widget.GridLayoutManager
+import com.google.android.material.transition.MaterialSharedAxis
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.adapters.DriverAdapter
+import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
+import org.yuzu.yuzu_emu.model.DriverViewModel
+import org.yuzu.yuzu_emu.model.HomeViewModel
+import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import java.io.File
+import java.io.IOException
+
+class DriverManagerFragment : Fragment() {
+ private var _binding: FragmentDriverManagerBinding? = null
+ private val binding get() = _binding!!
+
+ private val homeViewModel: HomeViewModel by activityViewModels()
+ private val driverViewModel: DriverViewModel by activityViewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
+ returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentDriverManagerBinding.inflate(inflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ homeViewModel.setNavigationVisibility(visible = false, animated = true)
+ homeViewModel.setStatusBarShadeVisibility(visible = false)
+
+ if (!driverViewModel.isInteractionAllowed) {
+ DriversLoadingDialogFragment().show(
+ childFragmentManager,
+ DriversLoadingDialogFragment.TAG
+ )
+ }
+
+ binding.toolbarDrivers.setNavigationOnClickListener {
+ binding.root.findNavController().popBackStack()
+ }
+
+ binding.buttonInstall.setOnClickListener {
+ getDriver.launch(arrayOf("application/zip"))
+ }
+
+ binding.listDrivers.apply {
+ layoutManager = GridLayoutManager(
+ requireContext(),
+ resources.getInteger(R.integer.grid_columns)
+ )
+ adapter = DriverAdapter(driverViewModel)
+ }
+
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ driverViewModel.driverList.collectLatest {
+ (binding.listDrivers.adapter as DriverAdapter).submitList(it)
+ }
+ }
+ launch {
+ driverViewModel.newDriverInstalled.collect {
+ if (_binding != null && it) {
+ (binding.listDrivers.adapter as DriverAdapter).apply {
+ notifyItemChanged(driverViewModel.previouslySelectedDriver)
+ notifyItemChanged(driverViewModel.selectedDriver)
+ driverViewModel.setNewDriverInstalled(false)
+ }
+ }
+ }
+ }
+ }
+
+ setInsets()
+ }
+
+ // Start installing requested driver
+ override fun onStop() {
+ super.onStop()
+ driverViewModel.onCloseDriverManager()
+ }
+
+ 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 mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams
+ mlpAppBar.leftMargin = leftInsets
+ mlpAppBar.rightMargin = rightInsets
+ binding.toolbarDrivers.layoutParams = mlpAppBar
+
+ val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams
+ mlplistDrivers.leftMargin = leftInsets
+ mlplistDrivers.rightMargin = rightInsets
+ binding.listDrivers.layoutParams = mlplistDrivers
+
+ val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab)
+ val mlpFab =
+ binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams
+ mlpFab.leftMargin = leftInsets + fabSpacing
+ mlpFab.rightMargin = rightInsets + fabSpacing
+ mlpFab.bottomMargin = barInsets.bottom + fabSpacing
+ binding.buttonInstall.layoutParams = mlpFab
+
+ binding.listDrivers.updatePadding(
+ bottom = barInsets.bottom +
+ resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab)
+ )
+
+ windowInsets
+ }
+
+ private val getDriver =
+ registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
+ if (result == null) {
+ return@registerForActivityResult
+ }
+
+ IndeterminateProgressDialogFragment.newInstance(
+ requireActivity(),
+ R.string.installing_driver,
+ false
+ ) {
+ val driverPath =
+ "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
+ val driverFile = File(driverPath)
+
+ // Ignore file exceptions when a user selects an invalid zip
+ try {
+ if (!GpuDriverHelper.copyDriverToInternalStorage(result)) {
+ throw IOException("Driver failed validation!")
+ }
+ } catch (_: IOException) {
+ if (driverFile.exists()) {
+ driverFile.delete()
+ }
+ return@newInstance getString(R.string.select_gpu_driver_error)
+ }
+
+ val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
+ val driverInList =
+ driverViewModel.driverList.value.firstOrNull { it.second == driverData }
+ if (driverInList != null) {
+ return@newInstance getString(R.string.driver_already_installed)
+ } else {
+ driverViewModel.addDriver(Pair(driverPath, driverData))
+ driverViewModel.setNewDriverInstalled(true)
+ }
+ return@newInstance Any()
+ }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
new file mode 100644
index 000000000..f8c34346a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
+import org.yuzu.yuzu_emu.model.DriverViewModel
+
+class DriversLoadingDialogFragment : DialogFragment() {
+ private val driverViewModel: DriverViewModel by activityViewModels()
+
+ private lateinit var binding: DialogProgressBarBinding
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ binding = DialogProgressBarBinding.inflate(layoutInflater)
+ binding.progressBar.isIndeterminate = true
+
+ isCancelable = false
+
+ return MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.loading)
+ .setView(binding.root)
+ .create()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View = binding.root
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ viewLifecycleOwner.lifecycleScope.apply {
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ driverViewModel.areDriversLoading.collect { checkForDismiss() }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ driverViewModel.isDriverReady.collect { checkForDismiss() }
+ }
+ }
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ driverViewModel.isDeletingDrivers.collect { checkForDismiss() }
+ }
+ }
+ }
+ }
+
+ private fun checkForDismiss() {
+ if (driverViewModel.isInteractionAllowed) {
+ dismiss()
+ }
+ }
+
+ companion object {
+ const val TAG = "DriversLoadingDialogFragment"
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index e6ad2aa77..598a9d42b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -50,6 +51,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -70,6 +72,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private lateinit var game: Game
private val emulationViewModel: EmulationViewModel by activityViewModels()
+ private val driverViewModel: DriverViewModel by activityViewModels()
private var isInFoldableLayout = false
@@ -299,6 +302,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
}
+ launch {
+ repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ driverViewModel.isDriverReady.collect {
+ if (it && !emulationState.isRunning) {
+ if (!DirectoryInitialization.areDirectoriesReady) {
+ DirectoryInitialization.start()
+ }
+
+ updateScreenLayout()
+
+ emulationState.run(emulationActivity!!.isActivityRecreated)
+ }
+ }
+ }
+ }
}
}
@@ -332,17 +350,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
- override fun onResume() {
- super.onResume()
- if (!DirectoryInitialization.areDirectoriesReady) {
- DirectoryInitialization.start()
- }
-
- updateScreenLayout()
-
- emulationState.run(emulationActivity!!.isActivityRecreated)
- }
-
override fun onPause() {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index 8923c0ea2..fd9785075 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.fragments
import android.Manifest
import android.content.ActivityNotFoundException
-import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
@@ -28,7 +27,6 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController
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.BuildConfig
import org.yuzu.yuzu_emu.HomeNavigationDirections
@@ -37,6 +35,7 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -50,6 +49,7 @@ class HomeSettingsFragment : Fragment() {
private lateinit var mainActivity: MainActivity
private val homeViewModel: HomeViewModel by activityViewModels()
+ private val driverViewModel: DriverViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -107,13 +107,17 @@ class HomeSettingsFragment : Fragment() {
)
add(
HomeSetting(
- R.string.install_gpu_driver,
+ R.string.gpu_driver_manager,
R.string.install_gpu_driver_description,
- R.drawable.ic_exit,
- { driverInstaller() },
+ R.drawable.ic_build,
+ {
+ binding.root.findNavController()
+ .navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
+ },
{ GpuDriverHelper.supportsCustomDriverLoading() },
R.string.custom_driver_not_supported,
- R.string.custom_driver_not_supported_description
+ R.string.custom_driver_not_supported_description,
+ driverViewModel.selectedDriverMetadata
)
)
add(
@@ -292,31 +296,6 @@ class HomeSettingsFragment : Fragment() {
}
}
- private fun driverInstaller() {
- // Get the driver name for the dialog message.
- var driverName = GpuDriverHelper.customDriverName
- if (driverName == null) {
- driverName = getString(R.string.system_gpu_driver)
- }
-
- MaterialAlertDialogBuilder(requireContext())
- .setTitle(getString(R.string.select_gpu_driver_title))
- .setMessage(driverName)
- .setNegativeButton(android.R.string.cancel, null)
- .setNeutralButton(R.string.select_gpu_driver_default) { _: DialogInterface?, _: Int ->
- GpuDriverHelper.installDefaultDriver(requireContext())
- Toast.makeText(
- requireContext(),
- R.string.select_gpu_driver_use_default,
- Toast.LENGTH_SHORT
- ).show()
- }
- .setPositiveButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
- mainActivity.getDriver.launch(arrayOf("application/zip"))
- }
- .show()
- }
-
private fun shareLog() {
val file = DocumentFile.fromSingleUri(
mainActivity,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
index f128deda8..7e467814d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -10,8 +10,8 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
-import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
@@ -78,6 +78,10 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
requireActivity().supportFragmentManager,
MessageDialogFragment.TAG
)
+
+ else -> {
+ // Do nothing
+ }
}
taskViewModel.clear()
}
@@ -115,7 +119,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
private const val CANCELLABLE = "Cancellable"
fun newInstance(
- activity: AppCompatActivity,
+ activity: FragmentActivity,
titleId: Int,
cancellable: Boolean = false,
task: () -> Any
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
new file mode 100644
index 000000000..62945ad65
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
@@ -0,0 +1,158 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
+import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
+import java.io.BufferedOutputStream
+import java.io.File
+
+class DriverViewModel : ViewModel() {
+ private val _areDriversLoading = MutableStateFlow(false)
+ val areDriversLoading: StateFlow<Boolean> get() = _areDriversLoading
+
+ private val _isDriverReady = MutableStateFlow(true)
+ val isDriverReady: StateFlow<Boolean> get() = _isDriverReady
+
+ private val _isDeletingDrivers = MutableStateFlow(false)
+ val isDeletingDrivers: StateFlow<Boolean> get() = _isDeletingDrivers
+
+ private val _driverList = MutableStateFlow(mutableListOf<Pair<String, GpuDriverMetadata>>())
+ val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
+
+ var previouslySelectedDriver = 0
+ var selectedDriver = -1
+
+ private val _selectedDriverMetadata =
+ MutableStateFlow(
+ GpuDriverHelper.customDriverData.name
+ ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+ )
+ val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata
+
+ private val _newDriverInstalled = MutableStateFlow(false)
+ val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
+
+ val driversToDelete = mutableListOf<String>()
+
+ val isInteractionAllowed
+ get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value
+
+ init {
+ _areDriversLoading.value = true
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ val drivers = GpuDriverHelper.getDrivers()
+ val currentDriverMetadata = GpuDriverHelper.customDriverData
+ for (i in drivers.indices) {
+ if (drivers[i].second == currentDriverMetadata) {
+ setSelectedDriverIndex(i)
+ break
+ }
+ }
+
+ // If a user had installed a driver before the manager was implemented, this zips
+ // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
+ // be indexed and exported as expected.
+ if (selectedDriver == -1) {
+ val driverToSave =
+ File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
+ driverToSave.createNewFile()
+ FileUtil.zipFromInternalStorage(
+ File(GpuDriverHelper.driverInstallationPath!!),
+ GpuDriverHelper.driverInstallationPath!!,
+ BufferedOutputStream(driverToSave.outputStream())
+ )
+ drivers.add(Pair(driverToSave.path, currentDriverMetadata))
+ setSelectedDriverIndex(drivers.size - 1)
+ }
+
+ _driverList.value = drivers
+ _areDriversLoading.value = false
+ }
+ }
+ }
+
+ fun setSelectedDriverIndex(value: Int) {
+ if (selectedDriver != -1) {
+ previouslySelectedDriver = selectedDriver
+ }
+ selectedDriver = value
+ }
+
+ fun setNewDriverInstalled(value: Boolean) {
+ _newDriverInstalled.value = value
+ }
+
+ fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
+ val driverIndex = _driverList.value.indexOfFirst { it == driverData }
+ if (driverIndex == -1) {
+ setSelectedDriverIndex(_driverList.value.size)
+ _driverList.value.add(driverData)
+ _selectedDriverMetadata.value = driverData.second.name
+ ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+ } else {
+ setSelectedDriverIndex(driverIndex)
+ }
+ }
+
+ fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
+ _driverList.value.remove(driverData)
+ }
+
+ fun onCloseDriverManager() {
+ _isDeletingDrivers.value = true
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ driversToDelete.forEach {
+ val driver = File(it)
+ if (driver.exists()) {
+ driver.delete()
+ }
+ }
+ driversToDelete.clear()
+ _isDeletingDrivers.value = false
+ }
+ }
+
+ if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) {
+ return
+ }
+
+ _isDriverReady.value = false
+ viewModelScope.launch {
+ withContext(Dispatchers.IO) {
+ if (selectedDriver == 0) {
+ GpuDriverHelper.installDefaultDriver()
+ setDriverReady()
+ return@withContext
+ }
+
+ val driverToInstall = File(driverList.value[selectedDriver].first)
+ if (driverToInstall.exists()) {
+ GpuDriverHelper.installCustomDriver(driverToInstall)
+ } else {
+ GpuDriverHelper.installDefaultDriver()
+ }
+ setDriverReady()
+ }
+ }
+ }
+
+ private fun setDriverReady() {
+ _isDriverReady.value = true
+ _selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name
+ ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 0fa5df5e5..233aa4101 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -29,12 +29,10 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationBarView
import kotlinx.coroutines.CoroutineScope
import java.io.File
import java.io.FilenameFilter
-import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -43,7 +41,6 @@ import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
-import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
@@ -343,11 +340,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
if (FileUtil.copyUriToInternalStorage(
- applicationContext,
result,
dstPath,
"prod.keys"
- )
+ ) != null
) {
if (NativeLibrary.reloadKeys()) {
Toast.makeText(
@@ -446,11 +442,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
if (FileUtil.copyUriToInternalStorage(
- applicationContext,
result,
dstPath,
"key_retail.bin"
- )
+ ) != null
) {
if (NativeLibrary.reloadKeys()) {
Toast.makeText(
@@ -469,59 +464,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
- val getDriver =
- registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
- if (result == null) {
- return@registerForActivityResult
- }
-
- val takeFlags =
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
- contentResolver.takePersistableUriPermission(
- result,
- takeFlags
- )
-
- val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
- progressBinding.progressBar.isIndeterminate = true
- val installationDialog = MaterialAlertDialogBuilder(this)
- .setTitle(R.string.installing_driver)
- .setView(progressBinding.root)
- .show()
-
- lifecycleScope.launch {
- withContext(Dispatchers.IO) {
- // Ignore file exceptions when a user selects an invalid zip
- try {
- GpuDriverHelper.installCustomDriver(applicationContext, result)
- } catch (_: IOException) {
- }
-
- withContext(Dispatchers.Main) {
- installationDialog.dismiss()
-
- val driverName = GpuDriverHelper.customDriverName
- if (driverName != null) {
- Toast.makeText(
- applicationContext,
- getString(
- R.string.select_gpu_driver_install_success,
- driverName
- ),
- Toast.LENGTH_SHORT
- ).show()
- } else {
- Toast.makeText(
- applicationContext,
- R.string.select_gpu_driver_error,
- Toast.LENGTH_LONG
- ).show()
- }
- }
- }
- }
- }
-
val installGameUpdate = registerForActivityResult(
ActivityResultContracts.OpenMultipleDocuments()
) { documents: List<Uri> ->
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
index cf226ad94..eafcf9e42 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
@@ -7,7 +7,6 @@ import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import java.io.File
import java.util.*
-import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
class DocumentsTree {
@@ -22,7 +21,7 @@ class DocumentsTree {
fun openContentUri(filepath: String, openMode: String?): Int {
val node = resolvePath(filepath) ?: return -1
- return FileUtil.openContentUri(YuzuApplication.appContext, node.uri.toString(), openMode)
+ return FileUtil.openContentUri(node.uri.toString(), openMode)
}
fun getFileSize(filepath: String): Long {
@@ -30,7 +29,7 @@ class DocumentsTree {
return if (node == null || node.isDirectory) {
0
} else {
- FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
+ FileUtil.getFileSize(node.uri.toString())
}
}
@@ -67,7 +66,7 @@ class DocumentsTree {
* @param parent parent node of this level
*/
private fun structTree(parent: DocumentsNode) {
- val documents = FileUtil.listFiles(YuzuApplication.appContext, parent.uri!!)
+ val documents = FileUtil.listFiles(parent.uri!!)
for (document in documents) {
val node = DocumentsNode(document)
node.parent = parent
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index c3f53f1c5..5ee74a52c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -3,7 +3,6 @@
package org.yuzu.yuzu_emu.utils
-import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
@@ -11,7 +10,6 @@ import androidx.documentfile.provider.DocumentFile
import kotlinx.coroutines.flow.StateFlow
import java.io.BufferedInputStream
import java.io.File
-import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.net.URLDecoder
@@ -21,6 +19,8 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import org.yuzu.yuzu_emu.model.TaskState
import java.io.BufferedOutputStream
+import java.lang.NullPointerException
+import java.nio.charset.StandardCharsets
import java.util.zip.ZipOutputStream
object FileUtil {
@@ -29,6 +29,8 @@ object FileUtil {
const val APPLICATION_OCTET_STREAM = "application/octet-stream"
const val TEXT_PLAIN = "text/plain"
+ private val context get() = YuzuApplication.appContext
+
/**
* Create a file from directory with filename.
* @param context Application context
@@ -36,11 +38,11 @@ object FileUtil {
* @param filename file display name.
* @return boolean
*/
- fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? {
+ fun createFile(directory: String?, filename: String): DocumentFile? {
var decodedFilename = filename
try {
val directoryUri = Uri.parse(directory)
- val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
+ val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
var mimeType = APPLICATION_OCTET_STREAM
if (decodedFilename.endsWith(".txt")) {
@@ -56,16 +58,15 @@ object FileUtil {
/**
* Create a directory from directory with filename.
- * @param context Application context
* @param directory parent path for directory.
* @param directoryName directory display name.
* @return boolean
*/
- fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? {
+ fun createDir(directory: String?, directoryName: String?): DocumentFile? {
var decodedDirectoryName = directoryName
try {
val directoryUri = Uri.parse(directory)
- val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
+ val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
val isExist = parent.findFile(decodedDirectoryName)
return isExist ?: parent.createDirectory(decodedDirectoryName)
@@ -77,13 +78,12 @@ object FileUtil {
/**
* Open content uri and return file descriptor to JNI.
- * @param context Application context
* @param path Native content uri path
* @param openMode will be one of "r", "r", "rw", "wa", "rwa"
* @return file descriptor
*/
@JvmStatic
- fun openContentUri(context: Context, path: String, openMode: String?): Int {
+ fun openContentUri(path: String, openMode: String?): Int {
try {
val uri = Uri.parse(path)
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
@@ -103,11 +103,10 @@ object FileUtil {
/**
* Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
* This function will be faster than DoucmentFile.listFiles
- * @param context Application context
* @param uri Directory uri.
* @return CheapDocument lists.
*/
- fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> {
+ fun listFiles(uri: Uri): Array<MinimalDocumentFile> {
val resolver = context.contentResolver
val columns = arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -145,7 +144,7 @@ object FileUtil {
* @param path Native content uri path
* @return bool
*/
- fun exists(context: Context, path: String?): Boolean {
+ fun exists(path: String?): Boolean {
var c: Cursor? = null
try {
val mUri = Uri.parse(path)
@@ -165,7 +164,7 @@ object FileUtil {
* @param path content uri path
* @return bool
*/
- fun isDirectory(context: Context, path: String): Boolean {
+ fun isDirectory(path: String): Boolean {
val resolver = context.contentResolver
val columns = arrayOf(
DocumentsContract.Document.COLUMN_MIME_TYPE
@@ -210,10 +209,10 @@ object FileUtil {
return filename
}
- fun getFilesName(context: Context, path: String): Array<String> {
+ fun getFilesName(path: String): Array<String> {
val uri = Uri.parse(path)
val files: MutableList<String> = ArrayList()
- for (file in listFiles(context, uri)) {
+ for (file in listFiles(uri)) {
files.add(file.filename)
}
return files.toTypedArray()
@@ -225,7 +224,7 @@ object FileUtil {
* @return long file size
*/
@JvmStatic
- fun getFileSize(context: Context, path: String): Long {
+ fun getFileSize(path: String): Long {
val resolver = context.contentResolver
val columns = arrayOf(
DocumentsContract.Document.COLUMN_SIZE
@@ -245,44 +244,38 @@ object FileUtil {
return size
}
+ /**
+ * Creates an input stream with a given [Uri] and copies its data to the given path. This will
+ * overwrite any pre-existing files.
+ *
+ * @param sourceUri The [Uri] to copy data from
+ * @param destinationParentPath Destination directory
+ * @param destinationFilename Optionally renames the file once copied
+ */
fun copyUriToInternalStorage(
- context: Context,
- sourceUri: Uri?,
+ sourceUri: Uri,
destinationParentPath: String,
- destinationFilename: String
- ): Boolean {
- var input: InputStream? = null
- var output: FileOutputStream? = null
+ destinationFilename: String = ""
+ ): File? =
try {
- input = context.contentResolver.openInputStream(sourceUri!!)
- output = FileOutputStream("$destinationParentPath/$destinationFilename")
- val buffer = ByteArray(1024)
- var len: Int
- while (input!!.read(buffer).also { len = it } != -1) {
- output.write(buffer, 0, len)
- }
- output.flush()
- return true
- } catch (e: Exception) {
- Log.error("[FileUtil]: Cannot copy file, error: " + e.message)
- } finally {
- if (input != null) {
- try {
- input.close()
- } catch (e: IOException) {
- Log.error("[FileUtil]: Cannot close input file, error: " + e.message)
- }
+ val fileName =
+ if (destinationFilename == "") getFilename(sourceUri) else "/$destinationFilename"
+ val inputStream = context.contentResolver.openInputStream(sourceUri)!!
+
+ val destinationFile = File("$destinationParentPath$fileName")
+ if (destinationFile.exists()) {
+ destinationFile.delete()
}
- if (output != null) {
- try {
- output.close()
- } catch (e: IOException) {
- Log.error("[FileUtil]: Cannot close output file, error: " + e.message)
- }
+
+ destinationFile.outputStream().use { fos ->
+ inputStream.use { it.copyTo(fos) }
}
+ destinationFile
+ } catch (e: IOException) {
+ null
+ } catch (e: NullPointerException) {
+ null
}
- return false
- }
/**
* Extracts the given zip file into the given directory.
@@ -368,4 +361,12 @@ object FileUtil {
return fileName.substring(fileName.lastIndexOf(".") + 1)
.lowercase()
}
+
+ @Throws(IOException::class)
+ fun getStringFromFile(file: File): String =
+ String(file.readBytes(), StandardCharsets.UTF_8)
+
+ @Throws(IOException::class)
+ fun getStringFromInputStream(stream: InputStream): String =
+ String(stream.readBytes(), StandardCharsets.UTF_8)
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index e0ee29c9b..9001ca9ab 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -30,7 +30,7 @@ object GameHelper {
// Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys()
- addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3)
+ addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
// Cache list of games found on disk
val serializedGames = mutableSetOf<String>()
@@ -58,7 +58,7 @@ object GameHelper {
if (it.isDirectory) {
addGamesRecursive(
games,
- FileUtil.listFiles(YuzuApplication.appContext, it.uri),
+ FileUtil.listFiles(it.uri),
depth - 1
)
} else {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
index 1d4695a2a..f6882ce6c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
@@ -3,64 +3,33 @@
package org.yuzu.yuzu_emu.utils
-import android.content.Context
import android.net.Uri
+import android.os.Build
import java.io.BufferedInputStream
import java.io.File
-import java.io.FileInputStream
-import java.io.FileOutputStream
import java.io.IOException
-import java.util.zip.ZipInputStream
import org.yuzu.yuzu_emu.NativeLibrary
-import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
+import org.yuzu.yuzu_emu.YuzuApplication
+import java.util.zip.ZipException
+import java.util.zip.ZipFile
object GpuDriverHelper {
private const val META_JSON_FILENAME = "meta.json"
- private const val DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"
private var fileRedirectionPath: String? = null
- private var driverInstallationPath: String? = null
+ var driverInstallationPath: String? = null
private var hookLibPath: String? = null
- @Throws(IOException::class)
- private fun unzip(zipFilePath: String, destDir: String) {
- val dir = File(destDir)
-
- // Create output directory if it doesn't exist
- if (!dir.exists()) dir.mkdirs()
-
- // Unpack the files.
- val inputStream = FileInputStream(zipFilePath)
- val zis = ZipInputStream(BufferedInputStream(inputStream))
- val buffer = ByteArray(1024)
- var ze = zis.nextEntry
- while (ze != null) {
- val newFile = File(destDir, ze.name)
- val canonicalPath = newFile.canonicalPath
- if (!canonicalPath.startsWith(destDir + ze.name)) {
- throw SecurityException("Zip file attempted path traversal! " + ze.name)
- }
-
- newFile.parentFile!!.mkdirs()
- val fos = FileOutputStream(newFile)
- var len: Int
- while (zis.read(buffer).also { len = it } > 0) {
- fos.write(buffer, 0, len)
- }
- fos.close()
- zis.closeEntry()
- ze = zis.nextEntry
- }
- zis.closeEntry()
- }
+ val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/"
- fun initializeDriverParameters(context: Context) {
+ fun initializeDriverParameters() {
try {
// Initialize the file redirection directory.
- fileRedirectionPath =
- context.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
+ fileRedirectionPath = YuzuApplication.appContext
+ .getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
// Initialize the driver installation directory.
- driverInstallationPath = context.filesDir.canonicalPath + "/gpu_driver/"
+ driverInstallationPath = YuzuApplication.appContext
+ .filesDir.canonicalPath + "/gpu_driver/"
} catch (e: IOException) {
throw RuntimeException(e)
}
@@ -69,68 +38,169 @@ object GpuDriverHelper {
initializeDirectories()
// Initialize hook libraries directory.
- hookLibPath = context.applicationInfo.nativeLibraryDir + "/"
+ hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/"
// Initialize GPU driver.
NativeLibrary.initializeGpuDriver(
hookLibPath,
driverInstallationPath,
- customDriverLibraryName,
+ customDriverData.libraryName,
fileRedirectionPath
)
}
- fun installDefaultDriver(context: Context) {
+ fun getDrivers(): MutableList<Pair<String, GpuDriverMetadata>> {
+ val driverZips = File(driverStoragePath).listFiles()
+ val drivers: MutableList<Pair<String, GpuDriverMetadata>> =
+ driverZips
+ ?.mapNotNull {
+ val metadata = getMetadataFromZip(it)
+ metadata.name?.let { _ -> Pair(it.path, metadata) }
+ }
+ ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
+ ?.distinct()
+ ?.toMutableList() ?: mutableListOf()
+
+ // TODO: Get system driver information
+ drivers.add(0, Pair("", GpuDriverMetadata()))
+ return drivers
+ }
+
+ fun installDefaultDriver() {
// Removing the installed driver will result in the backend using the default system driver.
- val driverInstallationDir = File(driverInstallationPath!!)
- deleteRecursive(driverInstallationDir)
- initializeDriverParameters(context)
+ File(driverInstallationPath!!).deleteRecursively()
+ initializeDriverParameters()
+ }
+
+ fun copyDriverToInternalStorage(driverUri: Uri): Boolean {
+ // Ensure we have directories.
+ initializeDirectories()
+
+ // Copy the zip file URI to user data
+ val copiedFile =
+ FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
+
+ // Validate driver
+ val metadata = getMetadataFromZip(copiedFile)
+ if (metadata.name == null) {
+ copiedFile.delete()
+ return false
+ }
+
+ if (metadata.minApi > Build.VERSION.SDK_INT) {
+ copiedFile.delete()
+ return false
+ }
+ return true
}
- fun installCustomDriver(context: Context, driverPathUri: Uri?) {
+ /**
+ * Copies driver zip into user data directory so that it can be exported along with
+ * other user data and also unzipped into the installation directory
+ */
+ fun installCustomDriver(driverUri: Uri): Boolean {
// Revert to system default in the event the specified driver is bad.
- installDefaultDriver(context)
+ installDefaultDriver()
// Ensure we have directories.
initializeDirectories()
- // Copy the zip file URI into our private storage.
- copyUriToInternalStorage(
- context,
- driverPathUri,
- driverInstallationPath!!,
- DRIVER_INTERNAL_FILENAME
- )
+ // Copy the zip file URI to user data
+ val copiedFile =
+ FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
+
+ // Validate driver
+ val metadata = getMetadataFromZip(copiedFile)
+ if (metadata.name == null) {
+ copiedFile.delete()
+ return false
+ }
+
+ if (metadata.minApi > Build.VERSION.SDK_INT) {
+ copiedFile.delete()
+ return false
+ }
// Unzip the driver.
try {
- unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!)
+ FileUtil.unzipToInternalStorage(
+ BufferedInputStream(copiedFile.inputStream()),
+ File(driverInstallationPath!!)
+ )
} catch (e: SecurityException) {
- return
+ return false
}
// Initialize the driver parameters.
- initializeDriverParameters(context)
+ initializeDriverParameters()
+
+ return true
}
- external fun supportsCustomDriverLoading(): Boolean
+ /**
+ * Unzips driver into installation directory
+ */
+ fun installCustomDriver(driver: File): Boolean {
+ // Revert to system default in the event the specified driver is bad.
+ installDefaultDriver()
- // Parse the custom driver metadata to retrieve the name.
- val customDriverName: String?
- get() {
- val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME)
- return metadata.name
+ // Ensure we have directories.
+ initializeDirectories()
+
+ // Validate driver
+ val metadata = getMetadataFromZip(driver)
+ if (metadata.name == null) {
+ driver.delete()
+ return false
}
- // Parse the custom driver metadata to retrieve the library name.
- private val customDriverLibraryName: String?
- get() {
- // Parse the custom driver metadata to retrieve the library name.
- val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME)
- return metadata.libraryName
+ // Unzip the driver to the private installation directory
+ try {
+ FileUtil.unzipToInternalStorage(
+ BufferedInputStream(driver.inputStream()),
+ File(driverInstallationPath!!)
+ )
+ } catch (e: SecurityException) {
+ return false
}
- private fun initializeDirectories() {
+ // Initialize the driver parameters.
+ initializeDriverParameters()
+
+ return true
+ }
+
+ /**
+ * Takes in a zip file and reads the meta.json file for presentation to the UI
+ *
+ * @param driver Zip containing driver and meta.json file
+ * @return A non-null [GpuDriverMetadata] instance that may have null members
+ */
+ fun getMetadataFromZip(driver: File): GpuDriverMetadata {
+ try {
+ ZipFile(driver).use { zf ->
+ val entries = zf.entries()
+ while (entries.hasMoreElements()) {
+ val entry = entries.nextElement()
+ if (!entry.isDirectory && entry.name.lowercase().contains(".json")) {
+ zf.getInputStream(entry).use {
+ return GpuDriverMetadata(it, entry.size)
+ }
+ }
+ }
+ }
+ } catch (_: ZipException) {
+ }
+ return GpuDriverMetadata()
+ }
+
+ external fun supportsCustomDriverLoading(): Boolean
+
+ // Parse the custom driver metadata to retrieve the name.
+ val customDriverData: GpuDriverMetadata
+ get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
+
+ fun initializeDirectories() {
// Ensure the file redirection directory exists.
val fileRedirectionDir = File(fileRedirectionPath!!)
if (!fileRedirectionDir.exists()) {
@@ -141,14 +211,10 @@ object GpuDriverHelper {
if (!driverInstallationDir.exists()) {
driverInstallationDir.mkdirs()
}
- }
-
- private fun deleteRecursive(fileOrDirectory: File) {
- if (fileOrDirectory.isDirectory) {
- for (child in fileOrDirectory.listFiles()!!) {
- deleteRecursive(child)
- }
+ // Ensure the driver storage directory exists
+ val driverStorageDirectory = File(driverStoragePath)
+ if (!driverStorageDirectory.exists()) {
+ driverStorageDirectory.mkdirs()
}
- fileOrDirectory.delete()
}
}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
index a4e64070a..511a4171a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
@@ -4,29 +4,29 @@
package org.yuzu.yuzu_emu.utils
import java.io.IOException
-import java.nio.charset.StandardCharsets
-import java.nio.file.Files
-import java.nio.file.Paths
import org.json.JSONException
import org.json.JSONObject
+import java.io.File
+import java.io.InputStream
-class GpuDriverMetadata(metadataFilePath: String) {
- var name: String? = null
- var description: String? = null
- var author: String? = null
- var vendor: String? = null
- var driverVersion: String? = null
- var minApi = 0
- var libraryName: String? = null
+class GpuDriverMetadata {
+ /**
+ * Tries to get driver metadata information from a meta.json [File]
+ *
+ * @param metadataFile meta.json file provided with a GPU driver
+ */
+ constructor(metadataFile: File) {
+ if (metadataFile.length() > MAX_META_SIZE_BYTES) {
+ return
+ }
- init {
try {
- val json = JSONObject(getStringFromFile(metadataFilePath))
+ val json = JSONObject(FileUtil.getStringFromFile(metadataFile))
name = json.getString("name")
description = json.getString("description")
author = json.getString("author")
vendor = json.getString("vendor")
- driverVersion = json.getString("driverVersion")
+ version = json.getString("driverVersion")
minApi = json.getInt("minApi")
libraryName = json.getString("libraryName")
} catch (e: JSONException) {
@@ -36,12 +36,84 @@ class GpuDriverMetadata(metadataFilePath: String) {
}
}
- companion object {
- @Throws(IOException::class)
- private fun getStringFromFile(filePath: String): String {
- val path = Paths.get(filePath)
- val bytes = Files.readAllBytes(path)
- return String(bytes, StandardCharsets.UTF_8)
+ /**
+ * Tries to get driver metadata information from an input stream that's intended to be
+ * from a zip file
+ *
+ * @param metadataStream ZipEntry input stream
+ * @param size Size of the file in bytes
+ */
+ constructor(metadataStream: InputStream, size: Long) {
+ if (size > MAX_META_SIZE_BYTES) {
+ return
}
+
+ try {
+ val json = JSONObject(FileUtil.getStringFromInputStream(metadataStream))
+ name = json.getString("name")
+ description = json.getString("description")
+ author = json.getString("author")
+ vendor = json.getString("vendor")
+ version = json.getString("driverVersion")
+ minApi = json.getInt("minApi")
+ libraryName = json.getString("libraryName")
+ } catch (e: JSONException) {
+ // JSON is malformed, ignore and treat as unsupported metadata.
+ } catch (e: IOException) {
+ // File is inaccessible, ignore and treat as unsupported metadata.
+ }
+ }
+
+ /**
+ * Creates an empty metadata instance
+ */
+ constructor()
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is GpuDriverMetadata) {
+ return false
+ }
+
+ return other.name == name &&
+ other.description == description &&
+ other.author == author &&
+ other.vendor == vendor &&
+ other.version == version &&
+ other.minApi == minApi &&
+ other.libraryName == libraryName
+ }
+
+ override fun hashCode(): Int {
+ var result = name?.hashCode() ?: 0
+ result = 31 * result + (description?.hashCode() ?: 0)
+ result = 31 * result + (author?.hashCode() ?: 0)
+ result = 31 * result + (vendor?.hashCode() ?: 0)
+ result = 31 * result + (version?.hashCode() ?: 0)
+ result = 31 * result + minApi
+ result = 31 * result + (libraryName?.hashCode() ?: 0)
+ return result
+ }
+
+ override fun toString(): String =
+ """
+ Name - $name
+ Description - $description
+ Author - $author
+ Vendor - $vendor
+ Version - $version
+ Min API - $minApi
+ Library Name - $libraryName
+ """.trimMargin().trimIndent()
+
+ var name: String? = null
+ var description: String? = null
+ var author: String? = null
+ var vendor: String? = null
+ var version: String? = null
+ var minApi = 0
+ var libraryName: String? = null
+
+ companion object {
+ private const val MAX_META_SIZE_BYTES = 500000
}
}
diff --git a/src/android/app/src/main/res/drawable/ic_build.xml b/src/android/app/src/main/res/drawable/ic_build.xml
new file mode 100644
index 000000000..91d52f1b8
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_build.xml
@@ -0,0 +1,9 @@
+<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="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
+</vector>
diff --git a/src/android/app/src/main/res/drawable/ic_delete.xml b/src/android/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 000000000..d26a79711
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,9 @@
+<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="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
+</vector>
diff --git a/src/android/app/src/main/res/layout/card_driver_option.xml b/src/android/app/src/main/res/layout/card_driver_option.xml
new file mode 100644
index 000000000..1dd9a6d7d
--- /dev/null
+++ b/src/android/app/src/main/res/layout/card_driver_option.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ style="?attr/materialCardViewOutlinedStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="16dp"
+ android:layout_marginVertical="12dp"
+ android:background="?attr/selectableItemBackground"
+ android:clickable="true"
+ android:focusable="true">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="center"
+ android:padding="16dp">
+
+ <RadioButton
+ android:id="@+id/radio_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:clickable="false"
+ android:checked="false" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:layout_gravity="center_vertical">
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.Material3.TitleMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="@string/select_gpu_driver_default" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/version"
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dp"
+ android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="@string/install_gpu_driver_description" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.Material3.BodyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="6dp"
+ android:ellipsize="none"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:requiresFadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ tools:text="@string/install_gpu_driver_description" />
+
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/button_delete"
+ style="@style/Widget.Material3.Button.IconButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/delete"
+ android:tooltipText="@string/delete"
+ app:icon="@drawable/ic_delete"
+ app:iconTint="?attr/colorControlNormal" />
+
+ </LinearLayout>
+
+</com.google.android.material.card.MaterialCardView>
diff --git a/src/android/app/src/main/res/layout/fragment_driver_manager.xml b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
new file mode 100644
index 000000000..6cea2d164
--- /dev/null
+++ b/src/android/app/src/main/res/layout/fragment_driver_manager.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/coordinator_licenses"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/colorSurface">
+
+ <androidx.coordinatorlayout.widget.CoordinatorLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appbar_drivers"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fitsSystemWindows="true"
+ app:liftOnScrollTargetViewId="@id/list_drivers">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/toolbar_drivers"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:navigationIcon="@drawable/ic_back"
+ app:title="@string/gpu_driver_manager" />
+
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/list_drivers"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+ </androidx.coordinatorlayout.widget.CoordinatorLayout>
+
+ <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+ android:id="@+id/button_install"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:text="@string/install"
+ app:icon="@drawable/ic_add"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 2356b802b..82749359d 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -22,6 +22,9 @@
<action
android:id="@+id/action_homeSettingsFragment_to_installableFragment"
app:destination="@id/installableFragment" />
+ <action
+ android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
+ app:destination="@id/driverManagerFragment" />
</fragment>
<fragment
@@ -95,5 +98,9 @@
android:id="@+id/installableFragment"
android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
android:label="InstallableFragment" />
+ <fragment
+ android:id="@+id/driverManagerFragment"
+ android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
+ android:label="DriverManagerFragment" />
</navigation>
diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml
index dd0f36392..72a47fbdb 100644
--- a/src/android/app/src/main/res/values-de/strings.xml
+++ b/src/android/app/src/main/res/values-de/strings.xml
@@ -168,9 +168,7 @@
<string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string>
<string name="select_gpu_driver_install">Installieren</string>
<string name="select_gpu_driver_default">Standard</string>
- <string name="select_gpu_driver_install_success">%s wurde installiert</string>
<string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string>
- <string name="select_gpu_driver_error">Ungültiger Treiber ausgewählt, Standard-Treiber wird verwendet!</string>
<string name="system_gpu_driver">System GPU-Treiber</string>
<string name="installing_driver">Treiber wird installiert...</string>
diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml
index d398f862f..e5bdd5889 100644
--- a/src/android/app/src/main/res/values-es/strings.xml
+++ b/src/android/app/src/main/res/values-es/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string>
<string name="select_gpu_driver_install">Instalar</string>
<string name="select_gpu_driver_default">Predeterminado</string>
- <string name="select_gpu_driver_install_success">Instalado %s</string>
<string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string>
- <string name="select_gpu_driver_error">¡Driver no válido, utilizando el predeterminado del sistema!</string>
<string name="system_gpu_driver">Driver GPU del sistema</string>
<string name="installing_driver">Instalando driver...</string>
diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml
index a7abd9077..1e02828aa 100644
--- a/src/android/app/src/main/res/values-fr/strings.xml
+++ b/src/android/app/src/main/res/values-fr/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string>
<string name="select_gpu_driver_install">Installer</string>
<string name="select_gpu_driver_default">Défaut</string>
- <string name="select_gpu_driver_install_success">%s Installé</string>
<string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string>
- <string name="select_gpu_driver_error">Pilote non valide sélectionné, utilisation du paramètre par défaut du système !</string>
<string name="system_gpu_driver">Pilote du GPU du système</string>
<string name="installing_driver">Installation du pilote...</string>
diff --git a/src/android/app/src/main/res/values-it/strings.xml b/src/android/app/src/main/res/values-it/strings.xml
index b18161801..09c9345b0 100644
--- a/src/android/app/src/main/res/values-it/strings.xml
+++ b/src/android/app/src/main/res/values-it/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string>
<string name="select_gpu_driver_install">Installa</string>
<string name="select_gpu_driver_default">Predefinito</string>
- <string name="select_gpu_driver_install_success">Installato%s</string>
<string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string>
- <string name="select_gpu_driver_error">Il driver selezionato è invalido, è in utilizzo quello predefinito di sistema!</string>
<string name="system_gpu_driver">Driver GPU del sistema</string>
<string name="installing_driver">Installando i driver...</string>
diff --git a/src/android/app/src/main/res/values-ja/strings.xml b/src/android/app/src/main/res/values-ja/strings.xml
index 88fa5a0bb..a0ea78bef 100644
--- a/src/android/app/src/main/res/values-ja/strings.xml
+++ b/src/android/app/src/main/res/values-ja/strings.xml
@@ -170,9 +170,7 @@
<string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
<string name="select_gpu_driver_install">インストール</string>
<string name="select_gpu_driver_default">デフォルト</string>
- <string name="select_gpu_driver_install_success">%s をインストールしました</string>
<string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string>
- <string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string>
<string name="system_gpu_driver">システムのGPUドライバ</string>
<string name="installing_driver">インストール中…</string>
diff --git a/src/android/app/src/main/res/values-ko/strings.xml b/src/android/app/src/main/res/values-ko/strings.xml
index 4b658255c..214f95706 100644
--- a/src/android/app/src/main/res/values-ko/strings.xml
+++ b/src/android/app/src/main/res/values-ko/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
<string name="select_gpu_driver_install">설치</string>
<string name="select_gpu_driver_default">기본값</string>
- <string name="select_gpu_driver_install_success">설치된 %s</string>
<string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string>
- <string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string>
<string name="system_gpu_driver">시스템 GPU 드라이버</string>
<string name="installing_driver">드라이버 설치 중...</string>
diff --git a/src/android/app/src/main/res/values-nb/strings.xml b/src/android/app/src/main/res/values-nb/strings.xml
index dd602a389..5443cef42 100644
--- a/src/android/app/src/main/res/values-nb/strings.xml
+++ b/src/android/app/src/main/res/values-nb/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string>
<string name="select_gpu_driver_install">Installer</string>
<string name="select_gpu_driver_default">Standard</string>
- <string name="select_gpu_driver_install_success">Installert %s</string>
<string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string>
- <string name="select_gpu_driver_error">Ugyldig driver valgt, bruker systemstandard!</string>
<string name="system_gpu_driver">Systemets GPU-driver</string>
<string name="installing_driver">Installerer driver...</string>
diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml
index 2fdd1f952..899e233d0 100644
--- a/src/android/app/src/main/res/values-pl/strings.xml
+++ b/src/android/app/src/main/res/values-pl/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string>
<string name="select_gpu_driver_install">Zainstaluj</string>
<string name="select_gpu_driver_default">Domyślne</string>
- <string name="select_gpu_driver_install_success">Zainstalowano %s</string>
<string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string>
- <string name="select_gpu_driver_error">Wybrano błędny sterownik, powrót do domyślnego. </string>
<string name="system_gpu_driver">Systemowy sterownik GPU</string>
<string name="installing_driver">Instalowanie sterownika...</string>
diff --git a/src/android/app/src/main/res/values-pt-rBR/strings.xml b/src/android/app/src/main/res/values-pt-rBR/strings.xml
index 2f26367fe..caa095364 100644
--- a/src/android/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rBR/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
<string name="select_gpu_driver_install">Instalar</string>
<string name="select_gpu_driver_default">Padrão</string>
- <string name="select_gpu_driver_install_success">Instalado%s</string>
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
- <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
<string name="system_gpu_driver">Driver do GPU padrão</string>
<string name="installing_driver">A instalar o Driver...</string>
diff --git a/src/android/app/src/main/res/values-pt-rPT/strings.xml b/src/android/app/src/main/res/values-pt-rPT/strings.xml
index 4e1eb4cd7..0a1a47fbb 100644
--- a/src/android/app/src/main/res/values-pt-rPT/strings.xml
+++ b/src/android/app/src/main/res/values-pt-rPT/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
<string name="select_gpu_driver_install">Instalar</string>
<string name="select_gpu_driver_default">Padrão</string>
- <string name="select_gpu_driver_install_success">Instalado%s</string>
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
- <string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
<string name="system_gpu_driver">Driver do GPU padrão</string>
<string name="installing_driver">A instalar o Driver...</string>
diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml
index f5695dc93..0bef035d6 100644
--- a/src/android/app/src/main/res/values-ru/strings.xml
+++ b/src/android/app/src/main/res/values-ru/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
<string name="select_gpu_driver_install">Установить</string>
<string name="select_gpu_driver_default">По умолчанию</string>
- <string name="select_gpu_driver_install_success">Установлено %s</string>
<string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string>
- <string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string>
<string name="system_gpu_driver">Системный драйвер ГП</string>
<string name="installing_driver">Установка драйвера...</string>
diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml
index 061bc6f04..5b789ee98 100644
--- a/src/android/app/src/main/res/values-uk/strings.xml
+++ b/src/android/app/src/main/res/values-uk/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
<string name="select_gpu_driver_install">Встановити</string>
<string name="select_gpu_driver_default">За замовчуванням</string>
- <string name="select_gpu_driver_install_success">Встановлено %s</string>
<string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string>
- <string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
<string name="system_gpu_driver">Системний драйвер ГП</string>
<string name="installing_driver">Встановлення драйвера...</string>
diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml
index fe6dd5eaa..c0e885751 100644
--- a/src/android/app/src/main/res/values-zh-rCN/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
<string name="select_gpu_driver_install">安装</string>
<string name="select_gpu_driver_default">系统默认</string>
- <string name="select_gpu_driver_install_success">已安装 %s</string>
<string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string>
- <string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string>
<string name="system_gpu_driver">系统 GPU 驱动程序</string>
<string name="installing_driver">正在安装驱动程序…</string>
diff --git a/src/android/app/src/main/res/values-zh-rTW/strings.xml b/src/android/app/src/main/res/values-zh-rTW/strings.xml
index 9b3e54224..4a21bf893 100644
--- a/src/android/app/src/main/res/values-zh-rTW/strings.xml
+++ b/src/android/app/src/main/res/values-zh-rTW/strings.xml
@@ -171,9 +171,7 @@
<string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
<string name="select_gpu_driver_install">安裝</string>
<string name="select_gpu_driver_default">預設</string>
- <string name="select_gpu_driver_install_success">已安裝 %s</string>
<string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string>
- <string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string>
<string name="system_gpu_driver">系統 GPU 驅動程式</string>
<string name="installing_driver">正在安裝驅動程式…</string>
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index 7b2296d95..ef855ea6f 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -13,6 +13,8 @@
<dimen name="menu_width">256dp</dimen>
<dimen name="card_width">165dp</dimen>
<dimen name="icon_inset">24dp</dimen>
+ <dimen name="spacing_bottom_list_fab">72dp</dimen>
+ <dimen name="spacing_fab">24dp</dimen>
<dimen name="dialog_margin">20dp</dimen>
<dimen name="elevated_app_bar">3dp</dimen>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index e51edf872..9e4854221 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -72,6 +72,7 @@
<string name="invalid_keys_error">Invalid encryption keys</string>
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
<string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
+ <string name="gpu_driver_manager">GPU Driver Manager</string>
<string name="install_gpu_driver">Install GPU driver</string>
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="advanced_settings">Advanced settings</string>
@@ -234,15 +235,17 @@
<string name="export_failed">Export failed</string>
<string name="import_failed">Import failed</string>
<string name="cancelling">Cancelling</string>
+ <string name="install">Install</string>
+ <string name="delete">Delete</string>
<!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string>
<string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string>
<string name="select_gpu_driver_install">Install</string>
<string name="select_gpu_driver_default">Default</string>
- <string name="select_gpu_driver_install_success">Installed %s</string>
<string name="select_gpu_driver_use_default">Using default GPU driver</string>
- <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string>
+ <string name="select_gpu_driver_error">Invalid driver selected</string>
+ <string name="driver_already_installed">Driver already installed</string>
<string name="system_gpu_driver">System GPU driver</string>
<string name="installing_driver">Installing driver…</string>
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 80f370c16..51e559321 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -3,8 +3,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id("com.android.application") version "8.0.2" apply false
- id("com.android.library") version "8.0.2" apply false
+ id("com.android.application") version "8.1.2" apply false
+ id("com.android.library") version "8.1.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
}
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 0dad9338a..47d028d48 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -39,8 +39,12 @@
#define Crash() exit(1)
#endif
+#define LTO_NOINLINE __attribute__((noinline))
+
#else // _MSC_VER
+#define LTO_NOINLINE
+
// Locale Cross-Compatibility
#define locale_t _locale_t
diff --git a/src/common/elf.h b/src/common/elf.h
index 14a5e9597..0b728dc54 100644
--- a/src/common/elf.h
+++ b/src/common/elf.h
@@ -211,6 +211,11 @@ struct Elf64_Rela {
Elf64_Sxword r_addend; /* Addend */
};
+/* RELR relocation table entry */
+
+using Elf32_Relr = Elf32_Word;
+using Elf64_Relr = Elf64_Xword;
+
/* How to extract and insert information held in the r_info field. */
static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
@@ -328,6 +333,9 @@ constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */
constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
+constexpr u32 ElfDtRelrsz = 35; /* Size of RELR relative relocations */
+constexpr u32 ElfDtRelr = 36; /* Address of RELR relative relocations */
+constexpr u32 ElfDtRelrent = 37; /* Size of one RELR relative relocation */
} // namespace ELF
} // namespace Common
diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h
index 41cbb9ed5..12e59a893 100644
--- a/src/common/polyfill_thread.h
+++ b/src/common/polyfill_thread.h
@@ -15,12 +15,13 @@
#include <condition_variable>
#include <stop_token>
#include <thread>
+#include <utility>
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
- cv.wait(lk, token, std::move(pred));
+ cv.wait(lk, token, std::forward<Pred>(pred));
}
template <typename Rep, typename Period>
@@ -109,7 +110,7 @@ public:
// Insert the callback.
stop_state_callback ret = ++m_next_callback;
- m_callbacks.emplace(ret, move(f));
+ m_callbacks.emplace(ret, std::move(f));
return ret;
}
@@ -162,7 +163,7 @@ private:
friend class stop_source;
template <typename Callback>
friend class stop_callback;
- stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {}
+ stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
@@ -198,7 +199,7 @@ public:
private:
friend class jthread;
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
- : m_stop_state(move(stop_state)) {}
+ : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
@@ -218,16 +219,16 @@ public:
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state) {
if (m_stop_state) {
- m_callback = m_stop_state->insert_callback(move(cb));
+ m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(stop_token&& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
- : m_stop_state(move(st.m_stop_state)) {
+ : m_stop_state(std::move(st.m_stop_state)) {
if (m_stop_state) {
- m_callback = m_stop_state->insert_callback(move(cb));
+ m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
~stop_callback() {
@@ -260,7 +261,7 @@ public:
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()),
- m_thread(make_thread(move(f), move(args)...)) {}
+ m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
~jthread() {
if (joinable()) {
@@ -317,9 +318,9 @@ private:
template <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args) {
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
- return thread(move(f), get_stop_token(), move(args)...);
+ return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
} else {
- return thread(move(f), move(args)...);
+ return thread(std::forward<F>(f), std::forward<Args>(args)...);
}
}
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 1fbfbf31f..0b0cef984 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -3405,6 +3405,11 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K
new_attr, KMemoryBlockDisableMergeAttribute::Locked,
KMemoryBlockDisableMergeAttribute::None);
+ // If we have an output page group, open.
+ if (out_pg) {
+ out_pg->Open();
+ }
+
R_SUCCEED();
}
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index a1134b7e2..cb025c3d6 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -373,7 +373,7 @@ struct KernelCore::Impl {
static inline thread_local u8 host_thread_id = UINT8_MAX;
/// Sets the host thread ID for the caller.
- u32 SetHostThreadId(std::size_t core_id) {
+ LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) {
// This should only be called during core init.
ASSERT(host_thread_id == UINT8_MAX);
@@ -384,13 +384,13 @@ struct KernelCore::Impl {
}
/// Gets the host thread ID for the caller
- u32 GetHostThreadId() const {
+ LTO_NOINLINE u32 GetHostThreadId() const {
return host_thread_id;
}
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
- KThread* GetHostDummyThread(KThread* existing_thread) {
- const auto initialize{[](KThread* thread) {
+ LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) {
+ const auto initialize{[](KThread* thread) LTO_NOINLINE {
ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
return thread;
}};
@@ -424,11 +424,11 @@ struct KernelCore::Impl {
static inline thread_local bool is_phantom_mode_for_singlecore{false};
- bool IsPhantomModeForSingleCore() const {
+ LTO_NOINLINE bool IsPhantomModeForSingleCore() const {
return is_phantom_mode_for_singlecore;
}
- void SetIsPhantomModeForSingleCore(bool value) {
+ LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) {
ASSERT(!is_multicore);
is_phantom_mode_for_singlecore = value;
}
@@ -439,14 +439,14 @@ struct KernelCore::Impl {
static inline thread_local KThread* current_thread{nullptr};
- KThread* GetCurrentEmuThread() {
+ LTO_NOINLINE KThread* GetCurrentEmuThread() {
if (!current_thread) {
current_thread = GetHostDummyThread(nullptr);
}
return current_thread;
}
- void SetCurrentEmuThread(KThread* thread) {
+ LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) {
current_thread = thread;
}
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 286f9fd10..31dd98140 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -16,7 +16,7 @@ namespace Service::Capture {
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
- auto album_manager = std::make_shared<AlbumManager>();
+ auto album_manager = std::make_shared<AlbumManager>(system);
server_manager->RegisterNamedService(
"caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index e22f72bf6..9925720a3 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -128,9 +128,9 @@ void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
ctx.WriteBuffer(entries);
}
- IPC::ResponseBuilder rb{ctx, 3};
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(result);
- rb.Push(entries.size());
+ rb.Push<u64>(entries.size());
}
void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp
index 2df6a930a..2b4e3f076 100644
--- a/src/core/hle/service/caps/caps_manager.cpp
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -8,12 +8,15 @@
#include "common/fs/file.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "core/hle/service/caps/caps_manager.h"
#include "core/hle/service/caps/caps_result.h"
+#include "core/hle/service/time/time_manager.h"
+#include "core/hle/service/time/time_zone_content_manager.h"
namespace Service::Capture {
-AlbumManager::AlbumManager() {}
+AlbumManager::AlbumManager(Core::System& system_) : system{system_} {}
AlbumManager::~AlbumManager() = default;
@@ -83,6 +86,34 @@ Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, Albu
}
Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
+ ContentType contex_type, s64 start_posix_time,
+ s64 end_posix_time, u64 aruid) const {
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ std::vector<ApplicationAlbumEntry> album_entries;
+ const auto start_date = ConvertToAlbumDateTime(start_posix_time);
+ const auto end_date = ConvertToAlbumDateTime(end_posix_time);
+ const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ for (const auto& album_entry : album_entries) {
+ ApplicationAlbumFileEntry entry{
+ .entry = album_entry,
+ .datetime = album_entry.datetime,
+ .unknown = {},
+ };
+ out_entries.push_back(entry);
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
ContentType contex_type, AlbumFileDateTime start_date,
AlbumFileDateTime end_date, u64 aruid) const {
if (!is_mounted) {
@@ -93,31 +124,25 @@ Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& ou
if (file_id.type != contex_type) {
continue;
}
-
if (file_id.date > start_date) {
continue;
}
-
if (file_id.date < end_date) {
continue;
}
-
if (out_entries.size() >= SdAlbumFileLimit) {
break;
}
const auto entry_size = Common::FS::GetSize(path);
- ApplicationAlbumFileEntry entry{.entry =
- {
- .size = entry_size,
- .hash{},
- .datetime = file_id.date,
- .storage = file_id.storage,
- .content = contex_type,
- .unknown = 1,
- },
- .datetime = file_id.date,
- .unknown = {}};
+ ApplicationAlbumEntry entry{
+ .size = entry_size,
+ .hash{},
+ .datetime = file_id.date,
+ .storage = file_id.storage,
+ .content = contex_type,
+ .unknown = 1,
+ };
out_entries.push_back(entry);
}
@@ -274,12 +299,12 @@ Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem:
.application_id = static_cast<u64>(std::stoll(application, 0, 16)),
.date =
{
- .year = static_cast<u16>(std::stoi(year)),
- .month = static_cast<u8>(std::stoi(month)),
- .day = static_cast<u8>(std::stoi(day)),
- .hour = static_cast<u8>(std::stoi(hour)),
- .minute = static_cast<u8>(std::stoi(minute)),
- .second = static_cast<u8>(std::stoi(second)),
+ .year = static_cast<s16>(std::stoi(year)),
+ .month = static_cast<s8>(std::stoi(month)),
+ .day = static_cast<s8>(std::stoi(day)),
+ .hour = static_cast<s8>(std::stoi(hour)),
+ .minute = static_cast<s8>(std::stoi(minute)),
+ .second = static_cast<s8>(std::stoi(second)),
.unique_id = 0,
},
.storage = AlbumStorage::Sd,
@@ -339,4 +364,23 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
return ResultSuccess;
}
+
+AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
+ Time::TimeZone::CalendarInfo calendar_date{};
+ const auto& time_zone_manager =
+ system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
+
+ time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
+
+ return {
+ .year = calendar_date.time.year,
+ .month = calendar_date.time.month,
+ .day = calendar_date.time.day,
+ .hour = calendar_date.time.hour,
+ .minute = calendar_date.time.minute,
+ .second = calendar_date.time.second,
+ .unique_id = 0,
+ };
+}
+
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h
index 8337c655c..f65eb12c1 100644
--- a/src/core/hle/service/caps/caps_manager.h
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -37,7 +37,7 @@ namespace Service::Capture {
class AlbumManager {
public:
- explicit AlbumManager();
+ explicit AlbumManager(Core::System& system_);
~AlbumManager();
Result DeleteAlbumFile(const AlbumFileId& file_id);
@@ -45,6 +45,9 @@ public:
Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
u8 flags) const;
Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
+ ContentType contex_type, s64 start_posix_time, s64 end_posix_time,
+ u64 aruid) const;
+ Result GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
ContentType contex_type, AlbumFileDateTime start_date,
AlbumFileDateTime end_date, u64 aruid) const;
Result GetAutoSavingStorage(bool& out_is_autosaving) const;
@@ -65,8 +68,12 @@ private:
Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
int height, ScreenShotDecoderFlag flag) const;
+ AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
+
bool is_mounted{};
std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
+
+ Core::System& system;
};
} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h
index bf6061273..7fd357954 100644
--- a/src/core/hle/service/caps/caps_types.h
+++ b/src/core/hle/service/caps/caps_types.h
@@ -41,13 +41,13 @@ enum class ScreenShotDecoderFlag : u64 {
// This is nn::capsrv::AlbumFileDateTime
struct AlbumFileDateTime {
- u16 year{};
- u8 month{};
- u8 day{};
- u8 hour{};
- u8 minute{};
- u8 second{};
- u8 unique_id{};
+ s16 year{};
+ s8 month{};
+ s8 day{};
+ s8 hour{};
+ s8 minute{};
+ s8 second{};
+ s8 unique_id{};
friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp
index 260f25490..b6b33fb2f 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -50,22 +50,35 @@ void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto pid{rp.Pop<s32>()};
- const auto content_type{rp.PopEnum<ContentType>()};
- const auto start_posix_time{rp.Pop<s64>()};
- const auto end_posix_time{rp.Pop<s64>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ ContentType content_type;
+ INSERT_PADDING_BYTES(7);
+ s64 start_posix_time;
+ s64 end_posix_time;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_Capture,
- "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
- "end_posix_time={}, applet_resource_user_id={}",
- pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id);
+ "(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, "
+ "applet_resource_user_id={}",
+ parameters.content_type, parameters.start_posix_time, parameters.end_posix_time,
+ parameters.applet_resource_user_id);
- // TODO: Translate posix to DateTime
+ Result result = ResultSuccess;
+
+ if (result.IsSuccess()) {
+ result = manager->IsAlbumMounted(AlbumStorage::Sd);
+ }
std::vector<ApplicationAlbumFileEntry> entries;
- const Result result =
- manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id);
+ if (result.IsSuccess()) {
+ result = manager->GetAlbumFileList(entries, parameters.content_type,
+ parameters.start_posix_time, parameters.end_posix_time,
+ parameters.applet_resource_user_id);
+ }
if (!entries.empty()) {
ctx.WriteBuffer(entries);
@@ -78,19 +91,38 @@ void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestCo
void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto pid{rp.Pop<s32>()};
- const auto content_type{rp.PopEnum<ContentType>()};
- const auto start_date_time{rp.PopRaw<AlbumFileDateTime>()};
- const auto end_date_time{rp.PopRaw<AlbumFileDateTime>()};
- const auto applet_resource_user_id{rp.Pop<u64>()};
+ struct Parameters {
+ ContentType content_type;
+ INSERT_PADDING_BYTES(1);
+ AlbumFileDateTime start_date_time;
+ AlbumFileDateTime end_date_time;
+ INSERT_PADDING_BYTES(6);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_Capture,
- "(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid,
- content_type, applet_resource_user_id);
+ "(STUBBED) called. content_type={}, start_date={}/{}/{}, "
+ "end_date={}/{}/{}, applet_resource_user_id={}",
+ parameters.content_type, parameters.start_date_time.year,
+ parameters.start_date_time.month, parameters.start_date_time.day,
+ parameters.end_date_time.year, parameters.end_date_time.month,
+ parameters.end_date_time.day, parameters.applet_resource_user_id);
- std::vector<ApplicationAlbumFileEntry> entries;
- const Result result = manager->GetAlbumFileList(entries, content_type, start_date_time,
- end_date_time, applet_resource_user_id);
+ Result result = ResultSuccess;
+
+ if (result.IsSuccess()) {
+ result = manager->IsAlbumMounted(AlbumStorage::Sd);
+ }
+
+ std::vector<ApplicationAlbumEntry> entries;
+ if (result.IsSuccess()) {
+ result =
+ manager->GetAlbumFileList(entries, parameters.content_type, parameters.start_date_time,
+ parameters.end_date_time, parameters.applet_resource_user_id);
+ }
if (!entries.empty()) {
ctx.WriteBuffer(entries);
diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp
index 4ed3f02e2..0090e8568 100644
--- a/src/core/hle/service/jit/jit_context.cpp
+++ b/src/core/hle/service/jit/jit_context.cpp
@@ -156,6 +156,8 @@ public:
bool LoadNRO(std::span<const u8> data) {
local_memory.clear();
+
+ relocbase = local_memory.size();
local_memory.insert(local_memory.end(), data.begin(), data.end());
if (FixupRelocations()) {
@@ -181,8 +183,8 @@ public:
// https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
// https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
- VAddr rela_dyn = 0;
- size_t num_rela = 0;
+ VAddr rela_dyn = 0, relr_dyn = 0;
+ size_t num_rela = 0, num_relr = 0;
while (true) {
const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
dynamic_offset += sizeof(Elf64_Dyn);
@@ -196,6 +198,12 @@ public:
if (dyn.d_tag == ElfDtRelasz) {
num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
}
+ if (dyn.d_tag == ElfDtRelr) {
+ relr_dyn = dyn.d_un.d_ptr;
+ }
+ if (dyn.d_tag == ElfDtRelrsz) {
+ num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr);
+ }
}
for (size_t i = 0; i < num_rela; i++) {
@@ -207,6 +215,29 @@ public:
callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
}
+ VAddr relr_where = 0;
+ for (size_t i = 0; i < num_relr; i++) {
+ const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))};
+ const auto incr{[&](VAddr where) {
+ callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
+ }};
+
+ if ((relr & 1) == 0) {
+ // where pointer
+ relr_where = relocbase + relr;
+ incr(relr_where);
+ relr_where += sizeof(Elf64_Addr);
+ } else {
+ // bitmap
+ for (int bit = 1; bit < 64; bit++) {
+ if ((relr & (1ULL << bit)) != 0) {
+ incr(relr_where + i * sizeof(Elf64_Addr));
+ }
+ }
+ relr_where += 63 * sizeof(Elf64_Addr);
+ }
+ }
+
return true;
}
@@ -313,6 +344,7 @@ public:
Core::Memory::Memory& memory;
VAddr top_of_stack;
VAddr heap_pointer;
+ VAddr relocbase;
};
void DynarmicCallbacks64::CallSVC(u32 swi) {
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 3676eaaa9..e71b87e99 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -118,6 +118,8 @@ public:
void InsertUploadMemoryBarrier();
+ void TransitionImageLayout(Image& image) {}
+
FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
bool HasNativeBgr() const noexcept {
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 1b8bb9662..93773a69f 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -2013,4 +2013,32 @@ void TextureCacheRuntime::AccelerateImageUpload(
ASSERT(false);
}
+void TextureCacheRuntime::TransitionImageLayout(Image& image) {
+ if (!image.ExchangeInitialization()) {
+ VkImageMemoryBarrier barrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_NONE,
+ .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image.Handle(),
+ .subresourceRange{
+ .aspectMask = image.AspectMask(),
+ .baseMipLevel = 0,
+ .levelCount = VK_REMAINING_MIP_LEVELS,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ };
+ scheduler.RequestOutsideRenderPassOperationContext();
+ scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) {
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
+ });
+ }
+}
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index d6c5a15cc..7a0807709 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -92,6 +92,8 @@ public:
void InsertUploadMemoryBarrier() {}
+ void TransitionImageLayout(Image& image);
+
bool HasBrokenTextureViewFormats() const noexcept {
// No known Vulkan driver has broken image views
return false;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 1bdb0def5..d575c57ca 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1016,6 +1016,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
+ runtime.TransitionImageLayout(image);
return;
}
if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 74f48031a..2bb1a0239 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -826,12 +826,13 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
+ // Before deleting rows, cancel the worker so that it is not using them
+ emit ShouldCancelWorker();
+
// Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount());
search_field->clear();
- emit ShouldCancelWorker();
-
GameListWorker* worker =
new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 588f1dd6e..077ced12b 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -293,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
GameListDir* parent_dir) {
const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool {
- if (stop_processing) {
+ if (stop_requested) {
// Breaks the callback loop.
return false;
}
@@ -399,7 +399,6 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
}
void GameListWorker::run() {
- stop_processing = false;
provider->ClearAllEntries();
for (UISettings::GameDir& game_dir : game_dirs) {
@@ -427,9 +426,11 @@ void GameListWorker::run() {
}
emit Finished(watch_list);
+ processing_completed.Set();
}
void GameListWorker::Cancel() {
this->disconnect();
- stop_processing = true;
+ stop_requested.store(true);
+ processing_completed.Wait();
}
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 2bb0a0cb6..54dc05e30 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -12,6 +12,7 @@
#include <QRunnable>
#include <QString>
+#include "common/thread.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/play_time_manager.h"
@@ -82,7 +83,9 @@ private:
const PlayTime::PlayTimeManager& play_time_manager;
QStringList watch_list;
- std::atomic_bool stop_processing;
+
+ Common::Event processing_completed;
+ std::atomic_bool stop_requested = false;
Core::System& system;
};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 425f546f7..1431cf2fe 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -67,6 +67,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#define QT_NO_OPENGL
#include <QClipboard>
#include <QDesktopServices>
+#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QInputDialog>
@@ -76,6 +77,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QPushButton>
#include <QScreen>
#include <QShortcut>
+#include <QStandardPaths>
#include <QStatusBar>
#include <QString>
#include <QSysInfo>
@@ -2868,44 +2870,50 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
#endif // __linux__
std::filesystem::path target_directory{};
- // Determine target directory for shortcut
-#if defined(WIN32)
- const char* home = std::getenv("USERPROFILE");
-#else
- const char* home = std::getenv("HOME");
-#endif
- const std::filesystem::path home_path = (home == nullptr ? "~" : home);
- const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
- if (target == GameListShortcutTarget::Desktop) {
- target_directory = home_path / "Desktop";
- if (!Common::FS::IsDir(target_directory)) {
- QMessageBox::critical(
- this, tr("Create Shortcut"),
- tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
- .arg(QString::fromStdString(target_directory.generic_string())),
- QMessageBox::StandardButton::Ok);
- return;
- }
- } else if (target == GameListShortcutTarget::Applications) {
- target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
- "applications";
- if (!Common::FS::CreateDirs(target_directory)) {
- QMessageBox::critical(
- this, tr("Create Shortcut"),
- tr("Cannot create shortcut in applications menu. Path \"%1\" "
- "does not exist and cannot be created.")
- .arg(QString::fromStdString(target_directory.generic_string())),
- QMessageBox::StandardButton::Ok);
- return;
+ switch (target) {
+ case GameListShortcutTarget::Desktop: {
+ const QString desktop_path =
+ QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
+ target_directory = desktop_path.toUtf8().toStdString();
+ break;
+ }
+ case GameListShortcutTarget::Applications: {
+ const QString applications_path =
+ QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
+ if (applications_path.isEmpty()) {
+ const char* home = std::getenv("HOME");
+ if (home != nullptr) {
+ target_directory = std::filesystem::path(home) / ".local/share/applications";
+ }
+ } else {
+ target_directory = applications_path.toUtf8().toStdString();
}
+ break;
+ }
+ default:
+ return;
+ }
+
+ const QDir dir(QString::fromStdString(target_directory.generic_string()));
+ if (!dir.exists()) {
+ QMessageBox::critical(this, tr("Create Shortcut"),
+ tr("Cannot create shortcut. Path \"%1\" does not exist.")
+ .arg(QString::fromStdString(target_directory.generic_string())),
+ QMessageBox::StandardButton::Ok);
+ return;
}
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
// Determine full paths for icon and shortcut
#if defined(__linux__) || defined(__FreeBSD__)
+ const char* home = std::getenv("HOME");
+ const std::filesystem::path home_path = (home == nullptr ? "~" : home);
+ const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
+
std::filesystem::path system_icons_path =
- (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) /
+ (xdg_data_home == nullptr ? home_path / ".local/share/"
+ : std::filesystem::path(xdg_data_home)) /
"icons/hicolor/256x256";
if (!Common::FS::CreateDirs(system_icons_path)) {
QMessageBox::critical(