Compare commits

..

1 Commits

Author SHA1 Message Date
a0ce33806a Android #97 2023-10-11 00:57:24 +00:00
91 changed files with 604 additions and 1768 deletions

View File

@ -5,6 +5,6 @@
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`" GITREV="`git show -s --format='%h'`"
ARTIFACTS_DIR="$PWD/artifacts" ARTIFACTS_DIR="artifacts"
mkdir -p "${ARTIFACTS_DIR}/" mkdir -p "${ARTIFACTS_DIR}/"

View File

@ -11,7 +11,7 @@ ccache -s
mkdir build || true && cd build mkdir build || true && cd build
cmake .. \ cmake .. \
-DBoost_USE_STATIC_LIBS=ON \ -DBoost_USE_STATIC_LIBS=ON \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS="-march=x86-64-v2" \ -DCMAKE_CXX_FLAGS="-march=x86-64-v2" \
-DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ \ -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ \
-DCMAKE_C_COMPILER=/usr/lib/ccache/gcc \ -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc \
@ -31,19 +31,6 @@ ccache -s
ctest -VV -C Release ctest -VV -C Release
# Separate debug symbols from specified executables
for EXE in yuzu; do
EXE_PATH="bin/$EXE"
# Copy debug symbols out
objcopy --only-keep-debug $EXE_PATH $EXE_PATH.debug
# Add debug link and strip debug symbols
objcopy -g --add-gnu-debuglink=$EXE_PATH.debug $EXE_PATH $EXE_PATH.out
# Overwrite original with stripped copy
mv $EXE_PATH.out $EXE_PATH
done
# Strip debug symbols from all executables
find bin/ -type f -not -regex '.*.debug' -exec strip -g {} ';'
DESTDIR="$PWD/AppDir" ninja install DESTDIR="$PWD/AppDir" ninja install
rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester

View File

@ -59,9 +59,4 @@ if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ];
cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage" cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage"
fi fi
# Copy debug symbols to artifacts
cd build/bin
tar $COMPRESSION_FLAGS "${ARTIFACTS_DIR}/${REV_NAME}-debug.tar.xz" *.debug
cd -
. .ci/scripts/common/post-upload.sh . .ci/scripts/common/post-upload.sh

View File

@ -11,6 +11,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modul
include(DownloadExternals) include(DownloadExternals)
include(CMakeDependentOption) include(CMakeDependentOption)
include(CTest) include(CTest)
include(FetchContent)
# Set bundled sdl2/qt as dependent options. # Set bundled sdl2/qt as dependent options.
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON # OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
@ -98,8 +99,47 @@ if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
DESTINATION "${vvl_lib_path}") DESTINATION "${vvl_lib_path}")
endif() endif()
# On Android, fetch and compile libcxx before doing anything else
if (ANDROID) if (ANDROID)
set(CMAKE_SKIP_INSTALL_RULES ON) 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() endif()
if (YUZU_USE_BUNDLED_VCPKG) if (YUZU_USE_BUNDLED_VCPKG)
@ -289,7 +329,7 @@ find_package(Boost 1.79.0 REQUIRED context)
find_package(enet 1.3 MODULE) find_package(enet 1.3 MODULE)
find_package(fmt 9 REQUIRED) find_package(fmt 9 REQUIRED)
find_package(inih 52 MODULE COMPONENTS INIReader) find_package(inih 52 MODULE COMPONENTS INIReader)
find_package(LLVM 17.0.2 MODULE COMPONENTS Demangle) find_package(LLVM 17 MODULE COMPONENTS Demangle)
find_package(lz4 REQUIRED) find_package(lz4 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED) find_package(nlohmann_json 3.8 REQUIRED)
find_package(Opus 1.3 MODULE) find_package(Opus 1.3 MODULE)
@ -360,9 +400,6 @@ function(set_yuzu_qt_components)
if (ENABLE_QT_TRANSLATION) if (ENABLE_QT_TRANSLATION)
list(APPEND YUZU_QT_COMPONENTS2 LinguistTools) list(APPEND YUZU_QT_COMPONENTS2 LinguistTools)
endif() endif()
if (USE_DISCORD_PRESENCE)
list(APPEND YUZU_QT_COMPONENTS2 Network)
endif()
set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE) set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE)
endfunction(set_yuzu_qt_components) endfunction(set_yuzu_qt_components)

View File

@ -120,10 +120,6 @@ QWidget#connectedControllers {
background: transparent; background: transparent;
} }
QWidget#closeButtons {
background: transparent;
}
QWidget#playersSupported, QWidget#playersSupported,
QWidget#controllersSupported, QWidget#controllersSupported,
QWidget#controllerSupported1, QWidget#controllerSupported1,

View File

@ -1380,10 +1380,6 @@ QWidget#connectedControllers {
background: transparent; background: transparent;
} }
QWidget#closeButtons {
background: transparent;
}
QWidget#playersSupported, QWidget#playersSupported,
QWidget#controllersSupported, QWidget#controllersSupported,
QWidget#controllerSupported1, QWidget#controllerSupported1,

View File

@ -2305,10 +2305,6 @@ QWidget#connectedControllers {
background: transparent; background: transparent;
} }
QWidget#closeButtons {
background: transparent;
}
QWidget#playersSupported, QWidget#playersSupported,
QWidget#controllersSupported, QWidget#controllersSupported,
QWidget#controllerSupported1, QWidget#controllerSupported1,

View File

@ -27,7 +27,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
set(CAN_BUILD_NX_TZDB false) set(CAN_BUILD_NX_TZDB false)
endif() endif()
set(NX_TZDB_VERSION "221202") set(NX_TZDB_VERSION "220816")
set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip") set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb") set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")

View File

@ -27,7 +27,7 @@ android {
namespace = "org.yuzu.yuzu_emu" namespace = "org.yuzu.yuzu_emu"
compileSdkVersion = "android-34" compileSdkVersion = "android-34"
ndkVersion = "26.1.10909125" ndkVersion = "25.2.9519653"
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
@ -203,23 +203,23 @@ ktlint {
} }
dependencies { dependencies {
implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.recyclerview:recyclerview:1.3.1") implementation("androidx.recyclerview:recyclerview:1.3.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.fragment:fragment-ktx:1.6.1") implementation("androidx.fragment:fragment-ktx:1.6.0")
implementation("androidx.documentfile:documentfile:1.0.1") implementation("androidx.documentfile:documentfile:1.0.1")
implementation("com.google.android.material:material:1.9.0") implementation("com.google.android.material:material:1.9.0")
implementation("androidx.preference:preference-ktx:1.2.1") implementation("androidx.preference:preference:1.2.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("io.coil-kt:coil:2.2.2") implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.window:window:1.2.0-beta03") implementation("androidx.window:window:1.2.0-beta03")
implementation("org.ini4j:ini4j:0.5.4") implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
implementation("androidx.navigation:navigation-ui-ktx:2.7.4") implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
implementation("info.debatty:java-string-similarity:2.0.0") implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
} }

View File

@ -28,6 +28,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:appCategory="game" android:appCategory="game"
android:localeConfig="@xml/locales_config" android:localeConfig="@xml/locales_config"
android:banner="@drawable/tv_banner" android:banner="@drawable/tv_banner"
android:extractNativeLibs="true"
android:fullBackupContent="@xml/data_extraction_rules" android:fullBackupContent="@xml/data_extraction_rules"
android:dataExtractionRules="@xml/data_extraction_rules_api_31" android:dataExtractionRules="@xml/data_extraction_rules_api_31"
android:enableOnBackInvokedCallback="true"> android:enableOnBackInvokedCallback="true">

View File

@ -15,9 +15,13 @@ import androidx.annotation.Keep
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.lang.ref.WeakReference 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.activities.EmulationActivity
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
import org.yuzu.yuzu_emu.utils.FileUtil 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.Log import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
@ -71,7 +75,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) { return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.openContentUri(path, openmode) YuzuApplication.documentsTree!!.openContentUri(path, openmode)
} else { } else {
FileUtil.openContentUri(path, openmode) openContentUri(appContext, path, openmode)
} }
} }
@ -81,7 +85,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) { return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.getFileSize(path) YuzuApplication.documentsTree!!.getFileSize(path)
} else { } else {
FileUtil.getFileSize(path) getFileSize(appContext, path)
} }
} }
@ -91,7 +95,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) { return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.exists(path) YuzuApplication.documentsTree!!.exists(path)
} else { } else {
FileUtil.exists(path) exists(appContext, path)
} }
} }
@ -101,7 +105,7 @@ object NativeLibrary {
return if (isNativePath(path!!)) { return if (isNativePath(path!!)) {
YuzuApplication.documentsTree!!.isDirectory(path) YuzuApplication.documentsTree!!.isDirectory(path)
} else { } else {
FileUtil.isDirectory(path) isDirectory(appContext, path)
} }
} }

View File

@ -47,7 +47,7 @@ class YuzuApplication : Application() {
application = this application = this
documentsTree = DocumentsTree() documentsTree = DocumentsTree()
DirectoryInitialization.start() DirectoryInitialization.start()
GpuDriverHelper.initializeDriverParameters() GpuDriverHelper.initializeDriverParameters(applicationContext)
NativeLibrary.logDeviceInfo() NativeLibrary.logDeviceInfo()
createNotificationChannels() createNotificationChannels()

View File

@ -1,117 +0,0 @@
// 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
}
}
}

View File

@ -1,186 +0,0 @@
// 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)
}
}

View File

@ -1,75 +0,0 @@
// 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"
}
}

View File

@ -39,7 +39,6 @@ import androidx.window.layout.WindowLayoutInfo
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
@ -51,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.IntSetting import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.EmulationViewModel import org.yuzu.yuzu_emu.model.EmulationViewModel
import org.yuzu.yuzu_emu.overlay.InputOverlay import org.yuzu.yuzu_emu.overlay.InputOverlay
@ -72,7 +70,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private lateinit var game: Game private lateinit var game: Game
private val emulationViewModel: EmulationViewModel by activityViewModels() private val emulationViewModel: EmulationViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels()
private var isInFoldableLayout = false private var isInFoldableLayout = false
@ -302,21 +299,6 @@ 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)
}
}
}
}
} }
} }
@ -350,6 +332,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
} }
} }
override fun onResume() {
super.onResume()
if (!DirectoryInitialization.areDirectoriesReady) {
DirectoryInitialization.start()
}
updateScreenLayout()
emulationState.run(emulationActivity!!.isActivityRecreated)
}
override fun onPause() { override fun onPause() {
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) { if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
emulationState.pause() emulationState.pause()

View File

@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu.fragments
import android.Manifest import android.Manifest
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
@ -27,6 +28,7 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialSharedAxis import com.google.android.material.transition.MaterialSharedAxis
import org.yuzu.yuzu_emu.BuildConfig import org.yuzu.yuzu_emu.BuildConfig
import org.yuzu.yuzu_emu.HomeNavigationDirections import org.yuzu.yuzu_emu.HomeNavigationDirections
@ -35,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
import org.yuzu.yuzu_emu.features.DocumentProvider import org.yuzu.yuzu_emu.features.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings 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.HomeSetting
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.ui.main.MainActivity
@ -49,7 +50,6 @@ class HomeSettingsFragment : Fragment() {
private lateinit var mainActivity: MainActivity private lateinit var mainActivity: MainActivity
private val homeViewModel: HomeViewModel by activityViewModels() private val homeViewModel: HomeViewModel by activityViewModels()
private val driverViewModel: DriverViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -107,17 +107,13 @@ class HomeSettingsFragment : Fragment() {
) )
add( add(
HomeSetting( HomeSetting(
R.string.gpu_driver_manager, R.string.install_gpu_driver,
R.string.install_gpu_driver_description, R.string.install_gpu_driver_description,
R.drawable.ic_build, R.drawable.ic_exit,
{ { driverInstaller() },
binding.root.findNavController()
.navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
},
{ GpuDriverHelper.supportsCustomDriverLoading() }, { GpuDriverHelper.supportsCustomDriverLoading() },
R.string.custom_driver_not_supported, R.string.custom_driver_not_supported,
R.string.custom_driver_not_supported_description, R.string.custom_driver_not_supported_description
driverViewModel.selectedDriverMetadata
) )
) )
add( add(
@ -296,6 +292,31 @@ 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() { private fun shareLog() {
val file = DocumentFile.fromSingleUri( val file = DocumentFile.fromSingleUri(
mainActivity, mainActivity,

View File

@ -10,8 +10,8 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -78,10 +78,6 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
requireActivity().supportFragmentManager, requireActivity().supportFragmentManager,
MessageDialogFragment.TAG MessageDialogFragment.TAG
) )
else -> {
// Do nothing
}
} }
taskViewModel.clear() taskViewModel.clear()
} }
@ -119,7 +115,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
private const val CANCELLABLE = "Cancellable" private const val CANCELLABLE = "Cancellable"
fun newInstance( fun newInstance(
activity: FragmentActivity, activity: AppCompatActivity,
titleId: Int, titleId: Int,
cancellable: Boolean = false, cancellable: Boolean = false,
task: () -> Any task: () -> Any

View File

@ -1,158 +0,0 @@
// 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)
}
}

View File

@ -29,10 +29,12 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationBarView import com.google.android.material.navigation.NavigationBarView
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import java.io.File import java.io.File
import java.io.FilenameFilter import java.io.FilenameFilter
import java.io.IOException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -41,6 +43,7 @@ import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 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.DocumentProvider
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
@ -340,10 +343,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val dstPath = DirectoryInitialization.userDirectory + "/keys/" val dstPath = DirectoryInitialization.userDirectory + "/keys/"
if (FileUtil.copyUriToInternalStorage( if (FileUtil.copyUriToInternalStorage(
applicationContext,
result, result,
dstPath, dstPath,
"prod.keys" "prod.keys"
) != null )
) { ) {
if (NativeLibrary.reloadKeys()) { if (NativeLibrary.reloadKeys()) {
Toast.makeText( Toast.makeText(
@ -442,10 +446,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val dstPath = DirectoryInitialization.userDirectory + "/keys/" val dstPath = DirectoryInitialization.userDirectory + "/keys/"
if (FileUtil.copyUriToInternalStorage( if (FileUtil.copyUriToInternalStorage(
applicationContext,
result, result,
dstPath, dstPath,
"key_retail.bin" "key_retail.bin"
) != null )
) { ) {
if (NativeLibrary.reloadKeys()) { if (NativeLibrary.reloadKeys()) {
Toast.makeText( Toast.makeText(
@ -464,6 +469,59 @@ 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( val installGameUpdate = registerForActivityResult(
ActivityResultContracts.OpenMultipleDocuments() ActivityResultContracts.OpenMultipleDocuments()
) { documents: List<Uri> -> ) { documents: List<Uri> ->

View File

@ -7,6 +7,7 @@ import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import java.io.File import java.io.File
import java.util.* import java.util.*
import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile import org.yuzu.yuzu_emu.model.MinimalDocumentFile
class DocumentsTree { class DocumentsTree {
@ -21,7 +22,7 @@ class DocumentsTree {
fun openContentUri(filepath: String, openMode: String?): Int { fun openContentUri(filepath: String, openMode: String?): Int {
val node = resolvePath(filepath) ?: return -1 val node = resolvePath(filepath) ?: return -1
return FileUtil.openContentUri(node.uri.toString(), openMode) return FileUtil.openContentUri(YuzuApplication.appContext, node.uri.toString(), openMode)
} }
fun getFileSize(filepath: String): Long { fun getFileSize(filepath: String): Long {
@ -29,7 +30,7 @@ class DocumentsTree {
return if (node == null || node.isDirectory) { return if (node == null || node.isDirectory) {
0 0
} else { } else {
FileUtil.getFileSize(node.uri.toString()) FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
} }
} }
@ -66,7 +67,7 @@ class DocumentsTree {
* @param parent parent node of this level * @param parent parent node of this level
*/ */
private fun structTree(parent: DocumentsNode) { private fun structTree(parent: DocumentsNode) {
val documents = FileUtil.listFiles(parent.uri!!) val documents = FileUtil.listFiles(YuzuApplication.appContext, parent.uri!!)
for (document in documents) { for (document in documents) {
val node = DocumentsNode(document) val node = DocumentsNode(document)
node.parent = parent node.parent = parent

View File

@ -3,6 +3,7 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.provider.DocumentsContract import android.provider.DocumentsContract
@ -10,6 +11,7 @@ import androidx.documentfile.provider.DocumentFile
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.net.URLDecoder import java.net.URLDecoder
@ -19,8 +21,6 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import org.yuzu.yuzu_emu.model.TaskState import org.yuzu.yuzu_emu.model.TaskState
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.lang.NullPointerException
import java.nio.charset.StandardCharsets
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
object FileUtil { object FileUtil {
@ -29,8 +29,6 @@ object FileUtil {
const val APPLICATION_OCTET_STREAM = "application/octet-stream" const val APPLICATION_OCTET_STREAM = "application/octet-stream"
const val TEXT_PLAIN = "text/plain" const val TEXT_PLAIN = "text/plain"
private val context get() = YuzuApplication.appContext
/** /**
* Create a file from directory with filename. * Create a file from directory with filename.
* @param context Application context * @param context Application context
@ -38,11 +36,11 @@ object FileUtil {
* @param filename file display name. * @param filename file display name.
* @return boolean * @return boolean
*/ */
fun createFile(directory: String?, filename: String): DocumentFile? { fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? {
var decodedFilename = filename var decodedFilename = filename
try { try {
val directoryUri = Uri.parse(directory) 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) decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
var mimeType = APPLICATION_OCTET_STREAM var mimeType = APPLICATION_OCTET_STREAM
if (decodedFilename.endsWith(".txt")) { if (decodedFilename.endsWith(".txt")) {
@ -58,15 +56,16 @@ object FileUtil {
/** /**
* Create a directory from directory with filename. * Create a directory from directory with filename.
* @param context Application context
* @param directory parent path for directory. * @param directory parent path for directory.
* @param directoryName directory display name. * @param directoryName directory display name.
* @return boolean * @return boolean
*/ */
fun createDir(directory: String?, directoryName: String?): DocumentFile? { fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? {
var decodedDirectoryName = directoryName var decodedDirectoryName = directoryName
try { try {
val directoryUri = Uri.parse(directory) 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) decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
val isExist = parent.findFile(decodedDirectoryName) val isExist = parent.findFile(decodedDirectoryName)
return isExist ?: parent.createDirectory(decodedDirectoryName) return isExist ?: parent.createDirectory(decodedDirectoryName)
@ -78,12 +77,13 @@ object FileUtil {
/** /**
* Open content uri and return file descriptor to JNI. * Open content uri and return file descriptor to JNI.
* @param context Application context
* @param path Native content uri path * @param path Native content uri path
* @param openMode will be one of "r", "r", "rw", "wa", "rwa" * @param openMode will be one of "r", "r", "rw", "wa", "rwa"
* @return file descriptor * @return file descriptor
*/ */
@JvmStatic @JvmStatic
fun openContentUri(path: String, openMode: String?): Int { fun openContentUri(context: Context, path: String, openMode: String?): Int {
try { try {
val uri = Uri.parse(path) val uri = Uri.parse(path)
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!) val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
@ -103,10 +103,11 @@ object FileUtil {
/** /**
* Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow * Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
* This function will be faster than DoucmentFile.listFiles * This function will be faster than DoucmentFile.listFiles
* @param context Application context
* @param uri Directory uri. * @param uri Directory uri.
* @return CheapDocument lists. * @return CheapDocument lists.
*/ */
fun listFiles(uri: Uri): Array<MinimalDocumentFile> { fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> {
val resolver = context.contentResolver val resolver = context.contentResolver
val columns = arrayOf( val columns = arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@ -144,7 +145,7 @@ object FileUtil {
* @param path Native content uri path * @param path Native content uri path
* @return bool * @return bool
*/ */
fun exists(path: String?): Boolean { fun exists(context: Context, path: String?): Boolean {
var c: Cursor? = null var c: Cursor? = null
try { try {
val mUri = Uri.parse(path) val mUri = Uri.parse(path)
@ -164,7 +165,7 @@ object FileUtil {
* @param path content uri path * @param path content uri path
* @return bool * @return bool
*/ */
fun isDirectory(path: String): Boolean { fun isDirectory(context: Context, path: String): Boolean {
val resolver = context.contentResolver val resolver = context.contentResolver
val columns = arrayOf( val columns = arrayOf(
DocumentsContract.Document.COLUMN_MIME_TYPE DocumentsContract.Document.COLUMN_MIME_TYPE
@ -209,10 +210,10 @@ object FileUtil {
return filename return filename
} }
fun getFilesName(path: String): Array<String> { fun getFilesName(context: Context, path: String): Array<String> {
val uri = Uri.parse(path) val uri = Uri.parse(path)
val files: MutableList<String> = ArrayList() val files: MutableList<String> = ArrayList()
for (file in listFiles(uri)) { for (file in listFiles(context, uri)) {
files.add(file.filename) files.add(file.filename)
} }
return files.toTypedArray() return files.toTypedArray()
@ -224,7 +225,7 @@ object FileUtil {
* @return long file size * @return long file size
*/ */
@JvmStatic @JvmStatic
fun getFileSize(path: String): Long { fun getFileSize(context: Context, path: String): Long {
val resolver = context.contentResolver val resolver = context.contentResolver
val columns = arrayOf( val columns = arrayOf(
DocumentsContract.Document.COLUMN_SIZE DocumentsContract.Document.COLUMN_SIZE
@ -244,37 +245,43 @@ object FileUtil {
return size 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( fun copyUriToInternalStorage(
sourceUri: Uri, context: Context,
sourceUri: Uri?,
destinationParentPath: String, destinationParentPath: String,
destinationFilename: String = "" destinationFilename: String
): File? = ): Boolean {
var input: InputStream? = null
var output: FileOutputStream? = null
try { try {
val fileName = input = context.contentResolver.openInputStream(sourceUri!!)
if (destinationFilename == "") getFilename(sourceUri) else "/$destinationFilename" output = FileOutputStream("$destinationParentPath/$destinationFilename")
val inputStream = context.contentResolver.openInputStream(sourceUri)!! val buffer = ByteArray(1024)
var len: Int
val destinationFile = File("$destinationParentPath$fileName") while (input!!.read(buffer).also { len = it } != -1) {
if (destinationFile.exists()) { output.write(buffer, 0, len)
destinationFile.delete()
} }
output.flush()
destinationFile.outputStream().use { fos -> return true
inputStream.use { it.copyTo(fos) } } catch (e: Exception) {
} Log.error("[FileUtil]: Cannot copy file, error: " + e.message)
destinationFile } finally {
if (input != null) {
try {
input.close()
} catch (e: IOException) { } catch (e: IOException) {
null Log.error("[FileUtil]: Cannot close input file, error: " + e.message)
} catch (e: NullPointerException) { }
null }
if (output != null) {
try {
output.close()
} catch (e: IOException) {
Log.error("[FileUtil]: Cannot close output file, error: " + e.message)
}
}
}
return false
} }
/** /**
@ -361,12 +368,4 @@ object FileUtil {
return fileName.substring(fileName.lastIndexOf(".") + 1) return fileName.substring(fileName.lastIndexOf(".") + 1)
.lowercase() .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)
} }

View File

@ -30,7 +30,7 @@ object GameHelper {
// Ensure keys are loaded so that ROM metadata can be decrypted. // Ensure keys are loaded so that ROM metadata can be decrypted.
NativeLibrary.reloadKeys() NativeLibrary.reloadKeys()
addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3) addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3)
// Cache list of games found on disk // Cache list of games found on disk
val serializedGames = mutableSetOf<String>() val serializedGames = mutableSetOf<String>()
@ -58,7 +58,7 @@ object GameHelper {
if (it.isDirectory) { if (it.isDirectory) {
addGamesRecursive( addGamesRecursive(
games, games,
FileUtil.listFiles(it.uri), FileUtil.listFiles(YuzuApplication.appContext, it.uri),
depth - 1 depth - 1
) )
} else { } else {

View File

@ -3,33 +3,64 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.util.zip.ZipInputStream
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
import java.util.zip.ZipException
import java.util.zip.ZipFile
object GpuDriverHelper { object GpuDriverHelper {
private const val META_JSON_FILENAME = "meta.json" private const val META_JSON_FILENAME = "meta.json"
private const val DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"
private var fileRedirectionPath: String? = null private var fileRedirectionPath: String? = null
var driverInstallationPath: String? = null private var driverInstallationPath: String? = null
private var hookLibPath: String? = null private var hookLibPath: String? = null
val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/" @Throws(IOException::class)
private fun unzip(zipFilePath: String, destDir: String) {
val dir = File(destDir)
fun initializeDriverParameters() { // 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()
}
fun initializeDriverParameters(context: Context) {
try { try {
// Initialize the file redirection directory. // Initialize the file redirection directory.
fileRedirectionPath = YuzuApplication.appContext fileRedirectionPath =
.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/" context.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
// Initialize the driver installation directory. // Initialize the driver installation directory.
driverInstallationPath = YuzuApplication.appContext driverInstallationPath = context.filesDir.canonicalPath + "/gpu_driver/"
.filesDir.canonicalPath + "/gpu_driver/"
} catch (e: IOException) { } catch (e: IOException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
@ -38,169 +69,68 @@ object GpuDriverHelper {
initializeDirectories() initializeDirectories()
// Initialize hook libraries directory. // Initialize hook libraries directory.
hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/" hookLibPath = context.applicationInfo.nativeLibraryDir + "/"
// Initialize GPU driver. // Initialize GPU driver.
NativeLibrary.initializeGpuDriver( NativeLibrary.initializeGpuDriver(
hookLibPath, hookLibPath,
driverInstallationPath, driverInstallationPath,
customDriverData.libraryName, customDriverLibraryName,
fileRedirectionPath fileRedirectionPath
) )
} }
fun getDrivers(): MutableList<Pair<String, GpuDriverMetadata>> { fun installDefaultDriver(context: Context) {
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. // Removing the installed driver will result in the backend using the default system driver.
File(driverInstallationPath!!).deleteRecursively() val driverInstallationDir = File(driverInstallationPath!!)
initializeDriverParameters() deleteRecursive(driverInstallationDir)
initializeDriverParameters(context)
} }
fun copyDriverToInternalStorage(driverUri: Uri): Boolean { fun installCustomDriver(context: Context, driverPathUri: Uri?) {
// 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
}
/**
* 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. // Revert to system default in the event the specified driver is bad.
installDefaultDriver() installDefaultDriver(context)
// Ensure we have directories. // Ensure we have directories.
initializeDirectories() initializeDirectories()
// Copy the zip file URI to user data // Copy the zip file URI into our private storage.
val copiedFile = copyUriToInternalStorage(
FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false context,
driverPathUri,
// Validate driver driverInstallationPath!!,
val metadata = getMetadataFromZip(copiedFile) DRIVER_INTERNAL_FILENAME
if (metadata.name == null) { )
copiedFile.delete()
return false
}
if (metadata.minApi > Build.VERSION.SDK_INT) {
copiedFile.delete()
return false
}
// Unzip the driver. // Unzip the driver.
try { try {
FileUtil.unzipToInternalStorage( unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!)
BufferedInputStream(copiedFile.inputStream()),
File(driverInstallationPath!!)
)
} catch (e: SecurityException) { } catch (e: SecurityException) {
return false return
} }
// Initialize the driver parameters. // Initialize the driver parameters.
initializeDriverParameters() initializeDriverParameters(context)
return true
}
/**
* Unzips driver into installation directory
*/
fun installCustomDriver(driver: File): Boolean {
// Revert to system default in the event the specified driver is bad.
installDefaultDriver()
// Ensure we have directories.
initializeDirectories()
// Validate driver
val metadata = getMetadataFromZip(driver)
if (metadata.name == null) {
driver.delete()
return false
}
// Unzip the driver to the private installation directory
try {
FileUtil.unzipToInternalStorage(
BufferedInputStream(driver.inputStream()),
File(driverInstallationPath!!)
)
} catch (e: SecurityException) {
return false
}
// 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 external fun supportsCustomDriverLoading(): Boolean
// Parse the custom driver metadata to retrieve the name. // Parse the custom driver metadata to retrieve the name.
val customDriverData: GpuDriverMetadata val customDriverName: String?
get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME)) get() {
val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME)
return metadata.name
}
fun initializeDirectories() { // 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
}
private fun initializeDirectories() {
// Ensure the file redirection directory exists. // Ensure the file redirection directory exists.
val fileRedirectionDir = File(fileRedirectionPath!!) val fileRedirectionDir = File(fileRedirectionPath!!)
if (!fileRedirectionDir.exists()) { if (!fileRedirectionDir.exists()) {
@ -211,10 +141,14 @@ object GpuDriverHelper {
if (!driverInstallationDir.exists()) { if (!driverInstallationDir.exists()) {
driverInstallationDir.mkdirs() driverInstallationDir.mkdirs()
} }
// Ensure the driver storage directory exists
val driverStorageDirectory = File(driverStoragePath)
if (!driverStorageDirectory.exists()) {
driverStorageDirectory.mkdirs()
} }
private fun deleteRecursive(fileOrDirectory: File) {
if (fileOrDirectory.isDirectory) {
for (child in fileOrDirectory.listFiles()!!) {
deleteRecursive(child)
}
}
fileOrDirectory.delete()
} }
} }

View File

@ -4,116 +4,44 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import java.io.IOException 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.JSONException
import org.json.JSONObject import org.json.JSONObject
import java.io.File
import java.io.InputStream
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
}
try {
val json = JSONObject(FileUtil.getStringFromFile(metadataFile))
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.
}
}
/**
* 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()
class GpuDriverMetadata(metadataFilePath: String) {
var name: String? = null var name: String? = null
var description: String? = null var description: String? = null
var author: String? = null var author: String? = null
var vendor: String? = null var vendor: String? = null
var version: String? = null var driverVersion: String? = null
var minApi = 0 var minApi = 0
var libraryName: String? = null var libraryName: String? = null
init {
try {
val json = JSONObject(getStringFromFile(metadataFilePath))
name = json.getString("name")
description = json.getString("description")
author = json.getString("author")
vendor = json.getString("vendor")
driverVersion = 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.
}
}
companion object { companion object {
private const val MAX_META_SIZE_BYTES = 500000 @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)
}
} }
} }

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="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>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="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>

View File

@ -1,89 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
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>

View File

@ -1,48 +0,0 @@
<?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>

View File

@ -22,9 +22,6 @@
<action <action
android:id="@+id/action_homeSettingsFragment_to_installableFragment" android:id="@+id/action_homeSettingsFragment_to_installableFragment"
app:destination="@id/installableFragment" /> app:destination="@id/installableFragment" />
<action
android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
app:destination="@id/driverManagerFragment" />
</fragment> </fragment>
<fragment <fragment
@ -98,9 +95,5 @@
android:id="@+id/installableFragment" android:id="@+id/installableFragment"
android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment" android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
android:label="InstallableFragment" /> android:label="InstallableFragment" />
<fragment
android:id="@+id/driverManagerFragment"
android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
android:label="DriverManagerFragment" />
</navigation> </navigation>

View File

@ -168,7 +168,9 @@
<string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string> <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_install">Installieren</string>
<string name="select_gpu_driver_default">Standard</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_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="system_gpu_driver">System GPU-Treiber</string>
<string name="installing_driver">Treiber wird installiert...</string> <string name="installing_driver">Treiber wird installiert...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string> <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_install">Instalar</string>
<string name="select_gpu_driver_default">Predeterminado</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_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="system_gpu_driver">Driver GPU del sistema</string>
<string name="installing_driver">Instalando driver...</string> <string name="installing_driver">Instalando driver...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string> <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_install">Installer</string>
<string name="select_gpu_driver_default">Défaut</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_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="system_gpu_driver">Pilote du GPU du système</string>
<string name="installing_driver">Installation du pilote...</string> <string name="installing_driver">Installation du pilote...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string> <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_install">Installa</string>
<string name="select_gpu_driver_default">Predefinito</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_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="system_gpu_driver">Driver GPU del sistema</string>
<string name="installing_driver">Installando i driver...</string> <string name="installing_driver">Installando i driver...</string>

View File

@ -170,7 +170,9 @@
<string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか</string> <string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか</string>
<string name="select_gpu_driver_install">インストール</string> <string name="select_gpu_driver_install">インストール</string>
<string name="select_gpu_driver_default">デフォルト</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_use_default">デフォルトのGPUドライバーを使用します</string>
<string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string>
<string name="system_gpu_driver">システムのGPUドライバ</string> <string name="system_gpu_driver">システムのGPUドライバ</string>
<string name="installing_driver">インストール中…</string> <string name="installing_driver">インストール中…</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string> <string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
<string name="select_gpu_driver_install">설치</string> <string name="select_gpu_driver_install">설치</string>
<string name="select_gpu_driver_default">기본값</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_use_default">기본 GPU 드라이버 사용</string>
<string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string>
<string name="system_gpu_driver">시스템 GPU 드라이버</string> <string name="system_gpu_driver">시스템 GPU 드라이버</string>
<string name="installing_driver">드라이버 설치 중...</string> <string name="installing_driver">드라이버 설치 중...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string> <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_install">Installer</string>
<string name="select_gpu_driver_default">Standard</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_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="system_gpu_driver">Systemets GPU-driver</string>
<string name="installing_driver">Installerer driver...</string> <string name="installing_driver">Installerer driver...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string> <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_install">Zainstaluj</string>
<string name="select_gpu_driver_default">Domyślne</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_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="system_gpu_driver">Systemowy sterownik GPU</string>
<string name="installing_driver">Instalowanie sterownika...</string> <string name="installing_driver">Instalowanie sterownika...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> <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_install">Instalar</string>
<string name="select_gpu_driver_default">Padrão</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_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="system_gpu_driver">Driver do GPU padrão</string>
<string name="installing_driver">A instalar o Driver...</string> <string name="installing_driver">A instalar o Driver...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string> <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_install">Instalar</string>
<string name="select_gpu_driver_default">Padrão</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_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="system_gpu_driver">Driver do GPU padrão</string>
<string name="installing_driver">A instalar o Driver...</string> <string name="installing_driver">A instalar o Driver...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string> <string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
<string name="select_gpu_driver_install">Установить</string> <string name="select_gpu_driver_install">Установить</string>
<string name="select_gpu_driver_default">По умолчанию</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_use_default">Используется стандартный драйвер ГП </string>
<string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string>
<string name="system_gpu_driver">Системный драйвер ГП</string> <string name="system_gpu_driver">Системный драйвер ГП</string>
<string name="installing_driver">Установка драйвера...</string> <string name="installing_driver">Установка драйвера...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string> <string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
<string name="select_gpu_driver_install">Встановити</string> <string name="select_gpu_driver_install">Встановити</string>
<string name="select_gpu_driver_default">За замовчуванням</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_use_default">Використовується стандартний драйвер ГП</string>
<string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
<string name="system_gpu_driver">Системний драйвер ГП</string> <string name="system_gpu_driver">Системний драйвер ГП</string>
<string name="installing_driver">Встановлення драйвера...</string> <string name="installing_driver">Встановлення драйвера...</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string> <string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
<string name="select_gpu_driver_install">安装</string> <string name="select_gpu_driver_install">安装</string>
<string name="select_gpu_driver_default">系统默认</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_use_default">使用默认 GPU 驱动程序</string>
<string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string>
<string name="system_gpu_driver">系统 GPU 驱动程序</string> <string name="system_gpu_driver">系统 GPU 驱动程序</string>
<string name="installing_driver">正在安装驱动程序…</string> <string name="installing_driver">正在安装驱动程序…</string>

View File

@ -171,7 +171,9 @@
<string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string> <string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
<string name="select_gpu_driver_install">安裝</string> <string name="select_gpu_driver_install">安裝</string>
<string name="select_gpu_driver_default">預設</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_use_default">使用預設 GPU 驅動程式</string>
<string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string>
<string name="system_gpu_driver">系統 GPU 驅動程式</string> <string name="system_gpu_driver">系統 GPU 驅動程式</string>
<string name="installing_driver">正在安裝驅動程式…</string> <string name="installing_driver">正在安裝驅動程式…</string>

View File

@ -13,8 +13,6 @@
<dimen name="menu_width">256dp</dimen> <dimen name="menu_width">256dp</dimen>
<dimen name="card_width">165dp</dimen> <dimen name="card_width">165dp</dimen>
<dimen name="icon_inset">24dp</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="dialog_margin">20dp</dimen>
<dimen name="elevated_app_bar">3dp</dimen> <dimen name="elevated_app_bar">3dp</dimen>

View File

@ -72,7 +72,6 @@
<string name="invalid_keys_error">Invalid encryption keys</string> <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="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="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">Install GPU driver</string>
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string> <string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
<string name="advanced_settings">Advanced settings</string> <string name="advanced_settings">Advanced settings</string>
@ -235,17 +234,15 @@
<string name="export_failed">Export failed</string> <string name="export_failed">Export failed</string>
<string name="import_failed">Import failed</string> <string name="import_failed">Import failed</string>
<string name="cancelling">Cancelling</string> <string name="cancelling">Cancelling</string>
<string name="install">Install</string>
<string name="delete">Delete</string>
<!-- GPU driver installation --> <!-- GPU driver installation -->
<string name="select_gpu_driver">Select GPU driver</string> <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_title">Would you like to replace your current GPU driver?</string>
<string name="select_gpu_driver_install">Install</string> <string name="select_gpu_driver_install">Install</string>
<string name="select_gpu_driver_default">Default</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_use_default">Using default GPU driver</string>
<string name="select_gpu_driver_error">Invalid driver selected</string> <string name="select_gpu_driver_error">Invalid driver selected, using system default!</string>
<string name="driver_already_installed">Driver already installed</string>
<string name="system_gpu_driver">System GPU driver</string> <string name="system_gpu_driver">System GPU driver</string>
<string name="installing_driver">Installing driver…</string> <string name="installing_driver">Installing driver…</string>

View File

@ -3,8 +3,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id("com.android.application") version "8.1.2" apply false id("com.android.application") version "8.0.2" apply false
id("com.android.library") version "8.1.2" apply false id("com.android.library") version "8.0.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.21" apply false id("org.jetbrains.kotlin.android") version "1.8.21" apply false
} }

View File

@ -77,7 +77,6 @@ void AudioRenderer::Wait() {
"{}, got {}", "{}, got {}",
Message::RenderResponse, msg); Message::RenderResponse, msg);
} }
PostDSPClearCommandBuffer();
} }
void AudioRenderer::Send(Direction dir, u32 message) { void AudioRenderer::Send(Direction dir, u32 message) {
@ -97,14 +96,6 @@ void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u
command_buffers[session_id].reset_buffer = reset; command_buffers[session_id].reset_buffer = reset;
} }
void AudioRenderer::PostDSPClearCommandBuffer() noexcept {
for (auto& buffer : command_buffers) {
buffer.buffer = 0;
buffer.size = 0;
buffer.reset_buffer = false;
}
}
u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
return command_buffers[session_id].remaining_command_count; return command_buffers[session_id].remaining_command_count;
} }

View File

@ -85,8 +85,6 @@ private:
*/ */
void CreateSinkStreams(); void CreateSinkStreams();
void PostDSPClearCommandBuffer() noexcept;
/// Core system /// Core system
Core::System& system; Core::System& system;
/// The output sink the AudioRenderer will send samples to /// The output sink the AudioRenderer will send samples to

View File

@ -204,10 +204,6 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
// paused and we'll desync, so just play silence. // paused and we'll desync, so just play silence.
if (system.IsPaused() || system.IsShuttingDown()) { if (system.IsPaused() || system.IsShuttingDown()) {
if (system.IsShuttingDown()) { if (system.IsShuttingDown()) {
{
std::scoped_lock lk{release_mutex};
queued_buffers.store(0);
}
release_cv.notify_one(); release_cv.notify_one();
} }

View File

@ -39,12 +39,8 @@
#define Crash() exit(1) #define Crash() exit(1)
#endif #endif
#define LTO_NOINLINE __attribute__((noinline))
#else // _MSC_VER #else // _MSC_VER
#define LTO_NOINLINE
// Locale Cross-Compatibility // Locale Cross-Compatibility
#define locale_t _locale_t #define locale_t _locale_t

View File

@ -211,11 +211,6 @@ struct Elf64_Rela {
Elf64_Sxword r_addend; /* Addend */ 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. */ /* How to extract and insert information held in the r_info field. */
static inline u32 Elf32RelSymIndex(Elf32_Word r_info) { static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
@ -333,9 +328,6 @@ constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */
constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */ constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */ constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */ 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 ELF
} // namespace Common } // namespace Common

View File

@ -15,13 +15,12 @@
#include <condition_variable> #include <condition_variable>
#include <stop_token> #include <stop_token>
#include <thread> #include <thread>
#include <utility>
namespace Common { namespace Common {
template <typename Condvar, typename Lock, typename Pred> template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) { void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
cv.wait(lk, token, std::forward<Pred>(pred)); cv.wait(lk, token, std::move(pred));
} }
template <typename Rep, typename Period> template <typename Rep, typename Period>
@ -110,7 +109,7 @@ public:
// Insert the callback. // Insert the callback.
stop_state_callback ret = ++m_next_callback; stop_state_callback ret = ++m_next_callback;
m_callbacks.emplace(ret, std::move(f)); m_callbacks.emplace(ret, move(f));
return ret; return ret;
} }
@ -163,7 +162,7 @@ private:
friend class stop_source; friend class stop_source;
template <typename Callback> template <typename Callback>
friend class stop_callback; friend class stop_callback;
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {} stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {}
private: private:
shared_ptr<polyfill::stop_state> m_stop_state; shared_ptr<polyfill::stop_state> m_stop_state;
@ -199,7 +198,7 @@ public:
private: private:
friend class jthread; friend class jthread;
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state) explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
: m_stop_state(std::move(stop_state)) {} : m_stop_state(move(stop_state)) {}
private: private:
shared_ptr<polyfill::stop_state> m_stop_state; shared_ptr<polyfill::stop_state> m_stop_state;
@ -219,16 +218,16 @@ public:
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state) { : m_stop_state(st.m_stop_state) {
if (m_stop_state) { if (m_stop_state) {
m_callback = m_stop_state->insert_callback(std::move(cb)); m_callback = m_stop_state->insert_callback(move(cb));
} }
} }
template <typename C> template <typename C>
requires constructible_from<Callback, C> requires constructible_from<Callback, C>
explicit stop_callback(stop_token&& st, explicit stop_callback(stop_token&& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>) C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(std::move(st.m_stop_state)) { : m_stop_state(move(st.m_stop_state)) {
if (m_stop_state) { if (m_stop_state) {
m_callback = m_stop_state->insert_callback(std::move(cb)); m_callback = m_stop_state->insert_callback(move(cb));
} }
} }
~stop_callback() { ~stop_callback() {
@ -261,7 +260,7 @@ public:
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>> typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args) explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()), : m_stop_state(make_shared<polyfill::stop_state>()),
m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {} m_thread(make_thread(move(f), move(args)...)) {}
~jthread() { ~jthread() {
if (joinable()) { if (joinable()) {
@ -318,9 +317,9 @@ private:
template <typename F, typename... Args> template <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args) { thread make_thread(F&& f, Args&&... args) {
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) { if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...); return thread(move(f), get_stop_token(), move(args)...);
} else { } else {
return thread(std::forward<F>(f), std::forward<Args>(args)...); return thread(move(f), move(args)...);
} }
} }

View File

@ -45,7 +45,6 @@ SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true); SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true); SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true); SWITCHABLE(Language, true);
SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false); SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true); SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true); SWITCHABLE(RendererBackend, true);
@ -62,10 +61,6 @@ SWITCHABLE(u32, false);
SWITCHABLE(u8, false); SWITCHABLE(u8, false);
SWITCHABLE(u8, true); SWITCHABLE(u8, true);
// Used in UISettings
// TODO see if we can move this to uisettings.cpp
SWITCHABLE(ConfirmStop, true);
#undef SETTING #undef SETTING
#undef SWITCHABLE #undef SWITCHABLE
#endif #endif

View File

@ -67,7 +67,6 @@ SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true); SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true); SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true); SWITCHABLE(Language, true);
SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false); SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true); SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true); SWITCHABLE(RendererBackend, true);
@ -84,10 +83,6 @@ SWITCHABLE(u32, false);
SWITCHABLE(u8, false); SWITCHABLE(u8, false);
SWITCHABLE(u8, true); SWITCHABLE(u8, true);
// Used in UISettings
// TODO see if we can move this to uisettings.h
SWITCHABLE(ConfirmStop, true);
#undef SETTING #undef SETTING
#undef SWITCHABLE #undef SWITCHABLE
#endif #endif

View File

@ -133,8 +133,6 @@ ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb); ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
ENUM(FullscreenMode, Borderless, Exclusive); ENUM(FullscreenMode, Borderless, Exclusive);
ENUM(NvdecEmulation, Off, Cpu, Gpu); ENUM(NvdecEmulation, Off, Cpu, Gpu);

View File

@ -3405,11 +3405,6 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K
new_attr, KMemoryBlockDisableMergeAttribute::Locked, new_attr, KMemoryBlockDisableMergeAttribute::Locked,
KMemoryBlockDisableMergeAttribute::None); KMemoryBlockDisableMergeAttribute::None);
// If we have an output page group, open.
if (out_pg) {
out_pg->Open();
}
R_SUCCEED(); R_SUCCEED();
} }

View File

@ -373,7 +373,7 @@ struct KernelCore::Impl {
static inline thread_local u8 host_thread_id = UINT8_MAX; static inline thread_local u8 host_thread_id = UINT8_MAX;
/// Sets the host thread ID for the caller. /// Sets the host thread ID for the caller.
LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) { u32 SetHostThreadId(std::size_t core_id) {
// This should only be called during core init. // This should only be called during core init.
ASSERT(host_thread_id == UINT8_MAX); ASSERT(host_thread_id == UINT8_MAX);
@ -384,13 +384,13 @@ struct KernelCore::Impl {
} }
/// Gets the host thread ID for the caller /// Gets the host thread ID for the caller
LTO_NOINLINE u32 GetHostThreadId() const { u32 GetHostThreadId() const {
return host_thread_id; return host_thread_id;
} }
// Gets the dummy KThread for the caller, allocating a new one if this is the first time // Gets the dummy KThread for the caller, allocating a new one if this is the first time
LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) { KThread* GetHostDummyThread(KThread* existing_thread) {
const auto initialize{[](KThread* thread) LTO_NOINLINE { const auto initialize{[](KThread* thread) {
ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
return thread; return thread;
}}; }};
@ -424,11 +424,11 @@ struct KernelCore::Impl {
static inline thread_local bool is_phantom_mode_for_singlecore{false}; static inline thread_local bool is_phantom_mode_for_singlecore{false};
LTO_NOINLINE bool IsPhantomModeForSingleCore() const { bool IsPhantomModeForSingleCore() const {
return is_phantom_mode_for_singlecore; return is_phantom_mode_for_singlecore;
} }
LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) { void SetIsPhantomModeForSingleCore(bool value) {
ASSERT(!is_multicore); ASSERT(!is_multicore);
is_phantom_mode_for_singlecore = value; is_phantom_mode_for_singlecore = value;
} }
@ -439,14 +439,14 @@ struct KernelCore::Impl {
static inline thread_local KThread* current_thread{nullptr}; static inline thread_local KThread* current_thread{nullptr};
LTO_NOINLINE KThread* GetCurrentEmuThread() { KThread* GetCurrentEmuThread() {
if (!current_thread) { if (!current_thread) {
current_thread = GetHostDummyThread(nullptr); current_thread = GetHostDummyThread(nullptr);
} }
return current_thread; return current_thread;
} }
LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) { void SetCurrentEmuThread(KThread* thread) {
current_thread = thread; current_thread = thread;
} }

View File

@ -16,7 +16,7 @@ namespace Service::Capture {
void LoopProcess(Core::System& system) { void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system); auto server_manager = std::make_unique<ServerManager>(system);
auto album_manager = std::make_shared<AlbumManager>(system); auto album_manager = std::make_shared<AlbumManager>();
server_manager->RegisterNamedService( server_manager->RegisterNamedService(
"caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager)); "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));

View File

@ -128,9 +128,9 @@ void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
ctx.WriteBuffer(entries); ctx.WriteBuffer(entries);
} }
IPC::ResponseBuilder rb{ctx, 4}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result); rb.Push(result);
rb.Push<u64>(entries.size()); rb.Push(entries.size());
} }
void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) { void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {

View File

@ -8,15 +8,12 @@
#include "common/fs/file.h" #include "common/fs/file.h"
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/service/caps/caps_manager.h" #include "core/hle/service/caps/caps_manager.h"
#include "core/hle/service/caps/caps_result.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 { namespace Service::Capture {
AlbumManager::AlbumManager(Core::System& system_) : system{system_} {} AlbumManager::AlbumManager() {}
AlbumManager::~AlbumManager() = default; AlbumManager::~AlbumManager() = default;
@ -86,34 +83,6 @@ Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, Albu
} }
Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries, 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, ContentType contex_type, AlbumFileDateTime start_date,
AlbumFileDateTime end_date, u64 aruid) const { AlbumFileDateTime end_date, u64 aruid) const {
if (!is_mounted) { if (!is_mounted) {
@ -124,25 +93,31 @@ Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_en
if (file_id.type != contex_type) { if (file_id.type != contex_type) {
continue; continue;
} }
if (file_id.date > start_date) { if (file_id.date > start_date) {
continue; continue;
} }
if (file_id.date < end_date) { if (file_id.date < end_date) {
continue; continue;
} }
if (out_entries.size() >= SdAlbumFileLimit) { if (out_entries.size() >= SdAlbumFileLimit) {
break; break;
} }
const auto entry_size = Common::FS::GetSize(path); const auto entry_size = Common::FS::GetSize(path);
ApplicationAlbumEntry entry{ ApplicationAlbumFileEntry entry{.entry =
{
.size = entry_size, .size = entry_size,
.hash{}, .hash{},
.datetime = file_id.date, .datetime = file_id.date,
.storage = file_id.storage, .storage = file_id.storage,
.content = contex_type, .content = contex_type,
.unknown = 1, .unknown = 1,
}; },
.datetime = file_id.date,
.unknown = {}};
out_entries.push_back(entry); out_entries.push_back(entry);
} }
@ -299,12 +274,12 @@ Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem:
.application_id = static_cast<u64>(std::stoll(application, 0, 16)), .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
.date = .date =
{ {
.year = static_cast<s16>(std::stoi(year)), .year = static_cast<u16>(std::stoi(year)),
.month = static_cast<s8>(std::stoi(month)), .month = static_cast<u8>(std::stoi(month)),
.day = static_cast<s8>(std::stoi(day)), .day = static_cast<u8>(std::stoi(day)),
.hour = static_cast<s8>(std::stoi(hour)), .hour = static_cast<u8>(std::stoi(hour)),
.minute = static_cast<s8>(std::stoi(minute)), .minute = static_cast<u8>(std::stoi(minute)),
.second = static_cast<s8>(std::stoi(second)), .second = static_cast<u8>(std::stoi(second)),
.unique_id = 0, .unique_id = 0,
}, },
.storage = AlbumStorage::Sd, .storage = AlbumStorage::Sd,
@ -364,23 +339,4 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
return ResultSuccess; 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 } // namespace Service::Capture

View File

@ -37,7 +37,7 @@ namespace Service::Capture {
class AlbumManager { class AlbumManager {
public: public:
explicit AlbumManager(Core::System& system_); explicit AlbumManager();
~AlbumManager(); ~AlbumManager();
Result DeleteAlbumFile(const AlbumFileId& file_id); Result DeleteAlbumFile(const AlbumFileId& file_id);
@ -45,9 +45,6 @@ public:
Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage, Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
u8 flags) const; u8 flags) const;
Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries, 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, ContentType contex_type, AlbumFileDateTime start_date,
AlbumFileDateTime end_date, u64 aruid) const; AlbumFileDateTime end_date, u64 aruid) const;
Result GetAutoSavingStorage(bool& out_is_autosaving) const; Result GetAutoSavingStorage(bool& out_is_autosaving) const;
@ -68,12 +65,8 @@ private:
Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width, Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
int height, ScreenShotDecoderFlag flag) const; int height, ScreenShotDecoderFlag flag) const;
AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
bool is_mounted{}; bool is_mounted{};
std::unordered_map<AlbumFileId, std::filesystem::path> album_files; std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
Core::System& system;
}; };
} // namespace Service::Capture } // namespace Service::Capture

View File

@ -41,13 +41,13 @@ enum class ScreenShotDecoderFlag : u64 {
// This is nn::capsrv::AlbumFileDateTime // This is nn::capsrv::AlbumFileDateTime
struct AlbumFileDateTime { struct AlbumFileDateTime {
s16 year{}; u16 year{};
s8 month{}; u8 month{};
s8 day{}; u8 day{};
s8 hour{}; u8 hour{};
s8 minute{}; u8 minute{};
s8 second{}; u8 second{};
s8 unique_id{}; u8 unique_id{};
friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default; friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) { friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {

View File

@ -50,35 +50,22 @@ void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) { void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
struct Parameters { const auto pid{rp.Pop<s32>()};
ContentType content_type; const auto content_type{rp.PopEnum<ContentType>()};
INSERT_PADDING_BYTES(7); const auto start_posix_time{rp.Pop<s64>()};
s64 start_posix_time; const auto end_posix_time{rp.Pop<s64>()};
s64 end_posix_time; const auto applet_resource_user_id{rp.Pop<u64>()};
u64 applet_resource_user_id;
};
static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_Capture, LOG_WARNING(Service_Capture,
"(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, " "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
"applet_resource_user_id={}", "end_posix_time={}, applet_resource_user_id={}",
parameters.content_type, parameters.start_posix_time, parameters.end_posix_time, pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id);
parameters.applet_resource_user_id);
Result result = ResultSuccess; // TODO: Translate posix to DateTime
if (result.IsSuccess()) {
result = manager->IsAlbumMounted(AlbumStorage::Sd);
}
std::vector<ApplicationAlbumFileEntry> entries; std::vector<ApplicationAlbumFileEntry> entries;
if (result.IsSuccess()) { const Result result =
result = manager->GetAlbumFileList(entries, parameters.content_type, manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id);
parameters.start_posix_time, parameters.end_posix_time,
parameters.applet_resource_user_id);
}
if (!entries.empty()) { if (!entries.empty()) {
ctx.WriteBuffer(entries); ctx.WriteBuffer(entries);
@ -91,38 +78,19 @@ void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestCo
void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) { void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
struct Parameters { const auto pid{rp.Pop<s32>()};
ContentType content_type; const auto content_type{rp.PopEnum<ContentType>()};
INSERT_PADDING_BYTES(1); const auto start_date_time{rp.PopRaw<AlbumFileDateTime>()};
AlbumFileDateTime start_date_time; const auto end_date_time{rp.PopRaw<AlbumFileDateTime>()};
AlbumFileDateTime end_date_time; const auto applet_resource_user_id{rp.Pop<u64>()};
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, LOG_WARNING(Service_Capture,
"(STUBBED) called. content_type={}, start_date={}/{}/{}, " "(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid,
"end_date={}/{}/{}, applet_resource_user_id={}", content_type, 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);
Result result = ResultSuccess; std::vector<ApplicationAlbumFileEntry> entries;
const Result result = manager->GetAlbumFileList(entries, content_type, start_date_time,
if (result.IsSuccess()) { end_date_time, applet_resource_user_id);
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()) { if (!entries.empty()) {
ctx.WriteBuffer(entries); ctx.WriteBuffer(entries);

View File

@ -156,8 +156,6 @@ public:
bool LoadNRO(std::span<const u8> data) { bool LoadNRO(std::span<const u8> data) {
local_memory.clear(); local_memory.clear();
relocbase = local_memory.size();
local_memory.insert(local_memory.end(), data.begin(), data.end()); local_memory.insert(local_memory.end(), data.begin(), data.end());
if (FixupRelocations()) { if (FixupRelocations()) {
@ -183,8 +181,8 @@ public:
// https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
// https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
VAddr rela_dyn = 0, relr_dyn = 0; VAddr rela_dyn = 0;
size_t num_rela = 0, num_relr = 0; size_t num_rela = 0;
while (true) { while (true) {
const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
dynamic_offset += sizeof(Elf64_Dyn); dynamic_offset += sizeof(Elf64_Dyn);
@ -198,12 +196,6 @@ public:
if (dyn.d_tag == ElfDtRelasz) { if (dyn.d_tag == ElfDtRelasz) {
num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); 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++) { for (size_t i = 0; i < num_rela; i++) {
@ -215,29 +207,6 @@ public:
callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); 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; return true;
} }
@ -344,7 +313,6 @@ public:
Core::Memory::Memory& memory; Core::Memory::Memory& memory;
VAddr top_of_stack; VAddr top_of_stack;
VAddr heap_pointer; VAddr heap_pointer;
VAddr relocbase;
}; };
void DynarmicCallbacks64::CallSVC(u32 swi) { void DynarmicCallbacks64::CallSVC(u32 swi) {

View File

@ -544,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
it++; it++;
} }
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads; boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
u64 total_size_bytes = 0; u64 total_size_bytes = 0;
u64 largest_copy = 0; u64 largest_copy = 0;
for (const IntervalSet& intervals : committed_ranges) { for (const IntervalSet& intervals : committed_ranges) {
@ -914,11 +914,6 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
const u32 offset = buffer.Offset(binding.cpu_addr); const u32 offset = buffer.Offset(binding.cpu_addr);
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
}
if constexpr (NEEDS_BIND_STORAGE_INDEX) { if constexpr (NEEDS_BIND_STORAGE_INDEX) {
runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
++binding_index; ++binding_index;
@ -936,11 +931,6 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
const u32 size = binding.size; const u32 size = binding.size;
SynchronizeBuffer(buffer, binding.cpu_addr, size); SynchronizeBuffer(buffer, binding.cpu_addr, size);
const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
}
const u32 offset = buffer.Offset(binding.cpu_addr); const u32 offset = buffer.Offset(binding.cpu_addr);
const PixelFormat format = binding.format; const PixelFormat format = binding.format;
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@ -972,8 +962,6 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
const u32 size = binding.size; const u32 size = binding.size;
SynchronizeBuffer(buffer, binding.cpu_addr, size); SynchronizeBuffer(buffer, binding.cpu_addr, size);
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
const u32 offset = buffer.Offset(binding.cpu_addr); const u32 offset = buffer.Offset(binding.cpu_addr);
host_bindings.buffers.push_back(&buffer); host_bindings.buffers.push_back(&buffer);
host_bindings.offsets.push_back(offset); host_bindings.offsets.push_back(offset);
@ -1023,11 +1011,6 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
const u32 offset = buffer.Offset(binding.cpu_addr); const u32 offset = buffer.Offset(binding.cpu_addr);
const bool is_written = const bool is_written =
((channel_state->written_compute_storage_buffers >> index) & 1) != 0; ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
}
if constexpr (NEEDS_BIND_STORAGE_INDEX) { if constexpr (NEEDS_BIND_STORAGE_INDEX) {
runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
++binding_index; ++binding_index;
@ -1045,12 +1028,6 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
const u32 size = binding.size; const u32 size = binding.size;
SynchronizeBuffer(buffer, binding.cpu_addr, size); SynchronizeBuffer(buffer, binding.cpu_addr, size);
const bool is_written =
((channel_state->written_compute_texture_buffers >> index) & 1) != 0;
if (is_written) {
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
}
const u32 offset = buffer.Offset(binding.cpu_addr); const u32 offset = buffer.Offset(binding.cpu_addr);
const PixelFormat format = binding.format; const PixelFormat format = binding.format;
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@ -1224,11 +1201,16 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
template <class P> template <class P>
void BufferCache<P>::UpdateStorageBuffers(size_t stage) { void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
const u32 written_mask = channel_state->written_storage_buffers[stage];
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
// Resolve buffer // Resolve buffer
Binding& binding = channel_state->storage_buffers[stage][index]; Binding& binding = channel_state->storage_buffers[stage][index];
const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
binding.buffer_id = buffer_id; binding.buffer_id = buffer_id;
// Mark buffer as written if needed
if (((written_mask >> index) & 1) != 0) {
MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
}
}); });
} }
@ -1237,6 +1219,10 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
Binding& binding = channel_state->texture_buffers[stage][index]; Binding& binding = channel_state->texture_buffers[stage][index];
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
// Mark buffer as written if needed
if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) {
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
}
}); });
} }
@ -1266,6 +1252,7 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
.size = size, .size = size,
.buffer_id = buffer_id, .buffer_id = buffer_id,
}; };
MarkWrittenBuffer(buffer_id, *cpu_addr, size);
} }
template <class P> template <class P>
@ -1292,6 +1279,10 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
// Resolve buffer // Resolve buffer
Binding& binding = channel_state->compute_storage_buffers[index]; Binding& binding = channel_state->compute_storage_buffers[index];
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
// Mark as written if needed
if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) {
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
}
}); });
} }
@ -1300,11 +1291,18 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
Binding& binding = channel_state->compute_texture_buffers[index]; Binding& binding = channel_state->compute_texture_buffers[index];
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
// Mark as written if needed
if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) {
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
}
}); });
} }
template <class P> template <class P>
void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) {
SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size);
}
memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
const IntervalType base_interval{cpu_addr, cpu_addr + size}; const IntervalType base_interval{cpu_addr, cpu_addr + size};

View File

@ -48,14 +48,8 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
SetInlineIndexBuffer(regs.inline_index_4x8.index3); SetInlineIndexBuffer(regs.inline_index_4x8.index3);
break; break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_first): case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
regs.vertex_array_instance_first.start.Value(),
regs.vertex_array_instance_first.count.Value(), false);
break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): { case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(), LOG_WARNING(HW_GPU, "(STUBBED) called");
regs.vertex_array_instance_subsequent.start.Value(),
regs.vertex_array_instance_subsequent.count.Value(), true);
break; break;
} }
case MAXWELL3D_REG_INDEX(draw_texture.src_y0): { case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
@ -90,22 +84,6 @@ void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 ve
ProcessDraw(false, num_instances); ProcessDraw(false, num_instances);
} }
void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
bool subsequent) {
draw_state.topology = topology;
draw_state.vertex_buffer.first = vertex_first;
draw_state.vertex_buffer.count = vertex_count;
if (!subsequent) {
draw_state.instance_count = 1;
}
draw_state.base_instance = draw_state.instance_count - 1;
draw_state.draw_mode = DrawMode::Instance;
draw_state.instance_count++;
ProcessDraw(false, 1);
}
void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
u32 base_index, u32 base_instance, u32 num_instances) { u32 base_index, u32 base_instance, u32 num_instances) {
const auto& regs{maxwell3d->regs}; const auto& regs{maxwell3d->regs};

View File

@ -66,8 +66,6 @@ public:
void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count, void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
u32 base_instance, u32 num_instances); u32 base_instance, u32 num_instances);
void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
bool subsequent);
void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index, void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index,
u32 base_instance, u32 num_instances); u32 base_instance, u32 num_instances);

View File

@ -1048,10 +1048,6 @@ void Image::Scale(bool up_scale) {
} }
bool Image::ScaleUp(bool ignore) { bool Image::ScaleUp(bool ignore) {
const auto& resolution = runtime->resolution;
if (!resolution.active) {
return false;
}
if (True(flags & ImageFlagBits::Rescaled)) { if (True(flags & ImageFlagBits::Rescaled)) {
return false; return false;
} }
@ -1064,6 +1060,9 @@ bool Image::ScaleUp(bool ignore) {
return false; return false;
} }
flags |= ImageFlagBits::Rescaled; flags |= ImageFlagBits::Rescaled;
if (!runtime->resolution.active) {
return false;
}
has_scaled = true; has_scaled = true;
if (ignore) { if (ignore) {
current_texture = upscaled_backup.handle; current_texture = upscaled_backup.handle;
@ -1074,14 +1073,13 @@ bool Image::ScaleUp(bool ignore) {
} }
bool Image::ScaleDown(bool ignore) { bool Image::ScaleDown(bool ignore) {
const auto& resolution = runtime->resolution;
if (!resolution.active) {
return false;
}
if (False(flags & ImageFlagBits::Rescaled)) { if (False(flags & ImageFlagBits::Rescaled)) {
return false; return false;
} }
flags &= ~ImageFlagBits::Rescaled; flags &= ~ImageFlagBits::Rescaled;
if (!runtime->resolution.active) {
return false;
}
if (ignore) { if (ignore) {
current_texture = texture.handle; current_texture = texture.handle;
return true; return true;

View File

@ -118,8 +118,6 @@ public:
void InsertUploadMemoryBarrier(); void InsertUploadMemoryBarrier();
void TransitionImageLayout(Image& image) {}
FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
bool HasNativeBgr() const noexcept { bool HasNativeBgr() const noexcept {

View File

@ -1530,15 +1530,15 @@ bool Image::IsRescaled() const noexcept {
} }
bool Image::ScaleUp(bool ignore) { bool Image::ScaleUp(bool ignore) {
const auto& resolution = runtime->resolution;
if (!resolution.active) {
return false;
}
if (True(flags & ImageFlagBits::Rescaled)) { if (True(flags & ImageFlagBits::Rescaled)) {
return false; return false;
} }
ASSERT(info.type != ImageType::Linear); ASSERT(info.type != ImageType::Linear);
flags |= ImageFlagBits::Rescaled; flags |= ImageFlagBits::Rescaled;
const auto& resolution = runtime->resolution;
if (!resolution.active) {
return false;
}
has_scaled = true; has_scaled = true;
if (!scaled_image) { if (!scaled_image) {
const bool is_2d = info.type == ImageType::e2D; const bool is_2d = info.type == ImageType::e2D;
@ -1567,15 +1567,15 @@ bool Image::ScaleUp(bool ignore) {
} }
bool Image::ScaleDown(bool ignore) { bool Image::ScaleDown(bool ignore) {
const auto& resolution = runtime->resolution;
if (!resolution.active) {
return false;
}
if (False(flags & ImageFlagBits::Rescaled)) { if (False(flags & ImageFlagBits::Rescaled)) {
return false; return false;
} }
ASSERT(info.type != ImageType::Linear); ASSERT(info.type != ImageType::Linear);
flags &= ~ImageFlagBits::Rescaled; flags &= ~ImageFlagBits::Rescaled;
const auto& resolution = runtime->resolution;
if (!resolution.active) {
return false;
}
current_image = *original_image; current_image = *original_image;
if (ignore) { if (ignore) {
return true; return true;
@ -2013,32 +2013,4 @@ void TextureCacheRuntime::AccelerateImageUpload(
ASSERT(false); 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 } // namespace Vulkan

View File

@ -92,8 +92,6 @@ public:
void InsertUploadMemoryBarrier() {} void InsertUploadMemoryBarrier() {}
void TransitionImageLayout(Image& image);
bool HasBrokenTextureViewFormats() const noexcept { bool HasBrokenTextureViewFormats() const noexcept {
// No known Vulkan driver has broken image views // No known Vulkan driver has broken image views
return false; return false;

View File

@ -1016,7 +1016,6 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
runtime.TransitionImageLayout(image);
return; return;
} }
if (True(image.flags & ImageFlagBits::AsynchronousDecode)) { if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {

View File

@ -885,7 +885,7 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
}; };
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
const Extent3D block = const Extent3D block =
AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels); AdjustMipBlockSize(num_tiles, info.block, level, level_info.num_levels);
const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2); const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2);
size_t guest_layer_offset = 0; size_t guest_layer_offset = 0;
@ -1062,7 +1062,7 @@ boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const I
const Extent3D level_size = AdjustMipSize(size, level); const Extent3D level_size = AdjustMipSize(size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
const Extent3D block = const Extent3D block =
AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels); AdjustMipBlockSize(num_tiles, info.block, level, level_info.num_levels);
params[level] = SwizzleParameters{ params[level] = SwizzleParameters{
.num_tiles = num_tiles, .num_tiles = num_tiles,
.block = block, .block = block,

View File

@ -66,10 +66,9 @@ struct Range {
switch (usage) { switch (usage) {
case MemoryUsage::Upload: case MemoryUsage::Upload:
case MemoryUsage::Stream: case MemoryUsage::Stream:
return VMA_ALLOCATION_CREATE_MAPPED_BIT | return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download: case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal: case MemoryUsage::DeviceLocal:
return {}; return {};
} }
@ -253,7 +252,8 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const { vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
const VmaAllocationCreateInfo alloc_ci = { const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT |
MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage), .usage = MemoryUsageVma(usage),
.requiredFlags = 0, .requiredFlags = 0,
.preferredFlags = MemoryUsagePreferedVmaFlags(usage), .preferredFlags = MemoryUsagePreferedVmaFlags(usage),

View File

@ -384,7 +384,7 @@ if (USE_DISCORD_PRESENCE)
discord_impl.cpp discord_impl.cpp
discord_impl.h discord_impl.h
) )
target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network) target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib)
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
endif() endif()

View File

@ -23,7 +23,6 @@
#include "yuzu/configuration/configure_vibration.h" #include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h" #include "yuzu/configuration/input_profiles.h"
#include "yuzu/main.h" #include "yuzu/main.h"
#include "yuzu/util/controller_navigation.h"
namespace { namespace {
@ -133,8 +132,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
}; };
ui->labelError->setVisible(false);
// Setup/load everything prior to setting up connections. // Setup/load everything prior to setting up connections.
// This avoids unintentionally changing the states of elements while loading them in. // This avoids unintentionally changing the states of elements while loading them in.
SetSupportedControllers(); SetSupportedControllers();
@ -146,8 +143,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
LoadConfiguration(); LoadConfiguration();
controller_navigation = new ControllerNavigation(system.HIDCore(), this);
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
SetExplainText(i); SetExplainText(i);
UpdateControllerIcon(i); UpdateControllerIcon(i);
@ -156,8 +151,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
if (checked) { if (checked) {
// Hide eventual error message about number of controllers
ui->labelError->setVisible(false);
for (std::size_t index = 0; index <= i; ++index) { for (std::size_t index = 0; index <= i; ++index) {
connected_controller_checkboxes[index]->setChecked(checked); connected_controller_checkboxes[index]->setChecked(checked);
} }
@ -206,12 +199,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
&QtControllerSelectorDialog::ApplyConfiguration); &QtControllerSelectorDialog::ApplyConfiguration);
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
[this](Qt::Key key) {
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
QCoreApplication::postEvent(this, event);
});
// Enhancement: Check if the parameters have already been met before disconnecting controllers. // Enhancement: Check if the parameters have already been met before disconnecting controllers.
// If all the parameters are met AND only allows a single player, // If all the parameters are met AND only allows a single player,
// stop the constructor here as we do not need to continue. // stop the constructor here as we do not need to continue.
@ -230,7 +217,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
} }
QtControllerSelectorDialog::~QtControllerSelectorDialog() { QtControllerSelectorDialog::~QtControllerSelectorDialog() {
controller_navigation->UnloadController();
system.HIDCore().DisableAllControllerConfiguration(); system.HIDCore().DisableAllControllerConfiguration();
} }
@ -305,31 +291,6 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
dialog.exec(); dialog.exec();
} }
void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
const auto num_connected_players = static_cast<int>(
std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
[](const QGroupBox* player) { return player->isChecked(); }));
const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
// Display error message when trying to validate using "Enter" and "OK" button is disabled
ui->labelError->setVisible(true);
return;
} else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
// Remove a player if possible
connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
return;
} else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
// Add a player, if possible
ui->labelError->setVisible(false);
connected_controller_checkboxes[num_connected_players]->setChecked(true);
return;
}
QDialog::keyPressEvent(evt);
}
bool QtControllerSelectorDialog::CheckIfParametersMet() { bool QtControllerSelectorDialog::CheckIfParametersMet() {
// Here, we check and validate the current configuration against all applicable parameters. // Here, we check and validate the current configuration against all applicable parameters.
const auto num_connected_players = static_cast<int>( const auto num_connected_players = static_cast<int>(

View File

@ -34,8 +34,6 @@ class HIDCore;
enum class NpadStyleIndex : u8; enum class NpadStyleIndex : u8;
} // namespace Core::HID } // namespace Core::HID
class ControllerNavigation;
class QtControllerSelectorDialog final : public QDialog { class QtControllerSelectorDialog final : public QDialog {
Q_OBJECT Q_OBJECT
@ -48,8 +46,6 @@ public:
int exec() override; int exec() override;
void keyPressEvent(QKeyEvent* evt) override;
private: private:
// Applies the current configuration. // Applies the current configuration.
void ApplyConfiguration(); void ApplyConfiguration();
@ -114,8 +110,6 @@ private:
Core::System& system; Core::System& system;
ControllerNavigation* controller_navigation = nullptr;
// This is true if and only if all parameters are met. Otherwise, this is false. // This is true if and only if all parameters are met. Otherwise, this is false.
// This determines whether the "OK" button can be clicked to exit the applet. // This determines whether the "OK" button can be clicked to exit the applet.
bool parameters_met{false}; bool parameters_met{false};

View File

@ -2624,43 +2624,6 @@
</spacer> </spacer>
</item> </item>
<item alignment="Qt::AlignBottom"> <item alignment="Qt::AlignBottom">
<widget class="QWidget" name="closeButtons" native="true">
<layout class="QVBoxLayout" name="verticalLayout_46">
<property name="spacing">
<number>7</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="labelError">
<property name="enabled">
<bool>true</bool>
</property>
<property name="styleSheet">
<string notr="true">QLabel { color : red; }</string>
</property>
<property name="text">
<string>Not enough controllers</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
@ -2678,9 +2641,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
</item>
</layout>
</widget>
<resources/> <resources/>
<connections> <connections>
<connection> <connection>

View File

@ -128,8 +128,8 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}}, {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},

View File

@ -115,9 +115,17 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
for (std::size_t i = 0; i < player_tabs.size(); ++i) { for (std::size_t i = 0; i < player_tabs.size(); ++i) {
player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i])); player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
player_tabs[i]->layout()->addWidget(player_controllers[i]); player_tabs[i]->layout()->addWidget(player_controllers[i]);
connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) { connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) {
// Ensures that the controllers are always connected in sequential order // Ensures that the controllers are always connected in sequential order
this->propagateMouseClickOnPlayers(i, checked, true); if (is_connected) {
for (std::size_t index = 0; index <= i; ++index) {
player_connected[index]->setChecked(is_connected);
}
} else {
for (std::size_t index = i; index < player_tabs.size(); ++index) {
player_connected[index]->setChecked(is_connected);
}
}
}); });
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this, connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
&ConfigureInput::UpdateAllInputDevices); &ConfigureInput::UpdateAllInputDevices);
@ -175,30 +183,6 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
LoadConfiguration(); LoadConfiguration();
} }
void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) {
// Origin has already been toggled
if (!origin) {
player_connected[player_index]->setChecked(checked);
}
if (checked) {
// Check all previous buttons when checked
if (player_index > 0) {
propagateMouseClickOnPlayers(player_index - 1, checked, false);
}
} else {
// Unchecked all following buttons when unchecked
if (player_index < player_tabs.size() - 1) {
// Reconnect current player if it was the last one checked
// (player number was reduced by more than one)
if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) {
player_connected[player_index]->setCheckState(Qt::Checked);
}
propagateMouseClickOnPlayers(player_index + 1, checked, false);
}
}
}
QList<QWidget*> ConfigureInput::GetSubTabs() const { QList<QWidget*> ConfigureInput::GetSubTabs() const {
return { return {
ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,

View File

@ -56,7 +56,6 @@ private:
void UpdateDockedState(bool is_handheld); void UpdateDockedState(bool is_handheld);
void UpdateAllInputDevices(); void UpdateAllInputDevices();
void UpdateAllInputProfiles(std::size_t player_index); void UpdateAllInputProfiles(std::size_t player_index);
void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked);
/// Load configuration settings. /// Load configuration settings.
void LoadConfiguration(); void LoadConfiguration();

View File

@ -157,7 +157,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", ""); INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", "");
INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", ""); INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", "");
INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", ""); INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", "");
INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", "");
INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", ""); INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
INSERT(UISettings, controller_applet_disabled, "Disable controller applet", ""); INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
@ -384,13 +383,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
translations->insert( translations->insert(
{Settings::EnumMetadata<Settings::ConsoleMode>::Index(), {Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
{PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}}); {PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
translations->insert(
{Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
{
PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"),
PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"),
PAIR(ConfirmStop, Ask_Never, "Never ask"),
}});
#undef PAIR #undef PAIR
#undef CTX_PAIR #undef CTX_PAIR

View File

@ -826,13 +826,12 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time); 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 // Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount()); item_model->removeRows(0, item_model->rowCount());
search_field->clear(); search_field->clear();
emit ShouldCancelWorker();
GameListWorker* worker = GameListWorker* worker =
new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);

View File

@ -293,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
GameListDir* parent_dir) { GameListDir* parent_dir) {
const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool { const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool {
if (stop_requested) { if (stop_processing) {
// Breaks the callback loop. // Breaks the callback loop.
return false; return false;
} }
@ -399,6 +399,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
} }
void GameListWorker::run() { void GameListWorker::run() {
stop_processing = false;
provider->ClearAllEntries(); provider->ClearAllEntries();
for (UISettings::GameDir& game_dir : game_dirs) { for (UISettings::GameDir& game_dir : game_dirs) {
@ -426,11 +427,9 @@ void GameListWorker::run() {
} }
emit Finished(watch_list); emit Finished(watch_list);
processing_completed.Set();
} }
void GameListWorker::Cancel() { void GameListWorker::Cancel() {
this->disconnect(); this->disconnect();
stop_requested.store(true); stop_processing = true;
processing_completed.Wait();
} }

View File

@ -12,7 +12,6 @@
#include <QRunnable> #include <QRunnable>
#include <QString> #include <QString>
#include "common/thread.h"
#include "yuzu/compatibility_list.h" #include "yuzu/compatibility_list.h"
#include "yuzu/play_time_manager.h" #include "yuzu/play_time_manager.h"
@ -83,9 +82,7 @@ private:
const PlayTime::PlayTimeManager& play_time_manager; const PlayTime::PlayTimeManager& play_time_manager;
QStringList watch_list; QStringList watch_list;
std::atomic_bool stop_processing;
Common::Event processing_completed;
std::atomic_bool stop_requested = false;
Core::System& system; Core::System& system;
}; };

View File

@ -67,7 +67,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#define QT_NO_OPENGL #define QT_NO_OPENGL
#include <QClipboard> #include <QClipboard>
#include <QDesktopServices> #include <QDesktopServices>
#include <QDir>
#include <QFile> #include <QFile>
#include <QFileDialog> #include <QFileDialog>
#include <QInputDialog> #include <QInputDialog>
@ -77,7 +76,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QPushButton> #include <QPushButton>
#include <QScreen> #include <QScreen>
#include <QShortcut> #include <QShortcut>
#include <QStandardPaths>
#include <QStatusBar> #include <QStatusBar>
#include <QString> #include <QString>
#include <QSysInfo> #include <QSysInfo>
@ -211,7 +209,7 @@ void GMainWindow::ShowTelemetryCallout() {
tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
"data is collected</a> to help improve yuzu. " "data is collected</a> to help improve yuzu. "
"<br/><br/>Would you like to share your usage data with us?"); "<br/><br/>Would you like to share your usage data with us?");
if (!question(this, tr("Telemetry"), telemetry_message)) { if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) {
Settings::values.enable_telemetry = false; Settings::values.enable_telemetry = false;
system->ApplySettings(); system->ApplySettings();
} }
@ -2420,8 +2418,9 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
} }
}(); }();
if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No, if (QMessageBox::question(this, tr("Remove Entry"), entry_question,
QMessageBox::No)) { QMessageBox::Yes | QMessageBox::No,
QMessageBox::No) != QMessageBox::Yes) {
return; return;
} }
@ -2520,8 +2519,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
} }
}(); }();
if (!GMainWindow::question(this, tr("Remove File"), question, if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { QMessageBox::No) != QMessageBox::Yes) {
return; return;
} }
@ -2870,50 +2869,44 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
#endif // __linux__ #endif // __linux__
std::filesystem::path target_directory{}; std::filesystem::path target_directory{};
// Determine target directory for shortcut
switch (target) { #if defined(WIN32)
case GameListShortcutTarget::Desktop: { const char* home = std::getenv("USERPROFILE");
const QString desktop_path = #else
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"); const char* home = std::getenv("HOME");
if (home != nullptr) { #endif
target_directory = std::filesystem::path(home) / ".local/share/applications"; const std::filesystem::path home_path = (home == nullptr ? "~" : home);
} const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
} else {
target_directory = applications_path.toUtf8().toStdString();
}
break;
}
default:
return;
}
const QDir dir(QString::fromStdString(target_directory.generic_string())); if (target == GameListShortcutTarget::Desktop) {
if (!dir.exists()) { target_directory = home_path / "Desktop";
QMessageBox::critical(this, tr("Create Shortcut"), if (!Common::FS::IsDir(target_directory)) {
tr("Cannot create shortcut. Path \"%1\" does not exist.") QMessageBox::critical(
this, tr("Create Shortcut"),
tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
.arg(QString::fromStdString(target_directory.generic_string())), .arg(QString::fromStdString(target_directory.generic_string())),
QMessageBox::StandardButton::Ok); QMessageBox::StandardButton::Ok);
return; 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;
}
}
const std::string game_file_name = std::filesystem::path(game_path).filename().string(); const std::string game_file_name = std::filesystem::path(game_path).filename().string();
// Determine full paths for icon and shortcut // Determine full paths for icon and shortcut
#if defined(__linux__) || defined(__FreeBSD__) #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 = std::filesystem::path system_icons_path =
(xdg_data_home == nullptr ? home_path / ".local/share/" (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) /
: std::filesystem::path(xdg_data_home)) /
"icons/hicolor/256x256"; "icons/hicolor/256x256";
if (!Common::FS::CreateDirs(system_icons_path)) { if (!Common::FS::CreateDirs(system_icons_path)) {
QMessageBox::critical( QMessageBox::critical(
@ -3408,13 +3401,10 @@ void GMainWindow::OnRestartGame() {
if (!system->IsPoweredOn()) { if (!system->IsPoweredOn()) {
return; return;
} }
if (ConfirmShutdownGame()) {
// Make a copy since ShutdownGame edits game_path // Make a copy since ShutdownGame edits game_path
const auto current_game = QString(current_game_path); const auto current_game = QString(current_game_path);
ShutdownGame(); ShutdownGame();
BootGame(current_game); BootGame(current_game);
}
} }
void GMainWindow::OnPauseGame() { void GMainWindow::OnPauseGame() {
@ -3436,7 +3426,10 @@ void GMainWindow::OnPauseContinueGame() {
} }
void GMainWindow::OnStopGame() { void GMainWindow::OnStopGame() {
if (ConfirmShutdownGame()) { if (system->GetExitLocked() && !ConfirmForceLockedExit()) {
return;
}
play_time_manager->Stop(); play_time_manager->Stop();
// Update game list to show new play time // Update game list to show new play time
game_list->PopulateAsync(UISettings::values.game_dirs); game_list->PopulateAsync(UISettings::values.game_dirs);
@ -3445,30 +3438,6 @@ void GMainWindow::OnStopGame() {
} else { } else {
OnEmulationStopped(); OnEmulationStopped();
} }
}
}
bool GMainWindow::ConfirmShutdownGame() {
if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) {
if (system->GetExitLocked()) {
if (!ConfirmForceLockedExit()) {
return false;
}
} else {
if (!ConfirmChangeGame()) {
return false;
}
}
} else {
if (UISettings::values.confirm_before_stopping.GetValue() ==
ConfirmStop::Ask_Based_On_Game &&
system->GetExitLocked()) {
if (!ConfirmForceLockedExit()) {
return false;
}
}
}
return true;
} }
void GMainWindow::OnLoadComplete() { void GMainWindow::OnLoadComplete() {
@ -3848,11 +3817,22 @@ void GMainWindow::OnTasRecord() {
const bool is_recording = input_subsystem->GetTas()->Record(); const bool is_recording = input_subsystem->GetTas()->Record();
if (!is_recording) { if (!is_recording) {
is_tas_recording_dialog_active = true; is_tas_recording_dialog_active = true;
ControllerNavigation* controller_navigation =
bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"), new ControllerNavigation(system->HIDCore(), this);
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); // Use QMessageBox instead of question so we can link controller navigation
QMessageBox* box_dialog = new QMessageBox();
input_subsystem->GetTas()->SaveRecording(answer); box_dialog->setWindowTitle(tr("TAS Recording"));
box_dialog->setText(tr("Overwrite file of player 1?"));
box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
box_dialog->setDefaultButton(QMessageBox::Yes);
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
[box_dialog](Qt::Key key) {
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
QCoreApplication::postEvent(box_dialog, event);
});
int res = box_dialog->exec();
controller_navigation->UnloadController();
input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
is_tas_recording_dialog_active = false; is_tas_recording_dialog_active = false;
} }
OnTasStateChanged(); OnTasStateChanged();
@ -4093,29 +4073,6 @@ void GMainWindow::OnLoadAmiibo() {
LoadAmiibo(filename); LoadAmiibo(filename);
} }
bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons,
QMessageBox::StandardButton defaultButton) {
QMessageBox* box_dialog = new QMessageBox(parent);
box_dialog->setWindowTitle(title);
box_dialog->setText(text);
box_dialog->setStandardButtons(buttons);
box_dialog->setDefaultButton(defaultButton);
ControllerNavigation* controller_navigation =
new ControllerNavigation(system->HIDCore(), box_dialog);
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
[box_dialog](Qt::Key key) {
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
QCoreApplication::postEvent(box_dialog, event);
});
int res = box_dialog->exec();
controller_navigation->UnloadController();
return res == QMessageBox::Yes;
}
void GMainWindow::LoadAmiibo(const QString& filename) { void GMainWindow::LoadAmiibo(const QString& filename) {
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
const QString title = tr("Error loading Amiibo data"); const QString title = tr("Error loading Amiibo data");
@ -4849,7 +4806,8 @@ bool GMainWindow::ConfirmClose() {
return true; return true;
} }
const auto text = tr("Are you sure you want to close yuzu?"); const auto text = tr("Are you sure you want to close yuzu?");
return question(this, tr("yuzu"), text); const auto answer = QMessageBox::question(this, tr("yuzu"), text);
return answer != QMessageBox::No;
} }
void GMainWindow::closeEvent(QCloseEvent* event) { void GMainWindow::closeEvent(QCloseEvent* event) {
@ -4942,11 +4900,11 @@ bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr) if (emu_thread == nullptr)
return true; return true;
// Use custom question to link controller navigation const auto answer = QMessageBox::question(
return question(
this, tr("yuzu"), this, tr("yuzu"),
tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."), tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
return answer != QMessageBox::No;
} }
bool GMainWindow::ConfirmForceLockedExit() { bool GMainWindow::ConfirmForceLockedExit() {
@ -4956,7 +4914,8 @@ bool GMainWindow::ConfirmForceLockedExit() {
const auto text = tr("The currently running application has requested yuzu to not exit.\n\n" const auto text = tr("The currently running application has requested yuzu to not exit.\n\n"
"Would you like to bypass this and exit anyway?"); "Would you like to bypass this and exit anyway?");
return question(this, tr("yuzu"), text); const auto answer = QMessageBox::question(this, tr("yuzu"), text);
return answer != QMessageBox::No;
} }
void GMainWindow::RequestGameExit() { void GMainWindow::RequestGameExit() {

View File

@ -7,7 +7,6 @@
#include <optional> #include <optional>
#include <QMainWindow> #include <QMainWindow>
#include <QMessageBox>
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
@ -16,7 +15,6 @@
#include "input_common/drivers/tas_input.h" #include "input_common/drivers/tas_input.h"
#include "yuzu/compatibility_list.h" #include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h" #include "yuzu/hotkeys.h"
#include "yuzu/util/controller_navigation.h"
#ifdef __unix__ #ifdef __unix__
#include <QVariant> #include <QVariant>
@ -426,11 +424,6 @@ private:
bool CheckSystemArchiveDecryption(); bool CheckSystemArchiveDecryption();
bool CheckFirmwarePresence(); bool CheckFirmwarePresence();
void ConfigureFilesystemProvider(const std::string& filepath); void ConfigureFilesystemProvider(const std::string& filepath);
/**
* Open (or not) the right confirm dialog based on current setting and game exit lock
* @returns true if the player confirmed or the settings do no require it
*/
bool ConfirmShutdownGame();
QString GetTasStateDescription() const; QString GetTasStateDescription() const;
bool CreateShortcut(const std::string& shortcut_path, const std::string& title, bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
@ -438,17 +431,6 @@ private:
const std::string& command, const std::string& arguments, const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords); const std::string& categories, const std::string& keywords);
/**
* Mimic the behavior of QMessageBox::question but link controller navigation to the dialog
* The only difference is that it returns a boolean.
*
* @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button.
*/
bool question(QWidget* parent, const QString& title, const QString& text,
QMessageBox::StandardButtons buttons =
QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
std::unique_ptr<Ui::MainWindow> ui; std::unique_ptr<Ui::MainWindow> ui;
std::unique_ptr<Core::System> system; std::unique_ptr<Core::System> system;

View File

@ -16,9 +16,7 @@
#include "common/settings_enums.h" #include "common/settings_enums.h"
using Settings::Category; using Settings::Category;
using Settings::ConfirmStop;
using Settings::Setting; using Settings::Setting;
using Settings::SwitchableSetting;
#ifndef CANNOT_EXPLICITLY_INSTANTIATE #ifndef CANNOT_EXPLICITLY_INSTANTIATE
namespace Settings { namespace Settings {
@ -96,15 +94,6 @@ struct Values {
Setting<bool> confirm_before_closing{ Setting<bool> confirm_before_closing{
linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default, linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
true, true}; true, true};
SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
ConfirmStop::Ask_Always,
"confirmStop",
Category::UiGeneral,
Settings::Specialization::Default,
true,
true};
Setting<bool> first_start{linkage, true, "firstStart", Category::Ui}; Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
Setting<bool> pause_when_in_background{linkage, Setting<bool> pause_when_in_background{linkage,
false, false,

View File

@ -63,15 +63,25 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
}; };
#pragma pack(pop) #pragma pack(pop)
const QImage source_image = image.convertToFormat(QImage::Format_RGB32); QImage source_image = image.convertToFormat(QImage::Format_RGB32);
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
constexpr int bytes_per_pixel = 4; constexpr int bytes_per_pixel = 4;
const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
const IconDir icon_dir{ BITMAPINFOHEADER info_header{};
.id_reserved = 0, info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
.id_type = 1, info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
.id_count = static_cast<WORD>(scale_sizes.size()), info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB;
};
const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
.height = static_cast<BYTE>(source_image.height() * 2),
.color_count = 0,
.reserved = 0,
.planes = 1,
.bit_count = bytes_per_pixel * 8,
.bytes_in_res =
static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
.image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write, Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile); Common::FS::FileType::BinaryFile);
@ -82,56 +92,21 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
if (!icon_file.Write(icon_dir)) { if (!icon_file.Write(icon_dir)) {
return false; return false;
} }
std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
const IconDirEntry icon_entry{
.width = static_cast<BYTE>(scale_sizes[i]),
.height = static_cast<BYTE>(scale_sizes[i]),
.color_count = 0,
.reserved = 0,
.planes = 1,
.bit_count = bytes_per_pixel * 8,
.bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
.image_offset = static_cast<DWORD>(image_offset),
};
image_offset += icon_entry.bytes_in_res;
if (!icon_file.Write(icon_entry)) { if (!icon_file.Write(icon_entry)) {
return false; return false;
} }
}
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const QImage scaled_image = source_image.scaled(
scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
const BITMAPINFOHEADER info_header{
.biSize = sizeof(BITMAPINFOHEADER),
.biWidth = scaled_image.width(),
.biHeight = scaled_image.height() * 2,
.biPlanes = 1,
.biBitCount = bytes_per_pixel * 8,
.biCompression = BI_RGB,
.biSizeImage{},
.biXPelsPerMeter{},
.biYPelsPerMeter{},
.biClrUsed{},
.biClrImportant{},
};
if (!icon_file.Write(info_header)) { if (!icon_file.Write(info_header)) {
return false; return false;
} }
for (int y = 0; y < scaled_image.height(); y++) { for (int y = 0; y < image.height(); y++) {
const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y); const auto* line = source_image.scanLine(source_image.height() - 1 - y);
std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel); std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
std::memcpy(line_data.data(), line, line_data.size()); std::memcpy(line_data.data(), line, line_data.size());
if (!icon_file.Write(line_data)) { if (!icon_file.Write(line_data)) {
return false; return false;
} }
} }
}
icon_file.Close(); icon_file.Close();
return true; return true;