Compare commits
1 Commits
android-10
...
android-97
Author | SHA1 | Date | |
---|---|---|---|
a0ce33806a |
@ -5,6 +5,6 @@
|
||||
|
||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
||||
GITREV="`git show -s --format='%h'`"
|
||||
ARTIFACTS_DIR="$PWD/artifacts"
|
||||
ARTIFACTS_DIR="artifacts"
|
||||
|
||||
mkdir -p "${ARTIFACTS_DIR}/"
|
||||
|
@ -11,7 +11,7 @@ ccache -s
|
||||
mkdir build || true && cd build
|
||||
cmake .. \
|
||||
-DBoost_USE_STATIC_LIBS=ON \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CXX_FLAGS="-march=x86-64-v2" \
|
||||
-DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ \
|
||||
-DCMAKE_C_COMPILER=/usr/lib/ccache/gcc \
|
||||
@ -31,19 +31,6 @@ ccache -s
|
||||
|
||||
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
|
||||
rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester
|
||||
|
||||
|
@ -59,9 +59,4 @@ if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ];
|
||||
cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage"
|
||||
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
|
||||
|
@ -11,6 +11,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modul
|
||||
include(DownloadExternals)
|
||||
include(CMakeDependentOption)
|
||||
include(CTest)
|
||||
include(FetchContent)
|
||||
|
||||
# Set bundled sdl2/qt as dependent options.
|
||||
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
|
||||
@ -98,8 +99,47 @@ if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
|
||||
DESTINATION "${vvl_lib_path}")
|
||||
endif()
|
||||
|
||||
# On Android, fetch and compile libcxx before doing anything else
|
||||
if (ANDROID)
|
||||
set(CMAKE_SKIP_INSTALL_RULES ON)
|
||||
set(LLVM_VERSION "15.0.6")
|
||||
|
||||
# Note: even though libcxx and libcxxabi have separate releases on the project page,
|
||||
# the separated releases cannot be compiled. Only in-tree builds work. Therefore we
|
||||
# must fetch the source release for the entire llvm tree.
|
||||
FetchContent_Declare(llvm
|
||||
URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/llvm-project-${LLVM_VERSION}.src.tar.xz"
|
||||
URL_HASH SHA256=9d53ad04dc60cb7b30e810faf64c5ab8157dadef46c8766f67f286238256ff92
|
||||
TLS_VERIFY TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(llvm)
|
||||
|
||||
# libcxx has support for most of the range library, but it's gated behind a flag:
|
||||
add_compile_definitions(_LIBCPP_ENABLE_EXPERIMENTAL)
|
||||
|
||||
# Disable standard header inclusion
|
||||
set(ANDROID_STL "none")
|
||||
|
||||
# libcxxabi
|
||||
set(LIBCXXABI_INCLUDE_TESTS OFF)
|
||||
set(LIBCXXABI_ENABLE_SHARED FALSE)
|
||||
set(LIBCXXABI_ENABLE_STATIC TRUE)
|
||||
set(LIBCXXABI_LIBCXX_INCLUDES "${LIBCXX_TARGET_INCLUDE_DIRECTORY}" CACHE STRING "" FORCE)
|
||||
add_subdirectory("${llvm_SOURCE_DIR}/libcxxabi" "${llvm_BINARY_DIR}/libcxxabi")
|
||||
link_libraries(cxxabi_static)
|
||||
|
||||
# libcxx
|
||||
set(LIBCXX_ABI_NAMESPACE "__ndk1" CACHE STRING "" FORCE)
|
||||
set(LIBCXX_CXX_ABI "libcxxabi")
|
||||
set(LIBCXX_INCLUDE_TESTS OFF)
|
||||
set(LIBCXX_INCLUDE_BENCHMARKS OFF)
|
||||
set(LIBCXX_INCLUDE_DOCS OFF)
|
||||
set(LIBCXX_ENABLE_SHARED FALSE)
|
||||
set(LIBCXX_ENABLE_STATIC TRUE)
|
||||
set(LIBCXX_ENABLE_ASSERTIONS FALSE)
|
||||
add_subdirectory("${llvm_SOURCE_DIR}/libcxx" "${llvm_BINARY_DIR}/libcxx")
|
||||
set_target_properties(cxx-headers PROPERTIES INTERFACE_COMPILE_OPTIONS "-isystem${CMAKE_BINARY_DIR}/${LIBCXX_INSTALL_INCLUDE_DIR}")
|
||||
link_libraries(cxx_static cxx-headers)
|
||||
endif()
|
||||
|
||||
if (YUZU_USE_BUNDLED_VCPKG)
|
||||
@ -289,7 +329,7 @@ find_package(Boost 1.79.0 REQUIRED context)
|
||||
find_package(enet 1.3 MODULE)
|
||||
find_package(fmt 9 REQUIRED)
|
||||
find_package(inih 52 MODULE COMPONENTS INIReader)
|
||||
find_package(LLVM 17.0.2 MODULE COMPONENTS Demangle)
|
||||
find_package(LLVM 17 MODULE COMPONENTS Demangle)
|
||||
find_package(lz4 REQUIRED)
|
||||
find_package(nlohmann_json 3.8 REQUIRED)
|
||||
find_package(Opus 1.3 MODULE)
|
||||
@ -360,9 +400,6 @@ function(set_yuzu_qt_components)
|
||||
if (ENABLE_QT_TRANSLATION)
|
||||
list(APPEND YUZU_QT_COMPONENTS2 LinguistTools)
|
||||
endif()
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
list(APPEND YUZU_QT_COMPONENTS2 Network)
|
||||
endif()
|
||||
set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE)
|
||||
endfunction(set_yuzu_qt_components)
|
||||
|
||||
|
4
dist/qt_themes/default/style.qss
vendored
4
dist/qt_themes/default/style.qss
vendored
@ -120,10 +120,6 @@ QWidget#connectedControllers {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#closeButtons {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#playersSupported,
|
||||
QWidget#controllersSupported,
|
||||
QWidget#controllerSupported1,
|
||||
|
4
dist/qt_themes/qdarkstyle/style.qss
vendored
4
dist/qt_themes/qdarkstyle/style.qss
vendored
@ -1380,10 +1380,6 @@ QWidget#connectedControllers {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#closeButtons {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#playersSupported,
|
||||
QWidget#controllersSupported,
|
||||
QWidget#controllerSupported1,
|
||||
|
@ -2305,10 +2305,6 @@ QWidget#connectedControllers {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#closeButtons {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
QWidget#playersSupported,
|
||||
QWidget#controllersSupported,
|
||||
QWidget#controllerSupported1,
|
||||
|
2
externals/nx_tzdb/CMakeLists.txt
vendored
2
externals/nx_tzdb/CMakeLists.txt
vendored
@ -27,7 +27,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
|
||||
set(CAN_BUILD_NX_TZDB false)
|
||||
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_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
|
||||
|
2
externals/nx_tzdb/tzdb_to_nx
vendored
2
externals/nx_tzdb/tzdb_to_nx
vendored
Submodule externals/nx_tzdb/tzdb_to_nx updated: 0d17dd066d...212afa2394
@ -27,7 +27,7 @@ android {
|
||||
namespace = "org.yuzu.yuzu_emu"
|
||||
|
||||
compileSdkVersion = "android-34"
|
||||
ndkVersion = "26.1.10909125"
|
||||
ndkVersion = "25.2.9519653"
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
@ -203,23 +203,23 @@ ktlint {
|
||||
}
|
||||
|
||||
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.recyclerview:recyclerview:1.3.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.0")
|
||||
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("com.google.android.material:material:1.9.0")
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
|
||||
implementation("androidx.preference:preference:1.2.0")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
||||
implementation("io.coil-kt:coil:2.2.2")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.window:window:1.2.0-beta03")
|
||||
implementation("org.ini4j:ini4j:0.5.4")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
|
||||
implementation("info.debatty:java-string-similarity:2.0.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
android:appCategory="game"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:banner="@drawable/tv_banner"
|
||||
android:extractNativeLibs="true"
|
||||
android:fullBackupContent="@xml/data_extraction_rules"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules_api_31"
|
||||
android:enableOnBackInvokedCallback="true">
|
||||
|
@ -15,9 +15,13 @@ import androidx.annotation.Keep
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
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.SerializableHelper.serializable
|
||||
|
||||
@ -71,7 +75,7 @@ object NativeLibrary {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
|
||||
} else {
|
||||
FileUtil.openContentUri(path, openmode)
|
||||
openContentUri(appContext, path, openmode)
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,7 +85,7 @@ object NativeLibrary {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.getFileSize(path)
|
||||
} else {
|
||||
FileUtil.getFileSize(path)
|
||||
getFileSize(appContext, path)
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +95,7 @@ object NativeLibrary {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.exists(path)
|
||||
} else {
|
||||
FileUtil.exists(path)
|
||||
exists(appContext, path)
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +105,7 @@ object NativeLibrary {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.isDirectory(path)
|
||||
} else {
|
||||
FileUtil.isDirectory(path)
|
||||
isDirectory(appContext, path)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class YuzuApplication : Application() {
|
||||
application = this
|
||||
documentsTree = DocumentsTree()
|
||||
DirectoryInitialization.start()
|
||||
GpuDriverHelper.initializeDriverParameters()
|
||||
GpuDriverHelper.initializeDriverParameters(applicationContext)
|
||||
NativeLibrary.logDeviceInfo()
|
||||
|
||||
createNotificationChannels()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -39,7 +39,6 @@ import androidx.window.layout.WindowLayoutInfo
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
@ -51,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||
@ -72,7 +70,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
private lateinit var game: Game
|
||||
|
||||
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
||||
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||
|
||||
private var isInFoldableLayout = false
|
||||
|
||||
@ -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() {
|
||||
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
|
||||
emulationState.pause()
|
||||
|
@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
@ -27,6 +28,7 @@ import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import org.yuzu.yuzu_emu.BuildConfig
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
@ -35,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
@ -49,7 +50,6 @@ class HomeSettingsFragment : Fragment() {
|
||||
private lateinit var mainActivity: MainActivity
|
||||
|
||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -107,17 +107,13 @@ class HomeSettingsFragment : Fragment() {
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.gpu_driver_manager,
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_build,
|
||||
{
|
||||
binding.root.findNavController()
|
||||
.navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
|
||||
},
|
||||
R.drawable.ic_exit,
|
||||
{ driverInstaller() },
|
||||
{ GpuDriverHelper.supportsCustomDriverLoading() },
|
||||
R.string.custom_driver_not_supported,
|
||||
R.string.custom_driver_not_supported_description,
|
||||
driverViewModel.selectedDriverMetadata
|
||||
R.string.custom_driver_not_supported_description
|
||||
)
|
||||
)
|
||||
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() {
|
||||
val file = DocumentFile.fromSingleUri(
|
||||
mainActivity,
|
||||
|
@ -10,8 +10,8 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
@ -78,10 +78,6 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
|
||||
requireActivity().supportFragmentManager,
|
||||
MessageDialogFragment.TAG
|
||||
)
|
||||
|
||||
else -> {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
taskViewModel.clear()
|
||||
}
|
||||
@ -119,7 +115,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
|
||||
private const val CANCELLABLE = "Cancellable"
|
||||
|
||||
fun newInstance(
|
||||
activity: FragmentActivity,
|
||||
activity: AppCompatActivity,
|
||||
titleId: Int,
|
||||
cancellable: Boolean = false,
|
||||
task: () -> Any
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -29,10 +29,12 @@ import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -41,6 +43,7 @@ import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
|
||||
@ -340,10 +343,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||
if (FileUtil.copyUriToInternalStorage(
|
||||
applicationContext,
|
||||
result,
|
||||
dstPath,
|
||||
"prod.keys"
|
||||
) != null
|
||||
)
|
||||
) {
|
||||
if (NativeLibrary.reloadKeys()) {
|
||||
Toast.makeText(
|
||||
@ -442,10 +446,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||
if (FileUtil.copyUriToInternalStorage(
|
||||
applicationContext,
|
||||
result,
|
||||
dstPath,
|
||||
"key_retail.bin"
|
||||
) != null
|
||||
)
|
||||
) {
|
||||
if (NativeLibrary.reloadKeys()) {
|
||||
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(
|
||||
ActivityResultContracts.OpenMultipleDocuments()
|
||||
) { documents: List<Uri> ->
|
||||
|
@ -7,6 +7,7 @@ import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
|
||||
class DocumentsTree {
|
||||
@ -21,7 +22,7 @@ class DocumentsTree {
|
||||
|
||||
fun openContentUri(filepath: String, openMode: String?): Int {
|
||||
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 {
|
||||
@ -29,7 +30,7 @@ class DocumentsTree {
|
||||
return if (node == null || node.isDirectory) {
|
||||
0
|
||||
} 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
|
||||
*/
|
||||
private fun structTree(parent: DocumentsNode) {
|
||||
val documents = FileUtil.listFiles(parent.uri!!)
|
||||
val documents = FileUtil.listFiles(YuzuApplication.appContext, parent.uri!!)
|
||||
for (document in documents) {
|
||||
val node = DocumentsNode(document)
|
||||
node.parent = parent
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
@ -10,6 +11,7 @@ import androidx.documentfile.provider.DocumentFile
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.URLDecoder
|
||||
@ -19,8 +21,6 @@ import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
import org.yuzu.yuzu_emu.model.TaskState
|
||||
import java.io.BufferedOutputStream
|
||||
import java.lang.NullPointerException
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
object FileUtil {
|
||||
@ -29,8 +29,6 @@ object FileUtil {
|
||||
const val APPLICATION_OCTET_STREAM = "application/octet-stream"
|
||||
const val TEXT_PLAIN = "text/plain"
|
||||
|
||||
private val context get() = YuzuApplication.appContext
|
||||
|
||||
/**
|
||||
* Create a file from directory with filename.
|
||||
* @param context Application context
|
||||
@ -38,11 +36,11 @@ object FileUtil {
|
||||
* @param filename file display name.
|
||||
* @return boolean
|
||||
*/
|
||||
fun createFile(directory: String?, filename: String): DocumentFile? {
|
||||
fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? {
|
||||
var decodedFilename = filename
|
||||
try {
|
||||
val directoryUri = Uri.parse(directory)
|
||||
val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
|
||||
val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
|
||||
decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
|
||||
var mimeType = APPLICATION_OCTET_STREAM
|
||||
if (decodedFilename.endsWith(".txt")) {
|
||||
@ -58,15 +56,16 @@ object FileUtil {
|
||||
|
||||
/**
|
||||
* Create a directory from directory with filename.
|
||||
* @param context Application context
|
||||
* @param directory parent path for directory.
|
||||
* @param directoryName directory display name.
|
||||
* @return boolean
|
||||
*/
|
||||
fun createDir(directory: String?, directoryName: String?): DocumentFile? {
|
||||
fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? {
|
||||
var decodedDirectoryName = directoryName
|
||||
try {
|
||||
val directoryUri = Uri.parse(directory)
|
||||
val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
|
||||
val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
|
||||
decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
|
||||
val isExist = parent.findFile(decodedDirectoryName)
|
||||
return isExist ?: parent.createDirectory(decodedDirectoryName)
|
||||
@ -78,12 +77,13 @@ object FileUtil {
|
||||
|
||||
/**
|
||||
* Open content uri and return file descriptor to JNI.
|
||||
* @param context Application context
|
||||
* @param path Native content uri path
|
||||
* @param openMode will be one of "r", "r", "rw", "wa", "rwa"
|
||||
* @return file descriptor
|
||||
*/
|
||||
@JvmStatic
|
||||
fun openContentUri(path: String, openMode: String?): Int {
|
||||
fun openContentUri(context: Context, path: String, openMode: String?): Int {
|
||||
try {
|
||||
val uri = Uri.parse(path)
|
||||
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
|
||||
@ -103,10 +103,11 @@ object FileUtil {
|
||||
/**
|
||||
* Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
|
||||
* This function will be faster than DoucmentFile.listFiles
|
||||
* @param context Application context
|
||||
* @param uri Directory uri.
|
||||
* @return CheapDocument lists.
|
||||
*/
|
||||
fun listFiles(uri: Uri): Array<MinimalDocumentFile> {
|
||||
fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> {
|
||||
val resolver = context.contentResolver
|
||||
val columns = arrayOf(
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||
@ -144,7 +145,7 @@ object FileUtil {
|
||||
* @param path Native content uri path
|
||||
* @return bool
|
||||
*/
|
||||
fun exists(path: String?): Boolean {
|
||||
fun exists(context: Context, path: String?): Boolean {
|
||||
var c: Cursor? = null
|
||||
try {
|
||||
val mUri = Uri.parse(path)
|
||||
@ -164,7 +165,7 @@ object FileUtil {
|
||||
* @param path content uri path
|
||||
* @return bool
|
||||
*/
|
||||
fun isDirectory(path: String): Boolean {
|
||||
fun isDirectory(context: Context, path: String): Boolean {
|
||||
val resolver = context.contentResolver
|
||||
val columns = arrayOf(
|
||||
DocumentsContract.Document.COLUMN_MIME_TYPE
|
||||
@ -209,10 +210,10 @@ object FileUtil {
|
||||
return filename
|
||||
}
|
||||
|
||||
fun getFilesName(path: String): Array<String> {
|
||||
fun getFilesName(context: Context, path: String): Array<String> {
|
||||
val uri = Uri.parse(path)
|
||||
val files: MutableList<String> = ArrayList()
|
||||
for (file in listFiles(uri)) {
|
||||
for (file in listFiles(context, uri)) {
|
||||
files.add(file.filename)
|
||||
}
|
||||
return files.toTypedArray()
|
||||
@ -224,7 +225,7 @@ object FileUtil {
|
||||
* @return long file size
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getFileSize(path: String): Long {
|
||||
fun getFileSize(context: Context, path: String): Long {
|
||||
val resolver = context.contentResolver
|
||||
val columns = arrayOf(
|
||||
DocumentsContract.Document.COLUMN_SIZE
|
||||
@ -244,38 +245,44 @@ object FileUtil {
|
||||
return size
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an input stream with a given [Uri] and copies its data to the given path. This will
|
||||
* overwrite any pre-existing files.
|
||||
*
|
||||
* @param sourceUri The [Uri] to copy data from
|
||||
* @param destinationParentPath Destination directory
|
||||
* @param destinationFilename Optionally renames the file once copied
|
||||
*/
|
||||
fun copyUriToInternalStorage(
|
||||
sourceUri: Uri,
|
||||
context: Context,
|
||||
sourceUri: Uri?,
|
||||
destinationParentPath: String,
|
||||
destinationFilename: String = ""
|
||||
): File? =
|
||||
destinationFilename: String
|
||||
): Boolean {
|
||||
var input: InputStream? = null
|
||||
var output: FileOutputStream? = null
|
||||
try {
|
||||
val fileName =
|
||||
if (destinationFilename == "") getFilename(sourceUri) else "/$destinationFilename"
|
||||
val inputStream = context.contentResolver.openInputStream(sourceUri)!!
|
||||
|
||||
val destinationFile = File("$destinationParentPath$fileName")
|
||||
if (destinationFile.exists()) {
|
||||
destinationFile.delete()
|
||||
input = context.contentResolver.openInputStream(sourceUri!!)
|
||||
output = FileOutputStream("$destinationParentPath/$destinationFilename")
|
||||
val buffer = ByteArray(1024)
|
||||
var len: Int
|
||||
while (input!!.read(buffer).also { len = it } != -1) {
|
||||
output.write(buffer, 0, len)
|
||||
}
|
||||
|
||||
destinationFile.outputStream().use { fos ->
|
||||
inputStream.use { it.copyTo(fos) }
|
||||
output.flush()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
Log.error("[FileUtil]: Cannot copy file, error: " + e.message)
|
||||
} finally {
|
||||
if (input != null) {
|
||||
try {
|
||||
input.close()
|
||||
} catch (e: IOException) {
|
||||
Log.error("[FileUtil]: Cannot close input file, error: " + e.message)
|
||||
}
|
||||
}
|
||||
if (output != null) {
|
||||
try {
|
||||
output.close()
|
||||
} catch (e: IOException) {
|
||||
Log.error("[FileUtil]: Cannot close output file, error: " + e.message)
|
||||
}
|
||||
}
|
||||
destinationFile
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
} catch (e: NullPointerException) {
|
||||
null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the given zip file into the given directory.
|
||||
@ -361,12 +368,4 @@ object FileUtil {
|
||||
return fileName.substring(fileName.lastIndexOf(".") + 1)
|
||||
.lowercase()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getStringFromFile(file: File): String =
|
||||
String(file.readBytes(), StandardCharsets.UTF_8)
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getStringFromInputStream(stream: InputStream): String =
|
||||
String(stream.readBytes(), StandardCharsets.UTF_8)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ object GameHelper {
|
||||
// Ensure keys are loaded so that ROM metadata can be decrypted.
|
||||
NativeLibrary.reloadKeys()
|
||||
|
||||
addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
|
||||
addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3)
|
||||
|
||||
// Cache list of games found on disk
|
||||
val serializedGames = mutableSetOf<String>()
|
||||
@ -58,7 +58,7 @@ object GameHelper {
|
||||
if (it.isDirectory) {
|
||||
addGamesRecursive(
|
||||
games,
|
||||
FileUtil.listFiles(it.uri),
|
||||
FileUtil.listFiles(YuzuApplication.appContext, it.uri),
|
||||
depth - 1
|
||||
)
|
||||
} else {
|
||||
|
@ -3,33 +3,64 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipInputStream
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import java.util.zip.ZipException
|
||||
import java.util.zip.ZipFile
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
|
||||
|
||||
object GpuDriverHelper {
|
||||
private const val META_JSON_FILENAME = "meta.json"
|
||||
private const val DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"
|
||||
private var fileRedirectionPath: String? = null
|
||||
var driverInstallationPath: String? = null
|
||||
private var driverInstallationPath: 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 {
|
||||
// Initialize the file redirection directory.
|
||||
fileRedirectionPath = YuzuApplication.appContext
|
||||
.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
|
||||
fileRedirectionPath =
|
||||
context.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
|
||||
|
||||
// Initialize the driver installation directory.
|
||||
driverInstallationPath = YuzuApplication.appContext
|
||||
.filesDir.canonicalPath + "/gpu_driver/"
|
||||
driverInstallationPath = context.filesDir.canonicalPath + "/gpu_driver/"
|
||||
} catch (e: IOException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
@ -38,169 +69,68 @@ object GpuDriverHelper {
|
||||
initializeDirectories()
|
||||
|
||||
// Initialize hook libraries directory.
|
||||
hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/"
|
||||
hookLibPath = context.applicationInfo.nativeLibraryDir + "/"
|
||||
|
||||
// Initialize GPU driver.
|
||||
NativeLibrary.initializeGpuDriver(
|
||||
hookLibPath,
|
||||
driverInstallationPath,
|
||||
customDriverData.libraryName,
|
||||
customDriverLibraryName,
|
||||
fileRedirectionPath
|
||||
)
|
||||
}
|
||||
|
||||
fun getDrivers(): MutableList<Pair<String, GpuDriverMetadata>> {
|
||||
val driverZips = File(driverStoragePath).listFiles()
|
||||
val drivers: MutableList<Pair<String, GpuDriverMetadata>> =
|
||||
driverZips
|
||||
?.mapNotNull {
|
||||
val metadata = getMetadataFromZip(it)
|
||||
metadata.name?.let { _ -> Pair(it.path, metadata) }
|
||||
}
|
||||
?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
|
||||
?.distinct()
|
||||
?.toMutableList() ?: mutableListOf()
|
||||
|
||||
// TODO: Get system driver information
|
||||
drivers.add(0, Pair("", GpuDriverMetadata()))
|
||||
return drivers
|
||||
}
|
||||
|
||||
fun installDefaultDriver() {
|
||||
fun installDefaultDriver(context: Context) {
|
||||
// Removing the installed driver will result in the backend using the default system driver.
|
||||
File(driverInstallationPath!!).deleteRecursively()
|
||||
initializeDriverParameters()
|
||||
val driverInstallationDir = File(driverInstallationPath!!)
|
||||
deleteRecursive(driverInstallationDir)
|
||||
initializeDriverParameters(context)
|
||||
}
|
||||
|
||||
fun copyDriverToInternalStorage(driverUri: Uri): Boolean {
|
||||
// Ensure we have directories.
|
||||
initializeDirectories()
|
||||
|
||||
// Copy the zip file URI to user data
|
||||
val copiedFile =
|
||||
FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
|
||||
|
||||
// Validate driver
|
||||
val metadata = getMetadataFromZip(copiedFile)
|
||||
if (metadata.name == null) {
|
||||
copiedFile.delete()
|
||||
return false
|
||||
}
|
||||
|
||||
if (metadata.minApi > Build.VERSION.SDK_INT) {
|
||||
copiedFile.delete()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
fun installCustomDriver(context: Context, driverPathUri: Uri?) {
|
||||
// Revert to system default in the event the specified driver is bad.
|
||||
installDefaultDriver()
|
||||
installDefaultDriver(context)
|
||||
|
||||
// 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
|
||||
}
|
||||
// Copy the zip file URI into our private storage.
|
||||
copyUriToInternalStorage(
|
||||
context,
|
||||
driverPathUri,
|
||||
driverInstallationPath!!,
|
||||
DRIVER_INTERNAL_FILENAME
|
||||
)
|
||||
|
||||
// Unzip the driver.
|
||||
try {
|
||||
FileUtil.unzipToInternalStorage(
|
||||
BufferedInputStream(copiedFile.inputStream()),
|
||||
File(driverInstallationPath!!)
|
||||
)
|
||||
unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!)
|
||||
} catch (e: SecurityException) {
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the driver parameters.
|
||||
initializeDriverParameters()
|
||||
|
||||
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()
|
||||
initializeDriverParameters(context)
|
||||
}
|
||||
|
||||
external fun supportsCustomDriverLoading(): Boolean
|
||||
|
||||
// Parse the custom driver metadata to retrieve the name.
|
||||
val customDriverData: GpuDriverMetadata
|
||||
get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
|
||||
val customDriverName: String?
|
||||
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.
|
||||
val fileRedirectionDir = File(fileRedirectionPath!!)
|
||||
if (!fileRedirectionDir.exists()) {
|
||||
@ -211,10 +141,14 @@ object GpuDriverHelper {
|
||||
if (!driverInstallationDir.exists()) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -4,116 +4,44 @@
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
class GpuDriverMetadata {
|
||||
/**
|
||||
* 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 description: String? = null
|
||||
var author: String? = null
|
||||
var vendor: String? = null
|
||||
var version: String? = null
|
||||
var driverVersion: String? = null
|
||||
var minApi = 0
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -22,9 +22,6 @@
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_installableFragment"
|
||||
app:destination="@id/installableFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
|
||||
app:destination="@id/driverManagerFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
@ -98,9 +95,5 @@
|
||||
android:id="@+id/installableFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
|
||||
android:label="InstallableFragment" />
|
||||
<fragment
|
||||
android:id="@+id/driverManagerFragment"
|
||||
android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
|
||||
android:label="DriverManagerFragment" />
|
||||
|
||||
</navigation>
|
||||
|
@ -168,7 +168,9 @@
|
||||
<string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string>
|
||||
<string name="select_gpu_driver_install">Installieren</string>
|
||||
<string name="select_gpu_driver_default">Standard</string>
|
||||
<string name="select_gpu_driver_install_success">%s wurde installiert</string>
|
||||
<string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string>
|
||||
<string name="select_gpu_driver_error">Ungültiger Treiber ausgewählt, Standard-Treiber wird verwendet!</string>
|
||||
<string name="system_gpu_driver">System GPU-Treiber</string>
|
||||
<string name="installing_driver">Treiber wird installiert...</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string>
|
||||
<string name="select_gpu_driver_install">Instalar</string>
|
||||
<string name="select_gpu_driver_default">Predeterminado</string>
|
||||
<string name="select_gpu_driver_install_success">Instalado %s</string>
|
||||
<string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string>
|
||||
<string name="select_gpu_driver_error">¡Driver no válido, utilizando el predeterminado del sistema!</string>
|
||||
<string name="system_gpu_driver">Driver GPU del sistema</string>
|
||||
<string name="installing_driver">Instalando driver...</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string>
|
||||
<string name="select_gpu_driver_install">Installer</string>
|
||||
<string name="select_gpu_driver_default">Défaut</string>
|
||||
<string name="select_gpu_driver_install_success">%s Installé</string>
|
||||
<string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string>
|
||||
<string name="select_gpu_driver_error">Pilote non valide sélectionné, utilisation du paramètre par défaut du système !</string>
|
||||
<string name="system_gpu_driver">Pilote du GPU du système</string>
|
||||
<string name="installing_driver">Installation du pilote...</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string>
|
||||
<string name="select_gpu_driver_install">Installa</string>
|
||||
<string name="select_gpu_driver_default">Predefinito</string>
|
||||
<string name="select_gpu_driver_install_success">Installato%s</string>
|
||||
<string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string>
|
||||
<string name="select_gpu_driver_error">Il driver selezionato è invalido, è in utilizzo quello predefinito di sistema!</string>
|
||||
<string name="system_gpu_driver">Driver GPU del sistema</string>
|
||||
<string name="installing_driver">Installando i driver...</string>
|
||||
|
||||
|
@ -170,7 +170,9 @@
|
||||
<string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
|
||||
<string name="select_gpu_driver_install">インストール</string>
|
||||
<string name="select_gpu_driver_default">デフォルト</string>
|
||||
<string name="select_gpu_driver_install_success">%s をインストールしました</string>
|
||||
<string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string>
|
||||
<string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string>
|
||||
<string name="system_gpu_driver">システムのGPUドライバ</string>
|
||||
<string name="installing_driver">インストール中…</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
|
||||
<string name="select_gpu_driver_install">설치</string>
|
||||
<string name="select_gpu_driver_default">기본값</string>
|
||||
<string name="select_gpu_driver_install_success">설치된 %s</string>
|
||||
<string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string>
|
||||
<string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string>
|
||||
<string name="system_gpu_driver">시스템 GPU 드라이버</string>
|
||||
<string name="installing_driver">드라이버 설치 중...</string>
|
||||
|
||||
|
@ -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_install">Installer</string>
|
||||
<string name="select_gpu_driver_default">Standard</string>
|
||||
<string name="select_gpu_driver_install_success">Installert %s</string>
|
||||
<string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string>
|
||||
<string name="select_gpu_driver_error">Ugyldig driver valgt, bruker systemstandard!</string>
|
||||
<string name="system_gpu_driver">Systemets GPU-driver</string>
|
||||
<string name="installing_driver">Installerer driver...</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string>
|
||||
<string name="select_gpu_driver_install">Zainstaluj</string>
|
||||
<string name="select_gpu_driver_default">Domyślne</string>
|
||||
<string name="select_gpu_driver_install_success">Zainstalowano %s</string>
|
||||
<string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string>
|
||||
<string name="select_gpu_driver_error">Wybrano błędny sterownik, powrót do domyślnego. </string>
|
||||
<string name="system_gpu_driver">Systemowy sterownik GPU</string>
|
||||
<string name="installing_driver">Instalowanie sterownika...</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
|
||||
<string name="select_gpu_driver_install">Instalar</string>
|
||||
<string name="select_gpu_driver_default">Padrão</string>
|
||||
<string name="select_gpu_driver_install_success">Instalado%s</string>
|
||||
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
|
||||
<string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
|
||||
<string name="system_gpu_driver">Driver do GPU padrão</string>
|
||||
<string name="installing_driver">A instalar o Driver...</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
|
||||
<string name="select_gpu_driver_install">Instalar</string>
|
||||
<string name="select_gpu_driver_default">Padrão</string>
|
||||
<string name="select_gpu_driver_install_success">Instalado%s</string>
|
||||
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
|
||||
<string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
|
||||
<string name="system_gpu_driver">Driver do GPU padrão</string>
|
||||
<string name="installing_driver">A instalar o Driver...</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
|
||||
<string name="select_gpu_driver_install">Установить</string>
|
||||
<string name="select_gpu_driver_default">По умолчанию</string>
|
||||
<string name="select_gpu_driver_install_success">Установлено %s</string>
|
||||
<string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string>
|
||||
<string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string>
|
||||
<string name="system_gpu_driver">Системный драйвер ГП</string>
|
||||
<string name="installing_driver">Установка драйвера...</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
|
||||
<string name="select_gpu_driver_install">Встановити</string>
|
||||
<string name="select_gpu_driver_default">За замовчуванням</string>
|
||||
<string name="select_gpu_driver_install_success">Встановлено %s</string>
|
||||
<string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string>
|
||||
<string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
|
||||
<string name="system_gpu_driver">Системний драйвер ГП</string>
|
||||
<string name="installing_driver">Встановлення драйвера...</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
|
||||
<string name="select_gpu_driver_install">安装</string>
|
||||
<string name="select_gpu_driver_default">系统默认</string>
|
||||
<string name="select_gpu_driver_install_success">已安装 %s</string>
|
||||
<string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string>
|
||||
<string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string>
|
||||
<string name="system_gpu_driver">系统 GPU 驱动程序</string>
|
||||
<string name="installing_driver">正在安装驱动程序…</string>
|
||||
|
||||
|
@ -171,7 +171,9 @@
|
||||
<string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
|
||||
<string name="select_gpu_driver_install">安裝</string>
|
||||
<string name="select_gpu_driver_default">預設</string>
|
||||
<string name="select_gpu_driver_install_success">已安裝 %s</string>
|
||||
<string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string>
|
||||
<string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string>
|
||||
<string name="system_gpu_driver">系統 GPU 驅動程式</string>
|
||||
<string name="installing_driver">正在安裝驅動程式…</string>
|
||||
|
||||
|
@ -13,8 +13,6 @@
|
||||
<dimen name="menu_width">256dp</dimen>
|
||||
<dimen name="card_width">165dp</dimen>
|
||||
<dimen name="icon_inset">24dp</dimen>
|
||||
<dimen name="spacing_bottom_list_fab">72dp</dimen>
|
||||
<dimen name="spacing_fab">24dp</dimen>
|
||||
|
||||
<dimen name="dialog_margin">20dp</dimen>
|
||||
<dimen name="elevated_app_bar">3dp</dimen>
|
||||
|
@ -72,7 +72,6 @@
|
||||
<string name="invalid_keys_error">Invalid encryption keys</string>
|
||||
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
||||
<string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
|
||||
<string name="gpu_driver_manager">GPU Driver Manager</string>
|
||||
<string name="install_gpu_driver">Install GPU driver</string>
|
||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||
<string name="advanced_settings">Advanced settings</string>
|
||||
@ -235,17 +234,15 @@
|
||||
<string name="export_failed">Export failed</string>
|
||||
<string name="import_failed">Import failed</string>
|
||||
<string name="cancelling">Cancelling</string>
|
||||
<string name="install">Install</string>
|
||||
<string name="delete">Delete</string>
|
||||
|
||||
<!-- GPU driver installation -->
|
||||
<string name="select_gpu_driver">Select GPU driver</string>
|
||||
<string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string>
|
||||
<string name="select_gpu_driver_install">Install</string>
|
||||
<string name="select_gpu_driver_default">Default</string>
|
||||
<string name="select_gpu_driver_install_success">Installed %s</string>
|
||||
<string name="select_gpu_driver_use_default">Using default GPU driver</string>
|
||||
<string name="select_gpu_driver_error">Invalid driver selected</string>
|
||||
<string name="driver_already_installed">Driver already installed</string>
|
||||
<string name="select_gpu_driver_error">Invalid driver selected, using system default!</string>
|
||||
<string name="system_gpu_driver">System GPU driver</string>
|
||||
<string name="installing_driver">Installing driver…</string>
|
||||
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.1.2" apply false
|
||||
id("com.android.library") version "8.1.2" apply false
|
||||
id("com.android.application") version "8.0.2" apply false
|
||||
id("com.android.library") version "8.0.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,6 @@ void AudioRenderer::Wait() {
|
||||
"{}, got {}",
|
||||
Message::RenderResponse, msg);
|
||||
}
|
||||
PostDSPClearCommandBuffer();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
return command_buffers[session_id].remaining_command_count;
|
||||
}
|
||||
|
@ -85,8 +85,6 @@ private:
|
||||
*/
|
||||
void CreateSinkStreams();
|
||||
|
||||
void PostDSPClearCommandBuffer() noexcept;
|
||||
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// The output sink the AudioRenderer will send samples to
|
||||
|
@ -204,10 +204,6 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
|
||||
// paused and we'll desync, so just play silence.
|
||||
if (system.IsPaused() || system.IsShuttingDown()) {
|
||||
if (system.IsShuttingDown()) {
|
||||
{
|
||||
std::scoped_lock lk{release_mutex};
|
||||
queued_buffers.store(0);
|
||||
}
|
||||
release_cv.notify_one();
|
||||
}
|
||||
|
||||
|
@ -39,12 +39,8 @@
|
||||
#define Crash() exit(1)
|
||||
#endif
|
||||
|
||||
#define LTO_NOINLINE __attribute__((noinline))
|
||||
|
||||
#else // _MSC_VER
|
||||
|
||||
#define LTO_NOINLINE
|
||||
|
||||
// Locale Cross-Compatibility
|
||||
#define locale_t _locale_t
|
||||
|
||||
|
@ -211,11 +211,6 @@ struct Elf64_Rela {
|
||||
Elf64_Sxword r_addend; /* Addend */
|
||||
};
|
||||
|
||||
/* RELR relocation table entry */
|
||||
|
||||
using Elf32_Relr = Elf32_Word;
|
||||
using Elf64_Relr = Elf64_Xword;
|
||||
|
||||
/* How to extract and insert information held in the r_info field. */
|
||||
|
||||
static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
|
||||
@ -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 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
|
||||
constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
|
||||
constexpr u32 ElfDtRelrsz = 35; /* Size of RELR relative relocations */
|
||||
constexpr u32 ElfDtRelr = 36; /* Address of RELR relative relocations */
|
||||
constexpr u32 ElfDtRelrent = 37; /* Size of one RELR relative relocation */
|
||||
|
||||
} // namespace ELF
|
||||
} // namespace Common
|
||||
|
@ -15,13 +15,12 @@
|
||||
#include <condition_variable>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename Condvar, typename Lock, typename Pred>
|
||||
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
|
||||
cv.wait(lk, token, std::forward<Pred>(pred));
|
||||
cv.wait(lk, token, std::move(pred));
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
@ -110,7 +109,7 @@ public:
|
||||
|
||||
// Insert the callback.
|
||||
stop_state_callback ret = ++m_next_callback;
|
||||
m_callbacks.emplace(ret, std::move(f));
|
||||
m_callbacks.emplace(ret, move(f));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -163,7 +162,7 @@ private:
|
||||
friend class stop_source;
|
||||
template <typename Callback>
|
||||
friend class stop_callback;
|
||||
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
|
||||
stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {}
|
||||
|
||||
private:
|
||||
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||
@ -199,7 +198,7 @@ public:
|
||||
private:
|
||||
friend class jthread;
|
||||
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
|
||||
: m_stop_state(std::move(stop_state)) {}
|
||||
: m_stop_state(move(stop_state)) {}
|
||||
|
||||
private:
|
||||
shared_ptr<polyfill::stop_state> m_stop_state;
|
||||
@ -219,16 +218,16 @@ public:
|
||||
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
|
||||
: m_stop_state(st.m_stop_state) {
|
||||
if (m_stop_state) {
|
||||
m_callback = m_stop_state->insert_callback(std::move(cb));
|
||||
m_callback = m_stop_state->insert_callback(move(cb));
|
||||
}
|
||||
}
|
||||
template <typename C>
|
||||
requires constructible_from<Callback, C>
|
||||
explicit stop_callback(stop_token&& st,
|
||||
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
|
||||
: m_stop_state(std::move(st.m_stop_state)) {
|
||||
: m_stop_state(move(st.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() {
|
||||
@ -261,7 +260,7 @@ public:
|
||||
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
|
||||
explicit jthread(F&& f, Args&&... args)
|
||||
: m_stop_state(make_shared<polyfill::stop_state>()),
|
||||
m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
|
||||
m_thread(make_thread(move(f), move(args)...)) {}
|
||||
|
||||
~jthread() {
|
||||
if (joinable()) {
|
||||
@ -318,9 +317,9 @@ private:
|
||||
template <typename F, typename... Args>
|
||||
thread make_thread(F&& f, Args&&... args) {
|
||||
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
|
||||
return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
|
||||
return thread(move(f), get_stop_token(), move(args)...);
|
||||
} else {
|
||||
return thread(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
return thread(move(f), move(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,6 @@ SWITCHABLE(CpuAccuracy, true);
|
||||
SWITCHABLE(FullscreenMode, true);
|
||||
SWITCHABLE(GpuAccuracy, true);
|
||||
SWITCHABLE(Language, true);
|
||||
SWITCHABLE(MemoryLayout, true);
|
||||
SWITCHABLE(NvdecEmulation, false);
|
||||
SWITCHABLE(Region, true);
|
||||
SWITCHABLE(RendererBackend, true);
|
||||
@ -62,10 +61,6 @@ SWITCHABLE(u32, false);
|
||||
SWITCHABLE(u8, false);
|
||||
SWITCHABLE(u8, true);
|
||||
|
||||
// Used in UISettings
|
||||
// TODO see if we can move this to uisettings.cpp
|
||||
SWITCHABLE(ConfirmStop, true);
|
||||
|
||||
#undef SETTING
|
||||
#undef SWITCHABLE
|
||||
#endif
|
||||
|
@ -67,7 +67,6 @@ SWITCHABLE(CpuAccuracy, true);
|
||||
SWITCHABLE(FullscreenMode, true);
|
||||
SWITCHABLE(GpuAccuracy, true);
|
||||
SWITCHABLE(Language, true);
|
||||
SWITCHABLE(MemoryLayout, true);
|
||||
SWITCHABLE(NvdecEmulation, false);
|
||||
SWITCHABLE(Region, true);
|
||||
SWITCHABLE(RendererBackend, true);
|
||||
@ -84,10 +83,6 @@ SWITCHABLE(u32, false);
|
||||
SWITCHABLE(u8, false);
|
||||
SWITCHABLE(u8, true);
|
||||
|
||||
// Used in UISettings
|
||||
// TODO see if we can move this to uisettings.h
|
||||
SWITCHABLE(ConfirmStop, true);
|
||||
|
||||
#undef SETTING
|
||||
#undef SWITCHABLE
|
||||
#endif
|
||||
|
@ -133,8 +133,6 @@ ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
|
||||
|
||||
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
|
||||
|
||||
ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
|
||||
|
||||
ENUM(FullscreenMode, Borderless, Exclusive);
|
||||
|
||||
ENUM(NvdecEmulation, Off, Cpu, Gpu);
|
||||
|
@ -3405,11 +3405,6 @@ Result KPageTable::LockMemoryAndOpen(KPageGroup* out_pg, KPhysicalAddress* out_K
|
||||
new_attr, KMemoryBlockDisableMergeAttribute::Locked,
|
||||
KMemoryBlockDisableMergeAttribute::None);
|
||||
|
||||
// If we have an output page group, open.
|
||||
if (out_pg) {
|
||||
out_pg->Open();
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
|
@ -373,7 +373,7 @@ struct KernelCore::Impl {
|
||||
static inline thread_local u8 host_thread_id = UINT8_MAX;
|
||||
|
||||
/// Sets the host thread ID for the caller.
|
||||
LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) {
|
||||
u32 SetHostThreadId(std::size_t core_id) {
|
||||
// This should only be called during core init.
|
||||
ASSERT(host_thread_id == UINT8_MAX);
|
||||
|
||||
@ -384,13 +384,13 @@ struct KernelCore::Impl {
|
||||
}
|
||||
|
||||
/// Gets the host thread ID for the caller
|
||||
LTO_NOINLINE u32 GetHostThreadId() const {
|
||||
u32 GetHostThreadId() const {
|
||||
return host_thread_id;
|
||||
}
|
||||
|
||||
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
|
||||
LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) {
|
||||
const auto initialize{[](KThread* thread) LTO_NOINLINE {
|
||||
KThread* GetHostDummyThread(KThread* existing_thread) {
|
||||
const auto initialize{[](KThread* thread) {
|
||||
ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
|
||||
return thread;
|
||||
}};
|
||||
@ -424,11 +424,11 @@ struct KernelCore::Impl {
|
||||
|
||||
static inline thread_local bool is_phantom_mode_for_singlecore{false};
|
||||
|
||||
LTO_NOINLINE bool IsPhantomModeForSingleCore() const {
|
||||
bool IsPhantomModeForSingleCore() const {
|
||||
return is_phantom_mode_for_singlecore;
|
||||
}
|
||||
|
||||
LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) {
|
||||
void SetIsPhantomModeForSingleCore(bool value) {
|
||||
ASSERT(!is_multicore);
|
||||
is_phantom_mode_for_singlecore = value;
|
||||
}
|
||||
@ -439,14 +439,14 @@ struct KernelCore::Impl {
|
||||
|
||||
static inline thread_local KThread* current_thread{nullptr};
|
||||
|
||||
LTO_NOINLINE KThread* GetCurrentEmuThread() {
|
||||
KThread* GetCurrentEmuThread() {
|
||||
if (!current_thread) {
|
||||
current_thread = GetHostDummyThread(nullptr);
|
||||
}
|
||||
return current_thread;
|
||||
}
|
||||
|
||||
LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) {
|
||||
void SetCurrentEmuThread(KThread* thread) {
|
||||
current_thread = thread;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ namespace Service::Capture {
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
auto album_manager = std::make_shared<AlbumManager>(system);
|
||||
auto album_manager = std::make_shared<AlbumManager>();
|
||||
|
||||
server_manager->RegisterNamedService(
|
||||
"caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
|
||||
|
@ -128,9 +128,9 @@ void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
|
||||
ctx.WriteBuffer(entries);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(result);
|
||||
rb.Push<u64>(entries.size());
|
||||
rb.Push(entries.size());
|
||||
}
|
||||
|
||||
void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
|
||||
|
@ -8,15 +8,12 @@
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/caps/caps_manager.h"
|
||||
#include "core/hle/service/caps/caps_result.h"
|
||||
#include "core/hle/service/time/time_manager.h"
|
||||
#include "core/hle/service/time/time_zone_content_manager.h"
|
||||
|
||||
namespace Service::Capture {
|
||||
|
||||
AlbumManager::AlbumManager(Core::System& system_) : system{system_} {}
|
||||
AlbumManager::AlbumManager() {}
|
||||
|
||||
AlbumManager::~AlbumManager() = default;
|
||||
|
||||
@ -86,34 +83,6 @@ Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, Albu
|
||||
}
|
||||
|
||||
Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
|
||||
ContentType contex_type, s64 start_posix_time,
|
||||
s64 end_posix_time, u64 aruid) const {
|
||||
if (!is_mounted) {
|
||||
return ResultIsNotMounted;
|
||||
}
|
||||
|
||||
std::vector<ApplicationAlbumEntry> album_entries;
|
||||
const auto start_date = ConvertToAlbumDateTime(start_posix_time);
|
||||
const auto end_date = ConvertToAlbumDateTime(end_posix_time);
|
||||
const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid);
|
||||
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto& album_entry : album_entries) {
|
||||
ApplicationAlbumFileEntry entry{
|
||||
.entry = album_entry,
|
||||
.datetime = album_entry.datetime,
|
||||
.unknown = {},
|
||||
};
|
||||
out_entries.push_back(entry);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
|
||||
ContentType contex_type, AlbumFileDateTime start_date,
|
||||
AlbumFileDateTime end_date, u64 aruid) const {
|
||||
if (!is_mounted) {
|
||||
@ -124,25 +93,31 @@ Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_en
|
||||
if (file_id.type != contex_type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_id.date > start_date) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_id.date < end_date) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (out_entries.size() >= SdAlbumFileLimit) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto entry_size = Common::FS::GetSize(path);
|
||||
ApplicationAlbumEntry entry{
|
||||
.size = entry_size,
|
||||
.hash{},
|
||||
.datetime = file_id.date,
|
||||
.storage = file_id.storage,
|
||||
.content = contex_type,
|
||||
.unknown = 1,
|
||||
};
|
||||
ApplicationAlbumFileEntry entry{.entry =
|
||||
{
|
||||
.size = entry_size,
|
||||
.hash{},
|
||||
.datetime = file_id.date,
|
||||
.storage = file_id.storage,
|
||||
.content = contex_type,
|
||||
.unknown = 1,
|
||||
},
|
||||
.datetime = file_id.date,
|
||||
.unknown = {}};
|
||||
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)),
|
||||
.date =
|
||||
{
|
||||
.year = static_cast<s16>(std::stoi(year)),
|
||||
.month = static_cast<s8>(std::stoi(month)),
|
||||
.day = static_cast<s8>(std::stoi(day)),
|
||||
.hour = static_cast<s8>(std::stoi(hour)),
|
||||
.minute = static_cast<s8>(std::stoi(minute)),
|
||||
.second = static_cast<s8>(std::stoi(second)),
|
||||
.year = static_cast<u16>(std::stoi(year)),
|
||||
.month = static_cast<u8>(std::stoi(month)),
|
||||
.day = static_cast<u8>(std::stoi(day)),
|
||||
.hour = static_cast<u8>(std::stoi(hour)),
|
||||
.minute = static_cast<u8>(std::stoi(minute)),
|
||||
.second = static_cast<u8>(std::stoi(second)),
|
||||
.unique_id = 0,
|
||||
},
|
||||
.storage = AlbumStorage::Sd,
|
||||
@ -364,23 +339,4 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
|
||||
Time::TimeZone::CalendarInfo calendar_date{};
|
||||
const auto& time_zone_manager =
|
||||
system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
|
||||
|
||||
time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
|
||||
|
||||
return {
|
||||
.year = calendar_date.time.year,
|
||||
.month = calendar_date.time.month,
|
||||
.day = calendar_date.time.day,
|
||||
.hour = calendar_date.time.hour,
|
||||
.minute = calendar_date.time.minute,
|
||||
.second = calendar_date.time.second,
|
||||
.unique_id = 0,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Service::Capture
|
||||
|
@ -37,7 +37,7 @@ namespace Service::Capture {
|
||||
|
||||
class AlbumManager {
|
||||
public:
|
||||
explicit AlbumManager(Core::System& system_);
|
||||
explicit AlbumManager();
|
||||
~AlbumManager();
|
||||
|
||||
Result DeleteAlbumFile(const AlbumFileId& file_id);
|
||||
@ -45,9 +45,6 @@ public:
|
||||
Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
|
||||
u8 flags) const;
|
||||
Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
|
||||
ContentType contex_type, s64 start_posix_time, s64 end_posix_time,
|
||||
u64 aruid) const;
|
||||
Result GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
|
||||
ContentType contex_type, AlbumFileDateTime start_date,
|
||||
AlbumFileDateTime end_date, u64 aruid) const;
|
||||
Result GetAutoSavingStorage(bool& out_is_autosaving) const;
|
||||
@ -68,12 +65,8 @@ private:
|
||||
Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
|
||||
int height, ScreenShotDecoderFlag flag) const;
|
||||
|
||||
AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
|
||||
|
||||
bool is_mounted{};
|
||||
std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace Service::Capture
|
||||
|
@ -41,13 +41,13 @@ enum class ScreenShotDecoderFlag : u64 {
|
||||
|
||||
// This is nn::capsrv::AlbumFileDateTime
|
||||
struct AlbumFileDateTime {
|
||||
s16 year{};
|
||||
s8 month{};
|
||||
s8 day{};
|
||||
s8 hour{};
|
||||
s8 minute{};
|
||||
s8 second{};
|
||||
s8 unique_id{};
|
||||
u16 year{};
|
||||
u8 month{};
|
||||
u8 day{};
|
||||
u8 hour{};
|
||||
u8 minute{};
|
||||
u8 second{};
|
||||
u8 unique_id{};
|
||||
|
||||
friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
|
||||
friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
|
||||
|
@ -50,35 +50,22 @@ void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
|
||||
|
||||
void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
struct Parameters {
|
||||
ContentType content_type;
|
||||
INSERT_PADDING_BYTES(7);
|
||||
s64 start_posix_time;
|
||||
s64 end_posix_time;
|
||||
u64 applet_resource_user_id;
|
||||
};
|
||||
static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
|
||||
|
||||
const auto parameters{rp.PopRaw<Parameters>()};
|
||||
const auto pid{rp.Pop<s32>()};
|
||||
const auto content_type{rp.PopEnum<ContentType>()};
|
||||
const auto start_posix_time{rp.Pop<s64>()};
|
||||
const auto end_posix_time{rp.Pop<s64>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_Capture,
|
||||
"(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, "
|
||||
"applet_resource_user_id={}",
|
||||
parameters.content_type, parameters.start_posix_time, parameters.end_posix_time,
|
||||
parameters.applet_resource_user_id);
|
||||
"(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
|
||||
"end_posix_time={}, applet_resource_user_id={}",
|
||||
pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id);
|
||||
|
||||
Result result = ResultSuccess;
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
result = manager->IsAlbumMounted(AlbumStorage::Sd);
|
||||
}
|
||||
// TODO: Translate posix to DateTime
|
||||
|
||||
std::vector<ApplicationAlbumFileEntry> entries;
|
||||
if (result.IsSuccess()) {
|
||||
result = manager->GetAlbumFileList(entries, parameters.content_type,
|
||||
parameters.start_posix_time, parameters.end_posix_time,
|
||||
parameters.applet_resource_user_id);
|
||||
}
|
||||
const Result result =
|
||||
manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id);
|
||||
|
||||
if (!entries.empty()) {
|
||||
ctx.WriteBuffer(entries);
|
||||
@ -91,38 +78,19 @@ void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestCo
|
||||
|
||||
void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
struct Parameters {
|
||||
ContentType content_type;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
AlbumFileDateTime start_date_time;
|
||||
AlbumFileDateTime end_date_time;
|
||||
INSERT_PADDING_BYTES(6);
|
||||
u64 applet_resource_user_id;
|
||||
};
|
||||
static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
|
||||
|
||||
const auto parameters{rp.PopRaw<Parameters>()};
|
||||
const auto pid{rp.Pop<s32>()};
|
||||
const auto content_type{rp.PopEnum<ContentType>()};
|
||||
const auto start_date_time{rp.PopRaw<AlbumFileDateTime>()};
|
||||
const auto end_date_time{rp.PopRaw<AlbumFileDateTime>()};
|
||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
||||
|
||||
LOG_WARNING(Service_Capture,
|
||||
"(STUBBED) called. content_type={}, start_date={}/{}/{}, "
|
||||
"end_date={}/{}/{}, applet_resource_user_id={}",
|
||||
parameters.content_type, parameters.start_date_time.year,
|
||||
parameters.start_date_time.month, parameters.start_date_time.day,
|
||||
parameters.end_date_time.year, parameters.end_date_time.month,
|
||||
parameters.end_date_time.day, parameters.applet_resource_user_id);
|
||||
"(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid,
|
||||
content_type, applet_resource_user_id);
|
||||
|
||||
Result result = ResultSuccess;
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
result = manager->IsAlbumMounted(AlbumStorage::Sd);
|
||||
}
|
||||
|
||||
std::vector<ApplicationAlbumEntry> entries;
|
||||
if (result.IsSuccess()) {
|
||||
result =
|
||||
manager->GetAlbumFileList(entries, parameters.content_type, parameters.start_date_time,
|
||||
parameters.end_date_time, parameters.applet_resource_user_id);
|
||||
}
|
||||
std::vector<ApplicationAlbumFileEntry> entries;
|
||||
const Result result = manager->GetAlbumFileList(entries, content_type, start_date_time,
|
||||
end_date_time, applet_resource_user_id);
|
||||
|
||||
if (!entries.empty()) {
|
||||
ctx.WriteBuffer(entries);
|
||||
|
@ -156,8 +156,6 @@ public:
|
||||
|
||||
bool LoadNRO(std::span<const u8> data) {
|
||||
local_memory.clear();
|
||||
|
||||
relocbase = local_memory.size();
|
||||
local_memory.insert(local_memory.end(), data.begin(), data.end());
|
||||
|
||||
if (FixupRelocations()) {
|
||||
@ -183,8 +181,8 @@ public:
|
||||
// https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
|
||||
// https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
|
||||
VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
|
||||
VAddr rela_dyn = 0, relr_dyn = 0;
|
||||
size_t num_rela = 0, num_relr = 0;
|
||||
VAddr rela_dyn = 0;
|
||||
size_t num_rela = 0;
|
||||
while (true) {
|
||||
const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
|
||||
dynamic_offset += sizeof(Elf64_Dyn);
|
||||
@ -198,12 +196,6 @@ public:
|
||||
if (dyn.d_tag == ElfDtRelasz) {
|
||||
num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
|
||||
}
|
||||
if (dyn.d_tag == ElfDtRelr) {
|
||||
relr_dyn = dyn.d_un.d_ptr;
|
||||
}
|
||||
if (dyn.d_tag == ElfDtRelrsz) {
|
||||
num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_rela; i++) {
|
||||
@ -215,29 +207,6 @@ public:
|
||||
callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
|
||||
}
|
||||
|
||||
VAddr relr_where = 0;
|
||||
for (size_t i = 0; i < num_relr; i++) {
|
||||
const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))};
|
||||
const auto incr{[&](VAddr where) {
|
||||
callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
|
||||
}};
|
||||
|
||||
if ((relr & 1) == 0) {
|
||||
// where pointer
|
||||
relr_where = relocbase + relr;
|
||||
incr(relr_where);
|
||||
relr_where += sizeof(Elf64_Addr);
|
||||
} else {
|
||||
// bitmap
|
||||
for (int bit = 1; bit < 64; bit++) {
|
||||
if ((relr & (1ULL << bit)) != 0) {
|
||||
incr(relr_where + i * sizeof(Elf64_Addr));
|
||||
}
|
||||
}
|
||||
relr_where += 63 * sizeof(Elf64_Addr);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -344,7 +313,6 @@ public:
|
||||
Core::Memory::Memory& memory;
|
||||
VAddr top_of_stack;
|
||||
VAddr heap_pointer;
|
||||
VAddr relocbase;
|
||||
};
|
||||
|
||||
void DynarmicCallbacks64::CallSVC(u32 swi) {
|
||||
|
@ -544,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
||||
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 largest_copy = 0;
|
||||
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 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) {
|
||||
runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
|
||||
++binding_index;
|
||||
@ -936,11 +931,6 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
|
||||
const u32 size = binding.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 PixelFormat format = binding.format;
|
||||
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
|
||||
@ -972,8 +962,6 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||
const u32 size = binding.size;
|
||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||
|
||||
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||
|
||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||
host_bindings.buffers.push_back(&buffer);
|
||||
host_bindings.offsets.push_back(offset);
|
||||
@ -1023,11 +1011,6 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
|
||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||
const bool is_written =
|
||||
((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) {
|
||||
runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
|
||||
++binding_index;
|
||||
@ -1045,12 +1028,6 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
|
||||
const u32 size = binding.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 PixelFormat format = binding.format;
|
||||
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
|
||||
@ -1224,11 +1201,16 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
|
||||
|
||||
template <class P>
|
||||
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) {
|
||||
// Resolve buffer
|
||||
Binding& binding = channel_state->storage_buffers[stage][index];
|
||||
const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
||||
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) {
|
||||
Binding& binding = channel_state->texture_buffers[stage][index];
|
||||
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,
|
||||
.buffer_id = buffer_id,
|
||||
};
|
||||
MarkWrittenBuffer(buffer_id, *cpu_addr, size);
|
||||
}
|
||||
|
||||
template <class P>
|
||||
@ -1292,6 +1279,10 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
|
||||
// Resolve buffer
|
||||
Binding& binding = channel_state->compute_storage_buffers[index];
|
||||
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) {
|
||||
Binding& binding = channel_state->compute_texture_buffers[index];
|
||||
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>
|
||||
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);
|
||||
|
||||
const IntervalType base_interval{cpu_addr, cpu_addr + size};
|
||||
|
@ -48,14 +48,8 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
|
||||
SetInlineIndexBuffer(regs.inline_index_4x8.index3);
|
||||
break;
|
||||
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): {
|
||||
DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
|
||||
regs.vertex_array_instance_subsequent.start.Value(),
|
||||
regs.vertex_array_instance_subsequent.count.Value(), true);
|
||||
LOG_WARNING(HW_GPU, "(STUBBED) called");
|
||||
break;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
u32 base_index, u32 base_instance, u32 num_instances) {
|
||||
const auto& regs{maxwell3d->regs};
|
||||
|
@ -66,8 +66,6 @@ public:
|
||||
|
||||
void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
|
||||
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,
|
||||
u32 base_instance, u32 num_instances);
|
||||
|
@ -1048,10 +1048,6 @@ void Image::Scale(bool up_scale) {
|
||||
}
|
||||
|
||||
bool Image::ScaleUp(bool ignore) {
|
||||
const auto& resolution = runtime->resolution;
|
||||
if (!resolution.active) {
|
||||
return false;
|
||||
}
|
||||
if (True(flags & ImageFlagBits::Rescaled)) {
|
||||
return false;
|
||||
}
|
||||
@ -1064,6 +1060,9 @@ bool Image::ScaleUp(bool ignore) {
|
||||
return false;
|
||||
}
|
||||
flags |= ImageFlagBits::Rescaled;
|
||||
if (!runtime->resolution.active) {
|
||||
return false;
|
||||
}
|
||||
has_scaled = true;
|
||||
if (ignore) {
|
||||
current_texture = upscaled_backup.handle;
|
||||
@ -1074,14 +1073,13 @@ bool Image::ScaleUp(bool ignore) {
|
||||
}
|
||||
|
||||
bool Image::ScaleDown(bool ignore) {
|
||||
const auto& resolution = runtime->resolution;
|
||||
if (!resolution.active) {
|
||||
return false;
|
||||
}
|
||||
if (False(flags & ImageFlagBits::Rescaled)) {
|
||||
return false;
|
||||
}
|
||||
flags &= ~ImageFlagBits::Rescaled;
|
||||
if (!runtime->resolution.active) {
|
||||
return false;
|
||||
}
|
||||
if (ignore) {
|
||||
current_texture = texture.handle;
|
||||
return true;
|
||||
|
@ -118,8 +118,6 @@ public:
|
||||
|
||||
void InsertUploadMemoryBarrier();
|
||||
|
||||
void TransitionImageLayout(Image& image) {}
|
||||
|
||||
FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
|
||||
|
||||
bool HasNativeBgr() const noexcept {
|
||||
|
@ -1530,15 +1530,15 @@ bool Image::IsRescaled() const noexcept {
|
||||
}
|
||||
|
||||
bool Image::ScaleUp(bool ignore) {
|
||||
const auto& resolution = runtime->resolution;
|
||||
if (!resolution.active) {
|
||||
return false;
|
||||
}
|
||||
if (True(flags & ImageFlagBits::Rescaled)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(info.type != ImageType::Linear);
|
||||
flags |= ImageFlagBits::Rescaled;
|
||||
const auto& resolution = runtime->resolution;
|
||||
if (!resolution.active) {
|
||||
return false;
|
||||
}
|
||||
has_scaled = true;
|
||||
if (!scaled_image) {
|
||||
const bool is_2d = info.type == ImageType::e2D;
|
||||
@ -1567,15 +1567,15 @@ bool Image::ScaleUp(bool ignore) {
|
||||
}
|
||||
|
||||
bool Image::ScaleDown(bool ignore) {
|
||||
const auto& resolution = runtime->resolution;
|
||||
if (!resolution.active) {
|
||||
return false;
|
||||
}
|
||||
if (False(flags & ImageFlagBits::Rescaled)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT(info.type != ImageType::Linear);
|
||||
flags &= ~ImageFlagBits::Rescaled;
|
||||
const auto& resolution = runtime->resolution;
|
||||
if (!resolution.active) {
|
||||
return false;
|
||||
}
|
||||
current_image = *original_image;
|
||||
if (ignore) {
|
||||
return true;
|
||||
@ -2013,32 +2013,4 @@ void TextureCacheRuntime::AccelerateImageUpload(
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
void TextureCacheRuntime::TransitionImageLayout(Image& image) {
|
||||
if (!image.ExchangeInitialization()) {
|
||||
VkImageMemoryBarrier barrier{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.pNext = nullptr,
|
||||
.srcAccessMask = VK_ACCESS_NONE,
|
||||
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image.Handle(),
|
||||
.subresourceRange{
|
||||
.aspectMask = image.AspectMask(),
|
||||
.baseMipLevel = 0,
|
||||
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||
.baseArrayLayer = 0,
|
||||
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||
},
|
||||
};
|
||||
scheduler.RequestOutsideRenderPassOperationContext();
|
||||
scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) {
|
||||
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
@ -92,8 +92,6 @@ public:
|
||||
|
||||
void InsertUploadMemoryBarrier() {}
|
||||
|
||||
void TransitionImageLayout(Image& image);
|
||||
|
||||
bool HasBrokenTextureViewFormats() const noexcept {
|
||||
// No known Vulkan driver has broken image views
|
||||
return false;
|
||||
|
@ -1016,7 +1016,6 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
|
||||
|
||||
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
|
||||
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
|
||||
runtime.TransitionImageLayout(image);
|
||||
return;
|
||||
}
|
||||
if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {
|
||||
|
@ -885,7 +885,7 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
|
||||
};
|
||||
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
|
||||
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);
|
||||
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 num_tiles = AdjustTileSize(level_size, tile_size);
|
||||
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{
|
||||
.num_tiles = num_tiles,
|
||||
.block = block,
|
||||
|
@ -66,10 +66,9 @@ struct Range {
|
||||
switch (usage) {
|
||||
case MemoryUsage::Upload:
|
||||
case MemoryUsage::Stream:
|
||||
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
||||
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
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:
|
||||
return {};
|
||||
}
|
||||
@ -253,7 +252,8 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
|
||||
|
||||
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
|
||||
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),
|
||||
.requiredFlags = 0,
|
||||
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
|
||||
|
@ -384,7 +384,7 @@ if (USE_DISCORD_PRESENCE)
|
||||
discord_impl.cpp
|
||||
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)
|
||||
endif()
|
||||
|
||||
|
@ -23,7 +23,6 @@
|
||||
#include "yuzu/configuration/configure_vibration.h"
|
||||
#include "yuzu/configuration/input_profiles.h"
|
||||
#include "yuzu/main.h"
|
||||
#include "yuzu/util/controller_navigation.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -133,8 +132,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
||||
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
|
||||
};
|
||||
|
||||
ui->labelError->setVisible(false);
|
||||
|
||||
// Setup/load everything prior to setting up connections.
|
||||
// This avoids unintentionally changing the states of elements while loading them in.
|
||||
SetSupportedControllers();
|
||||
@ -146,8 +143,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
||||
|
||||
LoadConfiguration();
|
||||
|
||||
controller_navigation = new ControllerNavigation(system.HIDCore(), this);
|
||||
|
||||
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
|
||||
SetExplainText(i);
|
||||
UpdateControllerIcon(i);
|
||||
@ -156,8 +151,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
||||
|
||||
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
|
||||
if (checked) {
|
||||
// Hide eventual error message about number of controllers
|
||||
ui->labelError->setVisible(false);
|
||||
for (std::size_t index = 0; index <= i; ++index) {
|
||||
connected_controller_checkboxes[index]->setChecked(checked);
|
||||
}
|
||||
@ -206,12 +199,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
|
||||
&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.
|
||||
// If all the parameters are met AND only allows a single player,
|
||||
// stop the constructor here as we do not need to continue.
|
||||
@ -230,7 +217,6 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
||||
}
|
||||
|
||||
QtControllerSelectorDialog::~QtControllerSelectorDialog() {
|
||||
controller_navigation->UnloadController();
|
||||
system.HIDCore().DisableAllControllerConfiguration();
|
||||
}
|
||||
|
||||
@ -305,31 +291,6 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
|
||||
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() {
|
||||
// Here, we check and validate the current configuration against all applicable parameters.
|
||||
const auto num_connected_players = static_cast<int>(
|
||||
|
@ -34,8 +34,6 @@ class HIDCore;
|
||||
enum class NpadStyleIndex : u8;
|
||||
} // namespace Core::HID
|
||||
|
||||
class ControllerNavigation;
|
||||
|
||||
class QtControllerSelectorDialog final : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
@ -48,8 +46,6 @@ public:
|
||||
|
||||
int exec() override;
|
||||
|
||||
void keyPressEvent(QKeyEvent* evt) override;
|
||||
|
||||
private:
|
||||
// Applies the current configuration.
|
||||
void ApplyConfiguration();
|
||||
@ -114,8 +110,6 @@ private:
|
||||
|
||||
Core::System& system;
|
||||
|
||||
ControllerNavigation* controller_navigation = nullptr;
|
||||
|
||||
// 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.
|
||||
bool parameters_met{false};
|
||||
|
@ -2624,53 +2624,13 @@
|
||||
</spacer>
|
||||
</item>
|
||||
<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">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -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", "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", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), 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", "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(""), 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 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}},
|
||||
|
@ -115,9 +115,17 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
|
||||
for (std::size_t i = 0; i < player_tabs.size(); ++i) {
|
||||
player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[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
|
||||
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,
|
||||
&ConfigureInput::UpdateAllInputDevices);
|
||||
@ -175,30 +183,6 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
|
||||
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 {
|
||||
return {
|
||||
ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
|
||||
|
@ -56,7 +56,6 @@ private:
|
||||
void UpdateDockedState(bool is_handheld);
|
||||
void UpdateAllInputDevices();
|
||||
void UpdateAllInputProfiles(std::size_t player_index);
|
||||
void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked);
|
||||
|
||||
/// Load configuration settings.
|
||||
void LoadConfiguration();
|
||||
|
@ -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, pause_when_in_background, "Pause emulation when in background", "");
|
||||
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, controller_applet_disabled, "Disable controller applet", "");
|
||||
|
||||
@ -384,13 +383,6 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
|
||||
translations->insert(
|
||||
{Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
|
||||
{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 CTX_PAIR
|
||||
|
@ -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_PLAY_TIME, !UISettings::values.show_play_time);
|
||||
|
||||
// Before deleting rows, cancel the worker so that it is not using them
|
||||
emit ShouldCancelWorker();
|
||||
|
||||
// Delete any rows that might already exist if we're repopulating
|
||||
item_model->removeRows(0, item_model->rowCount());
|
||||
search_field->clear();
|
||||
|
||||
emit ShouldCancelWorker();
|
||||
|
||||
GameListWorker* worker =
|
||||
new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
|
||||
|
||||
|
@ -293,7 +293,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
|
||||
void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan,
|
||||
GameListDir* parent_dir) {
|
||||
const auto callback = [this, target, parent_dir](const std::filesystem::path& path) -> bool {
|
||||
if (stop_requested) {
|
||||
if (stop_processing) {
|
||||
// Breaks the callback loop.
|
||||
return false;
|
||||
}
|
||||
@ -399,6 +399,7 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
|
||||
}
|
||||
|
||||
void GameListWorker::run() {
|
||||
stop_processing = false;
|
||||
provider->ClearAllEntries();
|
||||
|
||||
for (UISettings::GameDir& game_dir : game_dirs) {
|
||||
@ -426,11 +427,9 @@ void GameListWorker::run() {
|
||||
}
|
||||
|
||||
emit Finished(watch_list);
|
||||
processing_completed.Set();
|
||||
}
|
||||
|
||||
void GameListWorker::Cancel() {
|
||||
this->disconnect();
|
||||
stop_requested.store(true);
|
||||
processing_completed.Wait();
|
||||
stop_processing = true;
|
||||
}
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include <QRunnable>
|
||||
#include <QString>
|
||||
|
||||
#include "common/thread.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/play_time_manager.h"
|
||||
|
||||
@ -83,9 +82,7 @@ private:
|
||||
const PlayTime::PlayTimeManager& play_time_manager;
|
||||
|
||||
QStringList watch_list;
|
||||
|
||||
Common::Event processing_completed;
|
||||
std::atomic_bool stop_requested = false;
|
||||
std::atomic_bool stop_processing;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
@ -67,7 +67,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#define QT_NO_OPENGL
|
||||
#include <QClipboard>
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
@ -77,7 +76,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
|
||||
#include <QPushButton>
|
||||
#include <QScreen>
|
||||
#include <QShortcut>
|
||||
#include <QStandardPaths>
|
||||
#include <QStatusBar>
|
||||
#include <QString>
|
||||
#include <QSysInfo>
|
||||
@ -211,7 +209,7 @@ void GMainWindow::ShowTelemetryCallout() {
|
||||
tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
|
||||
"data is collected</a> to help improve yuzu. "
|
||||
"<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;
|
||||
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,
|
||||
QMessageBox::No)) {
|
||||
if (QMessageBox::question(this, tr("Remove Entry"), entry_question,
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::No) != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2520,8 +2519,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
|
||||
}
|
||||
}();
|
||||
|
||||
if (!GMainWindow::question(this, tr("Remove File"), question,
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
|
||||
if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::No) != QMessageBox::Yes) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2870,50 +2869,44 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
|
||||
#endif // __linux__
|
||||
|
||||
std::filesystem::path target_directory{};
|
||||
// Determine target directory for shortcut
|
||||
#if defined(WIN32)
|
||||
const char* home = std::getenv("USERPROFILE");
|
||||
#else
|
||||
const char* home = std::getenv("HOME");
|
||||
#endif
|
||||
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
|
||||
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
||||
|
||||
switch (target) {
|
||||
case GameListShortcutTarget::Desktop: {
|
||||
const QString desktop_path =
|
||||
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
target_directory = desktop_path.toUtf8().toStdString();
|
||||
break;
|
||||
}
|
||||
case GameListShortcutTarget::Applications: {
|
||||
const QString applications_path =
|
||||
QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
|
||||
if (applications_path.isEmpty()) {
|
||||
const char* home = std::getenv("HOME");
|
||||
if (home != nullptr) {
|
||||
target_directory = std::filesystem::path(home) / ".local/share/applications";
|
||||
}
|
||||
} else {
|
||||
target_directory = applications_path.toUtf8().toStdString();
|
||||
if (target == GameListShortcutTarget::Desktop) {
|
||||
target_directory = home_path / "Desktop";
|
||||
if (!Common::FS::IsDir(target_directory)) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Create Shortcut"),
|
||||
tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
|
||||
.arg(QString::fromStdString(target_directory.generic_string())),
|
||||
QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
} else if (target == GameListShortcutTarget::Applications) {
|
||||
target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
|
||||
"applications";
|
||||
if (!Common::FS::CreateDirs(target_directory)) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Create Shortcut"),
|
||||
tr("Cannot create shortcut in applications menu. Path \"%1\" "
|
||||
"does not exist and cannot be created.")
|
||||
.arg(QString::fromStdString(target_directory.generic_string())),
|
||||
QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
const QDir dir(QString::fromStdString(target_directory.generic_string()));
|
||||
if (!dir.exists()) {
|
||||
QMessageBox::critical(this, tr("Create Shortcut"),
|
||||
tr("Cannot create shortcut. Path \"%1\" does not exist.")
|
||||
.arg(QString::fromStdString(target_directory.generic_string())),
|
||||
QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
|
||||
// Determine full paths for icon and shortcut
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
const char* home = std::getenv("HOME");
|
||||
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
|
||||
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
|
||||
|
||||
std::filesystem::path system_icons_path =
|
||||
(xdg_data_home == nullptr ? home_path / ".local/share/"
|
||||
: std::filesystem::path(xdg_data_home)) /
|
||||
(xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) /
|
||||
"icons/hicolor/256x256";
|
||||
if (!Common::FS::CreateDirs(system_icons_path)) {
|
||||
QMessageBox::critical(
|
||||
@ -3408,13 +3401,10 @@ void GMainWindow::OnRestartGame() {
|
||||
if (!system->IsPoweredOn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConfirmShutdownGame()) {
|
||||
// Make a copy since ShutdownGame edits game_path
|
||||
const auto current_game = QString(current_game_path);
|
||||
ShutdownGame();
|
||||
BootGame(current_game);
|
||||
}
|
||||
// Make a copy since ShutdownGame edits game_path
|
||||
const auto current_game = QString(current_game_path);
|
||||
ShutdownGame();
|
||||
BootGame(current_game);
|
||||
}
|
||||
|
||||
void GMainWindow::OnPauseGame() {
|
||||
@ -3436,39 +3426,18 @@ void GMainWindow::OnPauseContinueGame() {
|
||||
}
|
||||
|
||||
void GMainWindow::OnStopGame() {
|
||||
if (ConfirmShutdownGame()) {
|
||||
play_time_manager->Stop();
|
||||
// Update game list to show new play time
|
||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
if (OnShutdownBegin()) {
|
||||
OnShutdownBeginDialog();
|
||||
} else {
|
||||
OnEmulationStopped();
|
||||
}
|
||||
if (system->GetExitLocked() && !ConfirmForceLockedExit()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
play_time_manager->Stop();
|
||||
// Update game list to show new play time
|
||||
game_list->PopulateAsync(UISettings::values.game_dirs);
|
||||
if (OnShutdownBegin()) {
|
||||
OnShutdownBeginDialog();
|
||||
} else {
|
||||
if (UISettings::values.confirm_before_stopping.GetValue() ==
|
||||
ConfirmStop::Ask_Based_On_Game &&
|
||||
system->GetExitLocked()) {
|
||||
if (!ConfirmForceLockedExit()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
OnEmulationStopped();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GMainWindow::OnLoadComplete() {
|
||||
@ -3848,11 +3817,22 @@ void GMainWindow::OnTasRecord() {
|
||||
const bool is_recording = input_subsystem->GetTas()->Record();
|
||||
if (!is_recording) {
|
||||
is_tas_recording_dialog_active = true;
|
||||
|
||||
bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
|
||||
input_subsystem->GetTas()->SaveRecording(answer);
|
||||
ControllerNavigation* controller_navigation =
|
||||
new ControllerNavigation(system->HIDCore(), this);
|
||||
// Use QMessageBox instead of question so we can link controller navigation
|
||||
QMessageBox* box_dialog = new QMessageBox();
|
||||
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;
|
||||
}
|
||||
OnTasStateChanged();
|
||||
@ -4093,29 +4073,6 @@ void GMainWindow::OnLoadAmiibo() {
|
||||
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) {
|
||||
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
|
||||
const QString title = tr("Error loading Amiibo data");
|
||||
@ -4849,7 +4806,8 @@ bool GMainWindow::ConfirmClose() {
|
||||
return true;
|
||||
}
|
||||
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) {
|
||||
@ -4942,11 +4900,11 @@ bool GMainWindow::ConfirmChangeGame() {
|
||||
if (emu_thread == nullptr)
|
||||
return true;
|
||||
|
||||
// Use custom question to link controller navigation
|
||||
return question(
|
||||
const auto answer = QMessageBox::question(
|
||||
this, tr("yuzu"),
|
||||
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() {
|
||||
@ -4956,7 +4914,8 @@ bool GMainWindow::ConfirmForceLockedExit() {
|
||||
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?");
|
||||
|
||||
return question(this, tr("yuzu"), text);
|
||||
const auto answer = QMessageBox::question(this, tr("yuzu"), text);
|
||||
return answer != QMessageBox::No;
|
||||
}
|
||||
|
||||
void GMainWindow::RequestGameExit() {
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include <optional>
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
|
||||
@ -16,7 +15,6 @@
|
||||
#include "input_common/drivers/tas_input.h"
|
||||
#include "yuzu/compatibility_list.h"
|
||||
#include "yuzu/hotkeys.h"
|
||||
#include "yuzu/util/controller_navigation.h"
|
||||
|
||||
#ifdef __unix__
|
||||
#include <QVariant>
|
||||
@ -426,11 +424,6 @@ private:
|
||||
bool CheckSystemArchiveDecryption();
|
||||
bool CheckFirmwarePresence();
|
||||
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;
|
||||
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& 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<Core::System> system;
|
||||
|
@ -16,9 +16,7 @@
|
||||
#include "common/settings_enums.h"
|
||||
|
||||
using Settings::Category;
|
||||
using Settings::ConfirmStop;
|
||||
using Settings::Setting;
|
||||
using Settings::SwitchableSetting;
|
||||
|
||||
#ifndef CANNOT_EXPLICITLY_INSTANTIATE
|
||||
namespace Settings {
|
||||
@ -96,15 +94,6 @@ struct Values {
|
||||
Setting<bool> confirm_before_closing{
|
||||
linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
|
||||
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> pause_when_in_background{linkage,
|
||||
false,
|
||||
|
@ -63,15 +63,25 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
|
||||
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
|
||||
QImage source_image = image.convertToFormat(QImage::Format_RGB32);
|
||||
constexpr int bytes_per_pixel = 4;
|
||||
const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
|
||||
|
||||
const IconDir icon_dir{
|
||||
.id_reserved = 0,
|
||||
.id_type = 1,
|
||||
.id_count = static_cast<WORD>(scale_sizes.size()),
|
||||
};
|
||||
BITMAPINFOHEADER info_header{};
|
||||
info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
|
||||
info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
|
||||
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::FileType::BinaryFile);
|
||||
@ -82,55 +92,20 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
|
||||
if (!icon_file.Write(icon_dir)) {
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
if (!icon_file.Write(icon_entry)) {
|
||||
return false;
|
||||
}
|
||||
if (!icon_file.Write(info_header)) {
|
||||
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)) {
|
||||
for (int y = 0; y < image.height(); y++) {
|
||||
const auto* line = source_image.scanLine(source_image.height() - 1 - y);
|
||||
std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
|
||||
std::memcpy(line_data.data(), line, line_data.size());
|
||||
if (!icon_file.Write(line_data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < scaled_image.height(); y++) {
|
||||
const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
|
||||
std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
|
||||
std::memcpy(line_data.data(), line, line_data.size());
|
||||
if (!icon_file.Write(line_data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
icon_file.Close();
|
||||
|
||||
|
Reference in New Issue
Block a user