Compare commits
33 Commits
android-97
...
android-99
Author | SHA1 | Date | |
---|---|---|---|
5b95f66146 | |||
019d648d4e | |||
519c12da15 | |||
d74fd9e2fe | |||
d011d3ff0e | |||
3f05b8facd | |||
65d3300875 | |||
7b2ac196d2 | |||
2212c9653d | |||
fe04a7523a | |||
45a76637f5 | |||
bf7c45e560 | |||
6f4a080b98 | |||
da6824d9fd | |||
b36f45b239 | |||
07ae6659e7 | |||
880b004321 | |||
21ebe3e462 | |||
7b5d234558 | |||
84b0e29b56 | |||
5ecdcfa334 | |||
5f4857691e | |||
b50ce645ac | |||
b6d19329ac | |||
8c769b71a1 | |||
9512992fe2 | |||
6b10f04322 | |||
c206a04747 | |||
ec6ddaf766 | |||
00b0938f10 | |||
ed58445111 | |||
a764f49910 | |||
b881949b6d |
@ -5,6 +5,6 @@
|
|||||||
|
|
||||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
||||||
GITREV="`git show -s --format='%h'`"
|
GITREV="`git show -s --format='%h'`"
|
||||||
ARTIFACTS_DIR="artifacts"
|
ARTIFACTS_DIR="$PWD/artifacts"
|
||||||
|
|
||||||
mkdir -p "${ARTIFACTS_DIR}/"
|
mkdir -p "${ARTIFACTS_DIR}/"
|
||||||
|
@ -11,7 +11,7 @@ ccache -s
|
|||||||
mkdir build || true && cd build
|
mkdir build || true && cd build
|
||||||
cmake .. \
|
cmake .. \
|
||||||
-DBoost_USE_STATIC_LIBS=ON \
|
-DBoost_USE_STATIC_LIBS=ON \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
-DCMAKE_CXX_FLAGS="-march=x86-64-v2" \
|
-DCMAKE_CXX_FLAGS="-march=x86-64-v2" \
|
||||||
-DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ \
|
-DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ \
|
||||||
-DCMAKE_C_COMPILER=/usr/lib/ccache/gcc \
|
-DCMAKE_C_COMPILER=/usr/lib/ccache/gcc \
|
||||||
@ -31,6 +31,19 @@ ccache -s
|
|||||||
|
|
||||||
ctest -VV -C Release
|
ctest -VV -C Release
|
||||||
|
|
||||||
|
# Separate debug symbols from specified executables
|
||||||
|
for EXE in yuzu; do
|
||||||
|
EXE_PATH="bin/$EXE"
|
||||||
|
# Copy debug symbols out
|
||||||
|
objcopy --only-keep-debug $EXE_PATH $EXE_PATH.debug
|
||||||
|
# Add debug link and strip debug symbols
|
||||||
|
objcopy -g --add-gnu-debuglink=$EXE_PATH.debug $EXE_PATH $EXE_PATH.out
|
||||||
|
# Overwrite original with stripped copy
|
||||||
|
mv $EXE_PATH.out $EXE_PATH
|
||||||
|
done
|
||||||
|
# Strip debug symbols from all executables
|
||||||
|
find bin/ -type f -not -regex '.*.debug' -exec strip -g {} ';'
|
||||||
|
|
||||||
DESTDIR="$PWD/AppDir" ninja install
|
DESTDIR="$PWD/AppDir" ninja install
|
||||||
rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester
|
rm -vf AppDir/usr/bin/yuzu-cmd AppDir/usr/bin/yuzu-tester
|
||||||
|
|
||||||
|
@ -59,4 +59,9 @@ if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ];
|
|||||||
cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage"
|
cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Copy debug symbols to artifacts
|
||||||
|
cd build/bin
|
||||||
|
tar $COMPRESSION_FLAGS "${ARTIFACTS_DIR}/${REV_NAME}-debug.tar.xz" *.debug
|
||||||
|
cd -
|
||||||
|
|
||||||
. .ci/scripts/common/post-upload.sh
|
. .ci/scripts/common/post-upload.sh
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
| Pull Request | Commit | Title | Author | Merged? |
|
||||||
|
|----|----|----|----|----|
|
||||||
|
| [11649](https://github.com/yuzu-emu/yuzu//pull/11649) | [`a5fb9de6f`](https://github.com/yuzu-emu/yuzu//pull/11649/files) | android: Driver manager | [t895](https://github.com/t895/) | Yes |
|
||||||
|
|
||||||
|
|
||||||
|
End of merge log. You can find the original README.md below the break.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
4
dist/qt_themes/default/style.qss
vendored
4
dist/qt_themes/default/style.qss
vendored
@ -120,6 +120,10 @@ QWidget#connectedControllers {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QWidget#closeButtons {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
QWidget#playersSupported,
|
QWidget#playersSupported,
|
||||||
QWidget#controllersSupported,
|
QWidget#controllersSupported,
|
||||||
QWidget#controllerSupported1,
|
QWidget#controllerSupported1,
|
||||||
|
4
dist/qt_themes/qdarkstyle/style.qss
vendored
4
dist/qt_themes/qdarkstyle/style.qss
vendored
@ -1380,6 +1380,10 @@ QWidget#connectedControllers {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QWidget#closeButtons {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
QWidget#playersSupported,
|
QWidget#playersSupported,
|
||||||
QWidget#controllersSupported,
|
QWidget#controllersSupported,
|
||||||
QWidget#controllerSupported1,
|
QWidget#controllerSupported1,
|
||||||
|
@ -2305,6 +2305,10 @@ QWidget#connectedControllers {
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QWidget#closeButtons {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
QWidget#playersSupported,
|
QWidget#playersSupported,
|
||||||
QWidget#controllersSupported,
|
QWidget#controllersSupported,
|
||||||
QWidget#controllerSupported1,
|
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)
|
set(CAN_BUILD_NX_TZDB false)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(NX_TZDB_VERSION "220816")
|
set(NX_TZDB_VERSION "221202")
|
||||||
set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
|
set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
|
||||||
|
|
||||||
set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
|
set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
|
||||||
|
2
externals/nx_tzdb/tzdb_to_nx
vendored
2
externals/nx_tzdb/tzdb_to_nx
vendored
Submodule externals/nx_tzdb/tzdb_to_nx updated: 212afa2394...0d17dd066d
@ -15,13 +15,9 @@ import androidx.annotation.Keep
|
|||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
|
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||||
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
|
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil.exists
|
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
|
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
|
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
|
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
||||||
|
|
||||||
@ -75,7 +71,7 @@ object NativeLibrary {
|
|||||||
return if (isNativePath(path!!)) {
|
return if (isNativePath(path!!)) {
|
||||||
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
|
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
|
||||||
} else {
|
} else {
|
||||||
openContentUri(appContext, path, openmode)
|
FileUtil.openContentUri(path, openmode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +81,7 @@ object NativeLibrary {
|
|||||||
return if (isNativePath(path!!)) {
|
return if (isNativePath(path!!)) {
|
||||||
YuzuApplication.documentsTree!!.getFileSize(path)
|
YuzuApplication.documentsTree!!.getFileSize(path)
|
||||||
} else {
|
} else {
|
||||||
getFileSize(appContext, path)
|
FileUtil.getFileSize(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +91,7 @@ object NativeLibrary {
|
|||||||
return if (isNativePath(path!!)) {
|
return if (isNativePath(path!!)) {
|
||||||
YuzuApplication.documentsTree!!.exists(path)
|
YuzuApplication.documentsTree!!.exists(path)
|
||||||
} else {
|
} else {
|
||||||
exists(appContext, path)
|
FileUtil.exists(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +101,7 @@ object NativeLibrary {
|
|||||||
return if (isNativePath(path!!)) {
|
return if (isNativePath(path!!)) {
|
||||||
YuzuApplication.documentsTree!!.isDirectory(path)
|
YuzuApplication.documentsTree!!.isDirectory(path)
|
||||||
} else {
|
} else {
|
||||||
isDirectory(appContext, path)
|
FileUtil.isDirectory(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class YuzuApplication : Application() {
|
|||||||
application = this
|
application = this
|
||||||
documentsTree = DocumentsTree()
|
documentsTree = DocumentsTree()
|
||||||
DirectoryInitialization.start()
|
DirectoryInitialization.start()
|
||||||
GpuDriverHelper.initializeDriverParameters(applicationContext)
|
GpuDriverHelper.initializeDriverParameters()
|
||||||
NativeLibrary.logDeviceInfo()
|
NativeLibrary.logDeviceInfo()
|
||||||
|
|
||||||
createNotificationChannels()
|
createNotificationChannels()
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding
|
||||||
|
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
|
||||||
|
|
||||||
|
class DriverAdapter(private val driverViewModel: DriverViewModel) :
|
||||||
|
ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>(
|
||||||
|
AsyncDifferConfig.Builder(DiffCallback()).build()
|
||||||
|
) {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
|
||||||
|
val binding =
|
||||||
|
CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return DriverViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = currentList.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: DriverViewHolder, position: Int) =
|
||||||
|
holder.bind(currentList[position])
|
||||||
|
|
||||||
|
private fun onSelectDriver(position: Int) {
|
||||||
|
driverViewModel.setSelectedDriverIndex(position)
|
||||||
|
notifyItemChanged(driverViewModel.previouslySelectedDriver)
|
||||||
|
notifyItemChanged(driverViewModel.selectedDriver)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) {
|
||||||
|
if (driverViewModel.selectedDriver > position) {
|
||||||
|
driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1)
|
||||||
|
}
|
||||||
|
if (GpuDriverHelper.customDriverData == driverData.second) {
|
||||||
|
driverViewModel.setSelectedDriverIndex(0)
|
||||||
|
}
|
||||||
|
driverViewModel.driversToDelete.add(driverData.first)
|
||||||
|
driverViewModel.removeDriver(driverData)
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
notifyItemChanged(driverViewModel.selectedDriver)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class DriverViewHolder(val binding: CardDriverOptionBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
private lateinit var driverData: Pair<String, GpuDriverMetadata>
|
||||||
|
|
||||||
|
fun bind(driverData: Pair<String, GpuDriverMetadata>) {
|
||||||
|
this.driverData = driverData
|
||||||
|
val driver = driverData.second
|
||||||
|
|
||||||
|
binding.apply {
|
||||||
|
radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition
|
||||||
|
root.setOnClickListener {
|
||||||
|
onSelectDriver(bindingAdapterPosition)
|
||||||
|
}
|
||||||
|
buttonDelete.setOnClickListener {
|
||||||
|
onDeleteDriver(driverData, bindingAdapterPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay marquee by 3s
|
||||||
|
title.postDelayed(
|
||||||
|
{
|
||||||
|
title.isSelected = true
|
||||||
|
title.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
version.isSelected = true
|
||||||
|
version.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
description.isSelected = true
|
||||||
|
description.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||||
|
},
|
||||||
|
3000
|
||||||
|
)
|
||||||
|
if (driver.name == null) {
|
||||||
|
title.setText(R.string.system_gpu_driver)
|
||||||
|
description.text = ""
|
||||||
|
version.text = ""
|
||||||
|
version.visibility = View.GONE
|
||||||
|
description.visibility = View.GONE
|
||||||
|
buttonDelete.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
title.text = driver.name
|
||||||
|
version.text = driver.version
|
||||||
|
description.text = driver.description
|
||||||
|
version.visibility = View.VISIBLE
|
||||||
|
description.visibility = View.VISIBLE
|
||||||
|
buttonDelete.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() {
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: Pair<String, GpuDriverMetadata>,
|
||||||
|
newItem: Pair<String, GpuDriverMetadata>
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.first == newItem.first
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: Pair<String, GpuDriverMetadata>,
|
||||||
|
newItem: Pair<String, GpuDriverMetadata>
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.second == newItem.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,185 @@
|
|||||||
|
// 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.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
|
||||||
|
) {
|
||||||
|
// Ignore file exceptions when a user selects an invalid zip
|
||||||
|
try {
|
||||||
|
GpuDriverHelper.copyDriverToInternalStorage(result)
|
||||||
|
} catch (_: IOException) {
|
||||||
|
return@newInstance getString(R.string.select_gpu_driver_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
val driverData = GpuDriverHelper.customDriverData
|
||||||
|
if (driverData.name == null) {
|
||||||
|
return@newInstance getString(R.string.select_gpu_driver_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
val driverInList =
|
||||||
|
driverViewModel.driverList.value.firstOrNull { it.second == driverData }
|
||||||
|
if (driverInList != null) {
|
||||||
|
return@newInstance getString(R.string.driver_already_installed)
|
||||||
|
} else {
|
||||||
|
driverViewModel.addDriver(
|
||||||
|
Pair(
|
||||||
|
"${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}",
|
||||||
|
driverData
|
||||||
|
)
|
||||||
|
)
|
||||||
|
driverViewModel.setNewDriverInstalled(true)
|
||||||
|
}
|
||||||
|
return@newInstance Any()
|
||||||
|
}.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||||
|
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||||
|
|
||||||
|
class DriversLoadingDialogFragment : DialogFragment() {
|
||||||
|
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||||
|
|
||||||
|
private lateinit var binding: DialogProgressBarBinding
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
binding = DialogProgressBarBinding.inflate(layoutInflater)
|
||||||
|
binding.progressBar.isIndeterminate = true
|
||||||
|
|
||||||
|
isCancelable = false
|
||||||
|
|
||||||
|
return MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.loading)
|
||||||
|
.setView(binding.root)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View = binding.root
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
viewLifecycleOwner.lifecycleScope.apply {
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
driverViewModel.areDriversLoading.collect { checkForDismiss() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
driverViewModel.isDriverReady.collect { checkForDismiss() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
driverViewModel.isDeletingDrivers.collect { checkForDismiss() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkForDismiss() {
|
||||||
|
if (driverViewModel.isInteractionAllowed) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "DriversLoadingDialogFragment"
|
||||||
|
}
|
||||||
|
}
|
@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.slider.Slider
|
import com.google.android.material.slider.Slider
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
@ -50,6 +51,7 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
|
|||||||
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
|
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||||
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||||
@ -70,6 +72,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
private lateinit var game: Game
|
private lateinit var game: Game
|
||||||
|
|
||||||
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
private val emulationViewModel: EmulationViewModel by activityViewModels()
|
||||||
|
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||||
|
|
||||||
private var isInFoldableLayout = false
|
private var isInFoldableLayout = false
|
||||||
|
|
||||||
@ -299,6 +302,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
driverViewModel.isDriverReady.collect {
|
||||||
|
if (it && !emulationState.isRunning) {
|
||||||
|
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||||
|
DirectoryInitialization.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateScreenLayout()
|
||||||
|
|
||||||
|
emulationState.run(emulationActivity!!.isActivityRecreated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,17 +350,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
|
||||||
DirectoryInitialization.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateScreenLayout()
|
|
||||||
|
|
||||||
emulationState.run(emulationActivity!!.isActivityRecreated)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
|
if (emulationState.isRunning && emulationActivity?.isInPictureInPictureMode != true) {
|
||||||
emulationState.pause()
|
emulationState.pause()
|
||||||
|
@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.fragments
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -28,7 +27,6 @@ import androidx.fragment.app.activityViewModels
|
|||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
import org.yuzu.yuzu_emu.BuildConfig
|
import org.yuzu.yuzu_emu.BuildConfig
|
||||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||||
@ -37,6 +35,7 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
|||||||
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
||||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
|
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||||
@ -50,6 +49,7 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
private lateinit var mainActivity: MainActivity
|
private lateinit var mainActivity: MainActivity
|
||||||
|
|
||||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||||
|
private val driverViewModel: DriverViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -107,13 +107,17 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.install_gpu_driver,
|
R.string.gpu_driver_manager,
|
||||||
R.string.install_gpu_driver_description,
|
R.string.install_gpu_driver_description,
|
||||||
R.drawable.ic_exit,
|
R.drawable.ic_build,
|
||||||
{ driverInstaller() },
|
{
|
||||||
|
binding.root.findNavController()
|
||||||
|
.navigate(R.id.action_homeSettingsFragment_to_driverManagerFragment)
|
||||||
|
},
|
||||||
{ GpuDriverHelper.supportsCustomDriverLoading() },
|
{ GpuDriverHelper.supportsCustomDriverLoading() },
|
||||||
R.string.custom_driver_not_supported,
|
R.string.custom_driver_not_supported,
|
||||||
R.string.custom_driver_not_supported_description
|
R.string.custom_driver_not_supported_description,
|
||||||
|
driverViewModel.selectedDriverMetadata
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(
|
||||||
@ -292,31 +296,6 @@ class HomeSettingsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun driverInstaller() {
|
|
||||||
// Get the driver name for the dialog message.
|
|
||||||
var driverName = GpuDriverHelper.customDriverName
|
|
||||||
if (driverName == null) {
|
|
||||||
driverName = getString(R.string.system_gpu_driver)
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(getString(R.string.select_gpu_driver_title))
|
|
||||||
.setMessage(driverName)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.setNeutralButton(R.string.select_gpu_driver_default) { _: DialogInterface?, _: Int ->
|
|
||||||
GpuDriverHelper.installDefaultDriver(requireContext())
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.select_gpu_driver_use_default,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
.setPositiveButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
|
|
||||||
mainActivity.getDriver.launch(arrayOf("application/zip"))
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shareLog() {
|
private fun shareLog() {
|
||||||
val file = DocumentFile.fromSingleUri(
|
val file = DocumentFile.fromSingleUri(
|
||||||
mainActivity,
|
mainActivity,
|
||||||
|
@ -10,8 +10,8 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
@ -78,6 +78,10 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
|
|||||||
requireActivity().supportFragmentManager,
|
requireActivity().supportFragmentManager,
|
||||||
MessageDialogFragment.TAG
|
MessageDialogFragment.TAG
|
||||||
)
|
)
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
taskViewModel.clear()
|
taskViewModel.clear()
|
||||||
}
|
}
|
||||||
@ -115,7 +119,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
|
|||||||
private const val CANCELLABLE = "Cancellable"
|
private const val CANCELLABLE = "Cancellable"
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
activity: AppCompatActivity,
|
activity: FragmentActivity,
|
||||||
titleId: Int,
|
titleId: Int,
|
||||||
cancellable: Boolean = false,
|
cancellable: Boolean = false,
|
||||||
task: () -> Any
|
task: () -> Any
|
||||||
|
@ -0,0 +1,158 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.model
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
|
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class DriverViewModel : ViewModel() {
|
||||||
|
private val _areDriversLoading = MutableStateFlow(false)
|
||||||
|
val areDriversLoading: StateFlow<Boolean> get() = _areDriversLoading
|
||||||
|
|
||||||
|
private val _isDriverReady = MutableStateFlow(true)
|
||||||
|
val isDriverReady: StateFlow<Boolean> get() = _isDriverReady
|
||||||
|
|
||||||
|
private val _isDeletingDrivers = MutableStateFlow(false)
|
||||||
|
val isDeletingDrivers: StateFlow<Boolean> get() = _isDeletingDrivers
|
||||||
|
|
||||||
|
private val _driverList = MutableStateFlow(mutableListOf<Pair<String, GpuDriverMetadata>>())
|
||||||
|
val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
|
||||||
|
|
||||||
|
var previouslySelectedDriver = 0
|
||||||
|
var selectedDriver = -1
|
||||||
|
|
||||||
|
private val _selectedDriverMetadata =
|
||||||
|
MutableStateFlow(
|
||||||
|
GpuDriverHelper.customDriverData.name
|
||||||
|
?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
|
||||||
|
)
|
||||||
|
val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata
|
||||||
|
|
||||||
|
private val _newDriverInstalled = MutableStateFlow(false)
|
||||||
|
val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
|
||||||
|
|
||||||
|
val driversToDelete = mutableListOf<String>()
|
||||||
|
|
||||||
|
val isInteractionAllowed
|
||||||
|
get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value
|
||||||
|
|
||||||
|
init {
|
||||||
|
_areDriversLoading.value = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val drivers = GpuDriverHelper.getDrivers()
|
||||||
|
val currentDriverMetadata = GpuDriverHelper.customDriverData
|
||||||
|
for (i in drivers.indices) {
|
||||||
|
if (drivers[i].second == currentDriverMetadata) {
|
||||||
|
setSelectedDriverIndex(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a user had installed a driver before the manager was implemented, this zips
|
||||||
|
// the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
|
||||||
|
// be indexed and exported as expected.
|
||||||
|
if (selectedDriver == -1) {
|
||||||
|
val driverToSave =
|
||||||
|
File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
|
||||||
|
driverToSave.createNewFile()
|
||||||
|
FileUtil.zipFromInternalStorage(
|
||||||
|
File(GpuDriverHelper.driverInstallationPath!!),
|
||||||
|
GpuDriverHelper.driverInstallationPath!!,
|
||||||
|
BufferedOutputStream(driverToSave.outputStream())
|
||||||
|
)
|
||||||
|
drivers.add(Pair(driverToSave.path, currentDriverMetadata))
|
||||||
|
setSelectedDriverIndex(drivers.size - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
_driverList.value = drivers
|
||||||
|
_areDriversLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSelectedDriverIndex(value: Int) {
|
||||||
|
if (selectedDriver != -1) {
|
||||||
|
previouslySelectedDriver = selectedDriver
|
||||||
|
}
|
||||||
|
selectedDriver = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setNewDriverInstalled(value: Boolean) {
|
||||||
|
_newDriverInstalled.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
|
||||||
|
val driverIndex = _driverList.value.indexOfFirst { it == driverData }
|
||||||
|
if (driverIndex == -1) {
|
||||||
|
setSelectedDriverIndex(_driverList.value.size)
|
||||||
|
_driverList.value.add(driverData)
|
||||||
|
_selectedDriverMetadata.value = driverData.second.name
|
||||||
|
?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
|
||||||
|
} else {
|
||||||
|
setSelectedDriverIndex(driverIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
|
||||||
|
_driverList.value.remove(driverData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCloseDriverManager() {
|
||||||
|
_isDeletingDrivers.value = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
driversToDelete.forEach {
|
||||||
|
val driver = File(it)
|
||||||
|
if (driver.exists()) {
|
||||||
|
driver.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
driversToDelete.clear()
|
||||||
|
_isDeletingDrivers.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDriverReady.value = false
|
||||||
|
viewModelScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (selectedDriver == 0) {
|
||||||
|
GpuDriverHelper.installDefaultDriver()
|
||||||
|
setDriverReady()
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
|
||||||
|
val driverToInstall = File(driverList.value[selectedDriver].first)
|
||||||
|
if (driverToInstall.exists()) {
|
||||||
|
GpuDriverHelper.installCustomDriver(driverToInstall)
|
||||||
|
} else {
|
||||||
|
GpuDriverHelper.installDefaultDriver()
|
||||||
|
}
|
||||||
|
setDriverReady()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setDriverReady() {
|
||||||
|
_isDriverReady.value = true
|
||||||
|
_selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name
|
||||||
|
?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
|
||||||
|
}
|
||||||
|
}
|
@ -29,12 +29,10 @@ import androidx.navigation.fragment.NavHostFragment
|
|||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FilenameFilter
|
import java.io.FilenameFilter
|
||||||
import java.io.IOException
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -43,7 +41,6 @@ import org.yuzu.yuzu_emu.NativeLibrary
|
|||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||||
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
|
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
|
||||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
|
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
|
||||||
@ -343,11 +340,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
|
|
||||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||||
if (FileUtil.copyUriToInternalStorage(
|
if (FileUtil.copyUriToInternalStorage(
|
||||||
applicationContext,
|
|
||||||
result,
|
result,
|
||||||
dstPath,
|
dstPath,
|
||||||
"prod.keys"
|
"prod.keys"
|
||||||
)
|
) != null
|
||||||
) {
|
) {
|
||||||
if (NativeLibrary.reloadKeys()) {
|
if (NativeLibrary.reloadKeys()) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
@ -446,11 +442,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
|
|
||||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||||
if (FileUtil.copyUriToInternalStorage(
|
if (FileUtil.copyUriToInternalStorage(
|
||||||
applicationContext,
|
|
||||||
result,
|
result,
|
||||||
dstPath,
|
dstPath,
|
||||||
"key_retail.bin"
|
"key_retail.bin"
|
||||||
)
|
) != null
|
||||||
) {
|
) {
|
||||||
if (NativeLibrary.reloadKeys()) {
|
if (NativeLibrary.reloadKeys()) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
@ -469,59 +464,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val getDriver =
|
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
|
||||||
if (result == null) {
|
|
||||||
return@registerForActivityResult
|
|
||||||
}
|
|
||||||
|
|
||||||
val takeFlags =
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
contentResolver.takePersistableUriPermission(
|
|
||||||
result,
|
|
||||||
takeFlags
|
|
||||||
)
|
|
||||||
|
|
||||||
val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
|
|
||||||
progressBinding.progressBar.isIndeterminate = true
|
|
||||||
val installationDialog = MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.installing_driver)
|
|
||||||
.setView(progressBinding.root)
|
|
||||||
.show()
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
// Ignore file exceptions when a user selects an invalid zip
|
|
||||||
try {
|
|
||||||
GpuDriverHelper.installCustomDriver(applicationContext, result)
|
|
||||||
} catch (_: IOException) {
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
installationDialog.dismiss()
|
|
||||||
|
|
||||||
val driverName = GpuDriverHelper.customDriverName
|
|
||||||
if (driverName != null) {
|
|
||||||
Toast.makeText(
|
|
||||||
applicationContext,
|
|
||||||
getString(
|
|
||||||
R.string.select_gpu_driver_install_success,
|
|
||||||
driverName
|
|
||||||
),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
applicationContext,
|
|
||||||
R.string.select_gpu_driver_error,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val installGameUpdate = registerForActivityResult(
|
val installGameUpdate = registerForActivityResult(
|
||||||
ActivityResultContracts.OpenMultipleDocuments()
|
ActivityResultContracts.OpenMultipleDocuments()
|
||||||
) { documents: List<Uri> ->
|
) { documents: List<Uri> ->
|
||||||
|
@ -7,7 +7,6 @@ import android.net.Uri
|
|||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import org.yuzu.yuzu_emu.YuzuApplication
|
|
||||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||||
|
|
||||||
class DocumentsTree {
|
class DocumentsTree {
|
||||||
@ -22,7 +21,7 @@ class DocumentsTree {
|
|||||||
|
|
||||||
fun openContentUri(filepath: String, openMode: String?): Int {
|
fun openContentUri(filepath: String, openMode: String?): Int {
|
||||||
val node = resolvePath(filepath) ?: return -1
|
val node = resolvePath(filepath) ?: return -1
|
||||||
return FileUtil.openContentUri(YuzuApplication.appContext, node.uri.toString(), openMode)
|
return FileUtil.openContentUri(node.uri.toString(), openMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFileSize(filepath: String): Long {
|
fun getFileSize(filepath: String): Long {
|
||||||
@ -30,7 +29,7 @@ class DocumentsTree {
|
|||||||
return if (node == null || node.isDirectory) {
|
return if (node == null || node.isDirectory) {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
|
FileUtil.getFileSize(node.uri.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +66,7 @@ class DocumentsTree {
|
|||||||
* @param parent parent node of this level
|
* @param parent parent node of this level
|
||||||
*/
|
*/
|
||||||
private fun structTree(parent: DocumentsNode) {
|
private fun structTree(parent: DocumentsNode) {
|
||||||
val documents = FileUtil.listFiles(YuzuApplication.appContext, parent.uri!!)
|
val documents = FileUtil.listFiles(parent.uri!!)
|
||||||
for (document in documents) {
|
for (document in documents) {
|
||||||
val node = DocumentsNode(document)
|
val node = DocumentsNode(document)
|
||||||
node.parent = parent
|
node.parent = parent
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
@ -11,7 +10,6 @@ import androidx.documentfile.provider.DocumentFile
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
@ -21,6 +19,8 @@ import org.yuzu.yuzu_emu.YuzuApplication
|
|||||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||||
import org.yuzu.yuzu_emu.model.TaskState
|
import org.yuzu.yuzu_emu.model.TaskState
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
|
import java.lang.NullPointerException
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
object FileUtil {
|
object FileUtil {
|
||||||
@ -29,6 +29,8 @@ object FileUtil {
|
|||||||
const val APPLICATION_OCTET_STREAM = "application/octet-stream"
|
const val APPLICATION_OCTET_STREAM = "application/octet-stream"
|
||||||
const val TEXT_PLAIN = "text/plain"
|
const val TEXT_PLAIN = "text/plain"
|
||||||
|
|
||||||
|
private val context get() = YuzuApplication.appContext
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a file from directory with filename.
|
* Create a file from directory with filename.
|
||||||
* @param context Application context
|
* @param context Application context
|
||||||
@ -36,11 +38,11 @@ object FileUtil {
|
|||||||
* @param filename file display name.
|
* @param filename file display name.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
fun createFile(context: Context?, directory: String?, filename: String): DocumentFile? {
|
fun createFile(directory: String?, filename: String): DocumentFile? {
|
||||||
var decodedFilename = filename
|
var decodedFilename = filename
|
||||||
try {
|
try {
|
||||||
val directoryUri = Uri.parse(directory)
|
val directoryUri = Uri.parse(directory)
|
||||||
val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
|
val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
|
||||||
decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
|
decodedFilename = URLDecoder.decode(decodedFilename, DECODE_METHOD)
|
||||||
var mimeType = APPLICATION_OCTET_STREAM
|
var mimeType = APPLICATION_OCTET_STREAM
|
||||||
if (decodedFilename.endsWith(".txt")) {
|
if (decodedFilename.endsWith(".txt")) {
|
||||||
@ -56,16 +58,15 @@ object FileUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a directory from directory with filename.
|
* Create a directory from directory with filename.
|
||||||
* @param context Application context
|
|
||||||
* @param directory parent path for directory.
|
* @param directory parent path for directory.
|
||||||
* @param directoryName directory display name.
|
* @param directoryName directory display name.
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
fun createDir(context: Context?, directory: String?, directoryName: String?): DocumentFile? {
|
fun createDir(directory: String?, directoryName: String?): DocumentFile? {
|
||||||
var decodedDirectoryName = directoryName
|
var decodedDirectoryName = directoryName
|
||||||
try {
|
try {
|
||||||
val directoryUri = Uri.parse(directory)
|
val directoryUri = Uri.parse(directory)
|
||||||
val parent = DocumentFile.fromTreeUri(context!!, directoryUri) ?: return null
|
val parent = DocumentFile.fromTreeUri(context, directoryUri) ?: return null
|
||||||
decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
|
decodedDirectoryName = URLDecoder.decode(decodedDirectoryName, DECODE_METHOD)
|
||||||
val isExist = parent.findFile(decodedDirectoryName)
|
val isExist = parent.findFile(decodedDirectoryName)
|
||||||
return isExist ?: parent.createDirectory(decodedDirectoryName)
|
return isExist ?: parent.createDirectory(decodedDirectoryName)
|
||||||
@ -77,13 +78,12 @@ object FileUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Open content uri and return file descriptor to JNI.
|
* Open content uri and return file descriptor to JNI.
|
||||||
* @param context Application context
|
|
||||||
* @param path Native content uri path
|
* @param path Native content uri path
|
||||||
* @param openMode will be one of "r", "r", "rw", "wa", "rwa"
|
* @param openMode will be one of "r", "r", "rw", "wa", "rwa"
|
||||||
* @return file descriptor
|
* @return file descriptor
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun openContentUri(context: Context, path: String, openMode: String?): Int {
|
fun openContentUri(path: String, openMode: String?): Int {
|
||||||
try {
|
try {
|
||||||
val uri = Uri.parse(path)
|
val uri = Uri.parse(path)
|
||||||
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
|
val parcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, openMode!!)
|
||||||
@ -103,11 +103,10 @@ object FileUtil {
|
|||||||
/**
|
/**
|
||||||
* Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
|
* Reference: https://stackoverflow.com/questions/42186820/documentfile-is-very-slow
|
||||||
* This function will be faster than DoucmentFile.listFiles
|
* This function will be faster than DoucmentFile.listFiles
|
||||||
* @param context Application context
|
|
||||||
* @param uri Directory uri.
|
* @param uri Directory uri.
|
||||||
* @return CheapDocument lists.
|
* @return CheapDocument lists.
|
||||||
*/
|
*/
|
||||||
fun listFiles(context: Context, uri: Uri): Array<MinimalDocumentFile> {
|
fun listFiles(uri: Uri): Array<MinimalDocumentFile> {
|
||||||
val resolver = context.contentResolver
|
val resolver = context.contentResolver
|
||||||
val columns = arrayOf(
|
val columns = arrayOf(
|
||||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||||
@ -145,7 +144,7 @@ object FileUtil {
|
|||||||
* @param path Native content uri path
|
* @param path Native content uri path
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
fun exists(context: Context, path: String?): Boolean {
|
fun exists(path: String?): Boolean {
|
||||||
var c: Cursor? = null
|
var c: Cursor? = null
|
||||||
try {
|
try {
|
||||||
val mUri = Uri.parse(path)
|
val mUri = Uri.parse(path)
|
||||||
@ -165,7 +164,7 @@ object FileUtil {
|
|||||||
* @param path content uri path
|
* @param path content uri path
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
fun isDirectory(context: Context, path: String): Boolean {
|
fun isDirectory(path: String): Boolean {
|
||||||
val resolver = context.contentResolver
|
val resolver = context.contentResolver
|
||||||
val columns = arrayOf(
|
val columns = arrayOf(
|
||||||
DocumentsContract.Document.COLUMN_MIME_TYPE
|
DocumentsContract.Document.COLUMN_MIME_TYPE
|
||||||
@ -210,10 +209,10 @@ object FileUtil {
|
|||||||
return filename
|
return filename
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFilesName(context: Context, path: String): Array<String> {
|
fun getFilesName(path: String): Array<String> {
|
||||||
val uri = Uri.parse(path)
|
val uri = Uri.parse(path)
|
||||||
val files: MutableList<String> = ArrayList()
|
val files: MutableList<String> = ArrayList()
|
||||||
for (file in listFiles(context, uri)) {
|
for (file in listFiles(uri)) {
|
||||||
files.add(file.filename)
|
files.add(file.filename)
|
||||||
}
|
}
|
||||||
return files.toTypedArray()
|
return files.toTypedArray()
|
||||||
@ -225,7 +224,7 @@ object FileUtil {
|
|||||||
* @return long file size
|
* @return long file size
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getFileSize(context: Context, path: String): Long {
|
fun getFileSize(path: String): Long {
|
||||||
val resolver = context.contentResolver
|
val resolver = context.contentResolver
|
||||||
val columns = arrayOf(
|
val columns = arrayOf(
|
||||||
DocumentsContract.Document.COLUMN_SIZE
|
DocumentsContract.Document.COLUMN_SIZE
|
||||||
@ -245,44 +244,38 @@ object FileUtil {
|
|||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an input stream with a given [Uri] and copies its data to the given path. This will
|
||||||
|
* overwrite any pre-existing files.
|
||||||
|
*
|
||||||
|
* @param sourceUri The [Uri] to copy data from
|
||||||
|
* @param destinationParentPath Destination directory
|
||||||
|
* @param destinationFilename Optionally renames the file once copied
|
||||||
|
*/
|
||||||
fun copyUriToInternalStorage(
|
fun copyUriToInternalStorage(
|
||||||
context: Context,
|
sourceUri: Uri,
|
||||||
sourceUri: Uri?,
|
|
||||||
destinationParentPath: String,
|
destinationParentPath: String,
|
||||||
destinationFilename: String
|
destinationFilename: String = ""
|
||||||
): Boolean {
|
): File? =
|
||||||
var input: InputStream? = null
|
|
||||||
var output: FileOutputStream? = null
|
|
||||||
try {
|
try {
|
||||||
input = context.contentResolver.openInputStream(sourceUri!!)
|
val fileName =
|
||||||
output = FileOutputStream("$destinationParentPath/$destinationFilename")
|
if (destinationFilename == "") getFilename(sourceUri) else "/$destinationFilename"
|
||||||
val buffer = ByteArray(1024)
|
val inputStream = context.contentResolver.openInputStream(sourceUri)!!
|
||||||
var len: Int
|
|
||||||
while (input!!.read(buffer).also { len = it } != -1) {
|
val destinationFile = File("$destinationParentPath$fileName")
|
||||||
output.write(buffer, 0, len)
|
if (destinationFile.exists()) {
|
||||||
|
destinationFile.delete()
|
||||||
}
|
}
|
||||||
output.flush()
|
|
||||||
return true
|
destinationFile.outputStream().use { fos ->
|
||||||
} catch (e: Exception) {
|
inputStream.use { it.copyTo(fos) }
|
||||||
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.
|
* Extracts the given zip file into the given directory.
|
||||||
@ -368,4 +361,12 @@ object FileUtil {
|
|||||||
return fileName.substring(fileName.lastIndexOf(".") + 1)
|
return fileName.substring(fileName.lastIndexOf(".") + 1)
|
||||||
.lowercase()
|
.lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun getStringFromFile(file: File): String =
|
||||||
|
String(file.readBytes(), StandardCharsets.UTF_8)
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun getStringFromInputStream(stream: InputStream): String =
|
||||||
|
String(stream.readBytes(), StandardCharsets.UTF_8)
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ object GameHelper {
|
|||||||
// Ensure keys are loaded so that ROM metadata can be decrypted.
|
// Ensure keys are loaded so that ROM metadata can be decrypted.
|
||||||
NativeLibrary.reloadKeys()
|
NativeLibrary.reloadKeys()
|
||||||
|
|
||||||
addGamesRecursive(games, FileUtil.listFiles(context, gamesUri), 3)
|
addGamesRecursive(games, FileUtil.listFiles(gamesUri), 3)
|
||||||
|
|
||||||
// Cache list of games found on disk
|
// Cache list of games found on disk
|
||||||
val serializedGames = mutableSetOf<String>()
|
val serializedGames = mutableSetOf<String>()
|
||||||
@ -58,7 +58,7 @@ object GameHelper {
|
|||||||
if (it.isDirectory) {
|
if (it.isDirectory) {
|
||||||
addGamesRecursive(
|
addGamesRecursive(
|
||||||
games,
|
games,
|
||||||
FileUtil.listFiles(YuzuApplication.appContext, it.uri),
|
FileUtil.listFiles(it.uri),
|
||||||
depth - 1
|
depth - 1
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,64 +3,33 @@
|
|||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.zip.ZipInputStream
|
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
|
import java.util.zip.ZipException
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
object GpuDriverHelper {
|
object GpuDriverHelper {
|
||||||
private const val META_JSON_FILENAME = "meta.json"
|
private const val META_JSON_FILENAME = "meta.json"
|
||||||
private const val DRIVER_INTERNAL_FILENAME = "gpu_driver.zip"
|
|
||||||
private var fileRedirectionPath: String? = null
|
private var fileRedirectionPath: String? = null
|
||||||
private var driverInstallationPath: String? = null
|
var driverInstallationPath: String? = null
|
||||||
private var hookLibPath: String? = null
|
private var hookLibPath: String? = null
|
||||||
|
|
||||||
@Throws(IOException::class)
|
val driverStoragePath get() = DirectoryInitialization.userDirectory!! + "/gpu_drivers/"
|
||||||
private fun unzip(zipFilePath: String, destDir: String) {
|
|
||||||
val dir = File(destDir)
|
|
||||||
|
|
||||||
// Create output directory if it doesn't exist
|
fun initializeDriverParameters() {
|
||||||
if (!dir.exists()) dir.mkdirs()
|
|
||||||
|
|
||||||
// Unpack the files.
|
|
||||||
val inputStream = FileInputStream(zipFilePath)
|
|
||||||
val zis = ZipInputStream(BufferedInputStream(inputStream))
|
|
||||||
val buffer = ByteArray(1024)
|
|
||||||
var ze = zis.nextEntry
|
|
||||||
while (ze != null) {
|
|
||||||
val newFile = File(destDir, ze.name)
|
|
||||||
val canonicalPath = newFile.canonicalPath
|
|
||||||
if (!canonicalPath.startsWith(destDir + ze.name)) {
|
|
||||||
throw SecurityException("Zip file attempted path traversal! " + ze.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
newFile.parentFile!!.mkdirs()
|
|
||||||
val fos = FileOutputStream(newFile)
|
|
||||||
var len: Int
|
|
||||||
while (zis.read(buffer).also { len = it } > 0) {
|
|
||||||
fos.write(buffer, 0, len)
|
|
||||||
}
|
|
||||||
fos.close()
|
|
||||||
zis.closeEntry()
|
|
||||||
ze = zis.nextEntry
|
|
||||||
}
|
|
||||||
zis.closeEntry()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun initializeDriverParameters(context: Context) {
|
|
||||||
try {
|
try {
|
||||||
// Initialize the file redirection directory.
|
// Initialize the file redirection directory.
|
||||||
fileRedirectionPath =
|
fileRedirectionPath = YuzuApplication.appContext
|
||||||
context.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
|
.getExternalFilesDir(null)!!.canonicalPath + "/gpu/vk_file_redirect/"
|
||||||
|
|
||||||
// Initialize the driver installation directory.
|
// Initialize the driver installation directory.
|
||||||
driverInstallationPath = context.filesDir.canonicalPath + "/gpu_driver/"
|
driverInstallationPath = YuzuApplication.appContext
|
||||||
|
.filesDir.canonicalPath + "/gpu_driver/"
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
throw RuntimeException(e)
|
throw RuntimeException(e)
|
||||||
}
|
}
|
||||||
@ -69,68 +38,169 @@ object GpuDriverHelper {
|
|||||||
initializeDirectories()
|
initializeDirectories()
|
||||||
|
|
||||||
// Initialize hook libraries directory.
|
// Initialize hook libraries directory.
|
||||||
hookLibPath = context.applicationInfo.nativeLibraryDir + "/"
|
hookLibPath = YuzuApplication.appContext.applicationInfo.nativeLibraryDir + "/"
|
||||||
|
|
||||||
// Initialize GPU driver.
|
// Initialize GPU driver.
|
||||||
NativeLibrary.initializeGpuDriver(
|
NativeLibrary.initializeGpuDriver(
|
||||||
hookLibPath,
|
hookLibPath,
|
||||||
driverInstallationPath,
|
driverInstallationPath,
|
||||||
customDriverLibraryName,
|
customDriverData.libraryName,
|
||||||
fileRedirectionPath
|
fileRedirectionPath
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun installDefaultDriver(context: Context) {
|
fun getDrivers(): MutableList<Pair<String, GpuDriverMetadata>> {
|
||||||
// Removing the installed driver will result in the backend using the default system driver.
|
val driverZips = File(driverStoragePath).listFiles()
|
||||||
val driverInstallationDir = File(driverInstallationPath!!)
|
val drivers: MutableList<Pair<String, GpuDriverMetadata>> =
|
||||||
deleteRecursive(driverInstallationDir)
|
driverZips
|
||||||
initializeDriverParameters(context)
|
?.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 installCustomDriver(context: Context, driverPathUri: Uri?) {
|
fun installDefaultDriver() {
|
||||||
|
// Removing the installed driver will result in the backend using the default system driver.
|
||||||
|
File(driverInstallationPath!!).deleteRecursively()
|
||||||
|
initializeDriverParameters()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyDriverToInternalStorage(driverUri: Uri): Boolean {
|
||||||
|
// Ensure we have directories.
|
||||||
|
initializeDirectories()
|
||||||
|
|
||||||
|
// Copy the zip file URI to user data
|
||||||
|
val copiedFile =
|
||||||
|
FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
|
||||||
|
|
||||||
|
// Validate driver
|
||||||
|
val metadata = getMetadataFromZip(copiedFile)
|
||||||
|
if (metadata.name == null) {
|
||||||
|
copiedFile.delete()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata.minApi > Build.VERSION.SDK_INT) {
|
||||||
|
copiedFile.delete()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies driver zip into user data directory so that it can be exported along with
|
||||||
|
* other user data and also unzipped into the installation directory
|
||||||
|
*/
|
||||||
|
fun installCustomDriver(driverUri: Uri): Boolean {
|
||||||
// Revert to system default in the event the specified driver is bad.
|
// Revert to system default in the event the specified driver is bad.
|
||||||
installDefaultDriver(context)
|
installDefaultDriver()
|
||||||
|
|
||||||
// Ensure we have directories.
|
// Ensure we have directories.
|
||||||
initializeDirectories()
|
initializeDirectories()
|
||||||
|
|
||||||
// Copy the zip file URI into our private storage.
|
// Copy the zip file URI to user data
|
||||||
copyUriToInternalStorage(
|
val copiedFile =
|
||||||
context,
|
FileUtil.copyUriToInternalStorage(driverUri, driverStoragePath) ?: return false
|
||||||
driverPathUri,
|
|
||||||
driverInstallationPath!!,
|
// Validate driver
|
||||||
DRIVER_INTERNAL_FILENAME
|
val metadata = getMetadataFromZip(copiedFile)
|
||||||
)
|
if (metadata.name == null) {
|
||||||
|
copiedFile.delete()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata.minApi > Build.VERSION.SDK_INT) {
|
||||||
|
copiedFile.delete()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Unzip the driver.
|
// Unzip the driver.
|
||||||
try {
|
try {
|
||||||
unzip(driverInstallationPath + DRIVER_INTERNAL_FILENAME, driverInstallationPath!!)
|
FileUtil.unzipToInternalStorage(
|
||||||
|
BufferedInputStream(copiedFile.inputStream()),
|
||||||
|
File(driverInstallationPath!!)
|
||||||
|
)
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the driver parameters.
|
// Initialize the driver parameters.
|
||||||
initializeDriverParameters(context)
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
external fun supportsCustomDriverLoading(): Boolean
|
external fun supportsCustomDriverLoading(): Boolean
|
||||||
|
|
||||||
// Parse the custom driver metadata to retrieve the name.
|
// Parse the custom driver metadata to retrieve the name.
|
||||||
val customDriverName: String?
|
val customDriverData: GpuDriverMetadata
|
||||||
get() {
|
get() = GpuDriverMetadata(File(driverInstallationPath + META_JSON_FILENAME))
|
||||||
val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME)
|
|
||||||
return metadata.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the custom driver metadata to retrieve the library name.
|
fun initializeDirectories() {
|
||||||
private val customDriverLibraryName: String?
|
|
||||||
get() {
|
|
||||||
// Parse the custom driver metadata to retrieve the library name.
|
|
||||||
val metadata = GpuDriverMetadata(driverInstallationPath + META_JSON_FILENAME)
|
|
||||||
return metadata.libraryName
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initializeDirectories() {
|
|
||||||
// Ensure the file redirection directory exists.
|
// Ensure the file redirection directory exists.
|
||||||
val fileRedirectionDir = File(fileRedirectionPath!!)
|
val fileRedirectionDir = File(fileRedirectionPath!!)
|
||||||
if (!fileRedirectionDir.exists()) {
|
if (!fileRedirectionDir.exists()) {
|
||||||
@ -141,14 +211,10 @@ object GpuDriverHelper {
|
|||||||
if (!driverInstallationDir.exists()) {
|
if (!driverInstallationDir.exists()) {
|
||||||
driverInstallationDir.mkdirs()
|
driverInstallationDir.mkdirs()
|
||||||
}
|
}
|
||||||
}
|
// Ensure the driver storage directory exists
|
||||||
|
val driverStorageDirectory = File(driverStoragePath)
|
||||||
private fun deleteRecursive(fileOrDirectory: File) {
|
if (!driverStorageDirectory.exists()) {
|
||||||
if (fileOrDirectory.isDirectory) {
|
driverStorageDirectory.mkdirs()
|
||||||
for (child in fileOrDirectory.listFiles()!!) {
|
|
||||||
deleteRecursive(child)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fileOrDirectory.delete()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,29 +4,29 @@
|
|||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
class GpuDriverMetadata(metadataFilePath: String) {
|
class GpuDriverMetadata {
|
||||||
var name: String? = null
|
/**
|
||||||
var description: String? = null
|
* Tries to get driver metadata information from a meta.json [File]
|
||||||
var author: String? = null
|
*
|
||||||
var vendor: String? = null
|
* @param metadataFile meta.json file provided with a GPU driver
|
||||||
var driverVersion: String? = null
|
*/
|
||||||
var minApi = 0
|
constructor(metadataFile: File) {
|
||||||
var libraryName: String? = null
|
if (metadataFile.length() > MAX_META_SIZE_BYTES) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
try {
|
try {
|
||||||
val json = JSONObject(getStringFromFile(metadataFilePath))
|
val json = JSONObject(FileUtil.getStringFromFile(metadataFile))
|
||||||
name = json.getString("name")
|
name = json.getString("name")
|
||||||
description = json.getString("description")
|
description = json.getString("description")
|
||||||
author = json.getString("author")
|
author = json.getString("author")
|
||||||
vendor = json.getString("vendor")
|
vendor = json.getString("vendor")
|
||||||
driverVersion = json.getString("driverVersion")
|
version = json.getString("driverVersion")
|
||||||
minApi = json.getInt("minApi")
|
minApi = json.getInt("minApi")
|
||||||
libraryName = json.getString("libraryName")
|
libraryName = json.getString("libraryName")
|
||||||
} catch (e: JSONException) {
|
} catch (e: JSONException) {
|
||||||
@ -36,12 +36,84 @@ class GpuDriverMetadata(metadataFilePath: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
/**
|
||||||
@Throws(IOException::class)
|
* Tries to get driver metadata information from an input stream that's intended to be
|
||||||
private fun getStringFromFile(filePath: String): String {
|
* from a zip file
|
||||||
val path = Paths.get(filePath)
|
*
|
||||||
val bytes = Files.readAllBytes(path)
|
* @param metadataStream ZipEntry input stream
|
||||||
return String(bytes, StandardCharsets.UTF_8)
|
* @param size Size of the file in bytes
|
||||||
|
*/
|
||||||
|
constructor(metadataStream: InputStream, size: Long) {
|
||||||
|
if (size > MAX_META_SIZE_BYTES) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val json = JSONObject(FileUtil.getStringFromInputStream(metadataStream))
|
||||||
|
name = json.getString("name")
|
||||||
|
description = json.getString("description")
|
||||||
|
author = json.getString("author")
|
||||||
|
vendor = json.getString("vendor")
|
||||||
|
version = json.getString("driverVersion")
|
||||||
|
minApi = json.getInt("minApi")
|
||||||
|
libraryName = json.getString("libraryName")
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
// JSON is malformed, ignore and treat as unsupported metadata.
|
||||||
|
} catch (e: IOException) {
|
||||||
|
// File is inaccessible, ignore and treat as unsupported metadata.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty metadata instance
|
||||||
|
*/
|
||||||
|
constructor()
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is GpuDriverMetadata) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return other.name == name &&
|
||||||
|
other.description == description &&
|
||||||
|
other.author == author &&
|
||||||
|
other.vendor == vendor &&
|
||||||
|
other.version == version &&
|
||||||
|
other.minApi == minApi &&
|
||||||
|
other.libraryName == libraryName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = name?.hashCode() ?: 0
|
||||||
|
result = 31 * result + (description?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (author?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (vendor?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (version?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + minApi
|
||||||
|
result = 31 * result + (libraryName?.hashCode() ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String =
|
||||||
|
"""
|
||||||
|
Name - $name
|
||||||
|
Description - $description
|
||||||
|
Author - $author
|
||||||
|
Vendor - $vendor
|
||||||
|
Version - $version
|
||||||
|
Min API - $minApi
|
||||||
|
Library Name - $libraryName
|
||||||
|
""".trimMargin().trimIndent()
|
||||||
|
|
||||||
|
var name: String? = null
|
||||||
|
var description: String? = null
|
||||||
|
var author: String? = null
|
||||||
|
var vendor: String? = null
|
||||||
|
var version: String? = null
|
||||||
|
var minApi = 0
|
||||||
|
var libraryName: String? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MAX_META_SIZE_BYTES = 500000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
9
src/android/app/src/main/res/drawable/ic_build.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_build.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorControlNormal"
|
||||||
|
android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" />
|
||||||
|
</vector>
|
9
src/android/app/src/main/res/drawable/ic_delete.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_delete.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorControlNormal"
|
||||||
|
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" />
|
||||||
|
</vector>
|
89
src/android/app/src/main/res/layout/card_driver_option.xml
Normal file
89
src/android/app/src/main/res/layout/card_driver_option.xml
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
style="?attr/materialCardViewOutlinedStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginVertical="12dp"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/radio_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:clickable="false"
|
||||||
|
android:checked="false" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_gravity="center_vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
style="@style/TextAppearance.Material3.TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="none"
|
||||||
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
|
android:requiresFadingEdge="horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
tools:text="@string/select_gpu_driver_default" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/version"
|
||||||
|
style="@style/TextAppearance.Material3.BodyMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:ellipsize="none"
|
||||||
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
|
android:requiresFadingEdge="horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
tools:text="@string/install_gpu_driver_description" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/description"
|
||||||
|
style="@style/TextAppearance.Material3.BodyMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:ellipsize="none"
|
||||||
|
android:marqueeRepeatLimit="marquee_forever"
|
||||||
|
android:requiresFadingEdge="horizontal"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
tools:text="@string/install_gpu_driver_description" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_delete"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:contentDescription="@string/delete"
|
||||||
|
android:tooltipText="@string/delete"
|
||||||
|
app:icon="@drawable/ic_delete"
|
||||||
|
app:iconTint="?attr/colorControlNormal" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/coordinator_licenses"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/colorSurface">
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar_drivers"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
app:liftOnScrollTargetViewId="@id/list_drivers">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar_drivers"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:navigationIcon="@drawable/ic_back"
|
||||||
|
app:title="@string/gpu_driver_manager" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/list_drivers"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
android:id="@+id/button_install"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:text="@string/install"
|
||||||
|
app:icon="@drawable/ic_add"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -22,6 +22,9 @@
|
|||||||
<action
|
<action
|
||||||
android:id="@+id/action_homeSettingsFragment_to_installableFragment"
|
android:id="@+id/action_homeSettingsFragment_to_installableFragment"
|
||||||
app:destination="@id/installableFragment" />
|
app:destination="@id/installableFragment" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_homeSettingsFragment_to_driverManagerFragment"
|
||||||
|
app:destination="@id/driverManagerFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
@ -95,5 +98,9 @@
|
|||||||
android:id="@+id/installableFragment"
|
android:id="@+id/installableFragment"
|
||||||
android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
|
android:name="org.yuzu.yuzu_emu.fragments.InstallableFragment"
|
||||||
android:label="InstallableFragment" />
|
android:label="InstallableFragment" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/driverManagerFragment"
|
||||||
|
android:name="org.yuzu.yuzu_emu.fragments.DriverManagerFragment"
|
||||||
|
android:label="DriverManagerFragment" />
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
|
@ -168,9 +168,7 @@
|
|||||||
<string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string>
|
<string name="select_gpu_driver_title">Möchtest du deinen aktuellen GPU-Treiber ersetzen?</string>
|
||||||
<string name="select_gpu_driver_install">Installieren</string>
|
<string name="select_gpu_driver_install">Installieren</string>
|
||||||
<string name="select_gpu_driver_default">Standard</string>
|
<string name="select_gpu_driver_default">Standard</string>
|
||||||
<string name="select_gpu_driver_install_success">%s wurde installiert</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string>
|
<string name="select_gpu_driver_use_default">Standard GPU-Treiber wird verwendet</string>
|
||||||
<string name="select_gpu_driver_error">Ungültiger Treiber ausgewählt, Standard-Treiber wird verwendet!</string>
|
|
||||||
<string name="system_gpu_driver">System GPU-Treiber</string>
|
<string name="system_gpu_driver">System GPU-Treiber</string>
|
||||||
<string name="installing_driver">Treiber wird installiert...</string>
|
<string name="installing_driver">Treiber wird installiert...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string>
|
<string name="select_gpu_driver_title">¿Quiere reemplazar el driver de GPU actual?</string>
|
||||||
<string name="select_gpu_driver_install">Instalar</string>
|
<string name="select_gpu_driver_install">Instalar</string>
|
||||||
<string name="select_gpu_driver_default">Predeterminado</string>
|
<string name="select_gpu_driver_default">Predeterminado</string>
|
||||||
<string name="select_gpu_driver_install_success">Instalado %s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string>
|
<string name="select_gpu_driver_use_default">Usando el driver de GPU por defecto </string>
|
||||||
<string name="select_gpu_driver_error">¡Driver no válido, utilizando el predeterminado del sistema!</string>
|
|
||||||
<string name="system_gpu_driver">Driver GPU del sistema</string>
|
<string name="system_gpu_driver">Driver GPU del sistema</string>
|
||||||
<string name="installing_driver">Instalando driver...</string>
|
<string name="installing_driver">Instalando driver...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string>
|
<string name="select_gpu_driver_title">Souhaitez vous remplacer votre pilote actuel ?</string>
|
||||||
<string name="select_gpu_driver_install">Installer</string>
|
<string name="select_gpu_driver_install">Installer</string>
|
||||||
<string name="select_gpu_driver_default">Défaut</string>
|
<string name="select_gpu_driver_default">Défaut</string>
|
||||||
<string name="select_gpu_driver_install_success">%s Installé</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string>
|
<string name="select_gpu_driver_use_default">Utilisation du pilote de GPU par défaut</string>
|
||||||
<string name="select_gpu_driver_error">Pilote non valide sélectionné, utilisation du paramètre par défaut du système !</string>
|
|
||||||
<string name="system_gpu_driver">Pilote du GPU du système</string>
|
<string name="system_gpu_driver">Pilote du GPU du système</string>
|
||||||
<string name="installing_driver">Installation du pilote...</string>
|
<string name="installing_driver">Installation du pilote...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string>
|
<string name="select_gpu_driver_title">Vuoi sostituire il driver della tua GPU attuale?</string>
|
||||||
<string name="select_gpu_driver_install">Installa</string>
|
<string name="select_gpu_driver_install">Installa</string>
|
||||||
<string name="select_gpu_driver_default">Predefinito</string>
|
<string name="select_gpu_driver_default">Predefinito</string>
|
||||||
<string name="select_gpu_driver_install_success">Installato%s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string>
|
<string name="select_gpu_driver_use_default">Utilizza il driver predefinito della GPU.</string>
|
||||||
<string name="select_gpu_driver_error">Il driver selezionato è invalido, è in utilizzo quello predefinito di sistema!</string>
|
|
||||||
<string name="system_gpu_driver">Driver GPU del sistema</string>
|
<string name="system_gpu_driver">Driver GPU del sistema</string>
|
||||||
<string name="installing_driver">Installando i driver...</string>
|
<string name="installing_driver">Installando i driver...</string>
|
||||||
|
|
||||||
|
@ -170,9 +170,7 @@
|
|||||||
<string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
|
<string name="select_gpu_driver_title">現在のGPUドライバーを置き換えますか?</string>
|
||||||
<string name="select_gpu_driver_install">インストール</string>
|
<string name="select_gpu_driver_install">インストール</string>
|
||||||
<string name="select_gpu_driver_default">デフォルト</string>
|
<string name="select_gpu_driver_default">デフォルト</string>
|
||||||
<string name="select_gpu_driver_install_success">%s をインストールしました</string>
|
|
||||||
<string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string>
|
<string name="select_gpu_driver_use_default">デフォルトのGPUドライバーを使用します</string>
|
||||||
<string name="select_gpu_driver_error">選択されたドライバが無効なため、システムのデフォルトを使用します!</string>
|
|
||||||
<string name="system_gpu_driver">システムのGPUドライバ</string>
|
<string name="system_gpu_driver">システムのGPUドライバ</string>
|
||||||
<string name="installing_driver">インストール中…</string>
|
<string name="installing_driver">インストール中…</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
|
<string name="select_gpu_driver_title">현재 사용 중인 GPU 드라이버를 교체하겠습니까?</string>
|
||||||
<string name="select_gpu_driver_install">설치</string>
|
<string name="select_gpu_driver_install">설치</string>
|
||||||
<string name="select_gpu_driver_default">기본값</string>
|
<string name="select_gpu_driver_default">기본값</string>
|
||||||
<string name="select_gpu_driver_install_success">설치된 %s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string>
|
<string name="select_gpu_driver_use_default">기본 GPU 드라이버 사용</string>
|
||||||
<string name="select_gpu_driver_error">시스템 기본값을 사용하여 잘못된 드라이버를 선택했습니다!</string>
|
|
||||||
<string name="system_gpu_driver">시스템 GPU 드라이버</string>
|
<string name="system_gpu_driver">시스템 GPU 드라이버</string>
|
||||||
<string name="installing_driver">드라이버 설치 중...</string>
|
<string name="installing_driver">드라이버 설치 중...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string>
|
<string name="select_gpu_driver_title">Ønsker du å bytte ut din nåværende GPU-driver?</string>
|
||||||
<string name="select_gpu_driver_install">Installer</string>
|
<string name="select_gpu_driver_install">Installer</string>
|
||||||
<string name="select_gpu_driver_default">Standard</string>
|
<string name="select_gpu_driver_default">Standard</string>
|
||||||
<string name="select_gpu_driver_install_success">Installert %s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string>
|
<string name="select_gpu_driver_use_default">Bruk av standard GPU-driver</string>
|
||||||
<string name="select_gpu_driver_error">Ugyldig driver valgt, bruker systemstandard!</string>
|
|
||||||
<string name="system_gpu_driver">Systemets GPU-driver</string>
|
<string name="system_gpu_driver">Systemets GPU-driver</string>
|
||||||
<string name="installing_driver">Installerer driver...</string>
|
<string name="installing_driver">Installerer driver...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string>
|
<string name="select_gpu_driver_title">Chcesz zastąpić obecny sterownik układu graficznego?</string>
|
||||||
<string name="select_gpu_driver_install">Zainstaluj</string>
|
<string name="select_gpu_driver_install">Zainstaluj</string>
|
||||||
<string name="select_gpu_driver_default">Domyślne</string>
|
<string name="select_gpu_driver_default">Domyślne</string>
|
||||||
<string name="select_gpu_driver_install_success">Zainstalowano %s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string>
|
<string name="select_gpu_driver_use_default">Aktywny domyślny sterownik GPU</string>
|
||||||
<string name="select_gpu_driver_error">Wybrano błędny sterownik, powrót do domyślnego. </string>
|
|
||||||
<string name="system_gpu_driver">Systemowy sterownik GPU</string>
|
<string name="system_gpu_driver">Systemowy sterownik GPU</string>
|
||||||
<string name="installing_driver">Instalowanie sterownika...</string>
|
<string name="installing_driver">Instalowanie sterownika...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
|
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
|
||||||
<string name="select_gpu_driver_install">Instalar</string>
|
<string name="select_gpu_driver_install">Instalar</string>
|
||||||
<string name="select_gpu_driver_default">Padrão</string>
|
<string name="select_gpu_driver_default">Padrão</string>
|
||||||
<string name="select_gpu_driver_install_success">Instalado%s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
|
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
|
||||||
<string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
|
|
||||||
<string name="system_gpu_driver">Driver do GPU padrão</string>
|
<string name="system_gpu_driver">Driver do GPU padrão</string>
|
||||||
<string name="installing_driver">A instalar o Driver...</string>
|
<string name="installing_driver">A instalar o Driver...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
|
<string name="select_gpu_driver_title">Queres substituir o driver do GPU atual? </string>
|
||||||
<string name="select_gpu_driver_install">Instalar</string>
|
<string name="select_gpu_driver_install">Instalar</string>
|
||||||
<string name="select_gpu_driver_default">Padrão</string>
|
<string name="select_gpu_driver_default">Padrão</string>
|
||||||
<string name="select_gpu_driver_install_success">Instalado%s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
|
<string name="select_gpu_driver_use_default">Usar o driver padrão do GPU</string>
|
||||||
<string name="select_gpu_driver_error">Driver selecionado inválido, a usar o padrão do sistema!</string>
|
|
||||||
<string name="system_gpu_driver">Driver do GPU padrão</string>
|
<string name="system_gpu_driver">Driver do GPU padrão</string>
|
||||||
<string name="installing_driver">A instalar o Driver...</string>
|
<string name="installing_driver">A instalar o Driver...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
|
<string name="select_gpu_driver_title">Хотите заменить текущий драйвер ГП?</string>
|
||||||
<string name="select_gpu_driver_install">Установить</string>
|
<string name="select_gpu_driver_install">Установить</string>
|
||||||
<string name="select_gpu_driver_default">По умолчанию</string>
|
<string name="select_gpu_driver_default">По умолчанию</string>
|
||||||
<string name="select_gpu_driver_install_success">Установлено %s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string>
|
<string name="select_gpu_driver_use_default">Используется стандартный драйвер ГП </string>
|
||||||
<string name="select_gpu_driver_error">Выбран неверный драйвер, используется стандартный системный!</string>
|
|
||||||
<string name="system_gpu_driver">Системный драйвер ГП</string>
|
<string name="system_gpu_driver">Системный драйвер ГП</string>
|
||||||
<string name="installing_driver">Установка драйвера...</string>
|
<string name="installing_driver">Установка драйвера...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
|
<string name="select_gpu_driver_title">Хочете замінити поточний драйвер ГП?</string>
|
||||||
<string name="select_gpu_driver_install">Встановити</string>
|
<string name="select_gpu_driver_install">Встановити</string>
|
||||||
<string name="select_gpu_driver_default">За замовчуванням</string>
|
<string name="select_gpu_driver_default">За замовчуванням</string>
|
||||||
<string name="select_gpu_driver_install_success">Встановлено %s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string>
|
<string name="select_gpu_driver_use_default">Використовується стандартний драйвер ГП</string>
|
||||||
<string name="select_gpu_driver_error">Обрано неправильний драйвер, використовується стандартний системний!</string>
|
|
||||||
<string name="system_gpu_driver">Системний драйвер ГП</string>
|
<string name="system_gpu_driver">Системний драйвер ГП</string>
|
||||||
<string name="installing_driver">Встановлення драйвера...</string>
|
<string name="installing_driver">Встановлення драйвера...</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
|
<string name="select_gpu_driver_title">要取代您当前的 GPU 驱动程序吗?</string>
|
||||||
<string name="select_gpu_driver_install">安装</string>
|
<string name="select_gpu_driver_install">安装</string>
|
||||||
<string name="select_gpu_driver_default">系统默认</string>
|
<string name="select_gpu_driver_default">系统默认</string>
|
||||||
<string name="select_gpu_driver_install_success">已安装 %s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string>
|
<string name="select_gpu_driver_use_default">使用默认 GPU 驱动程序</string>
|
||||||
<string name="select_gpu_driver_error">选择的驱动程序无效,将使用系统默认的驱动程序!</string>
|
|
||||||
<string name="system_gpu_driver">系统 GPU 驱动程序</string>
|
<string name="system_gpu_driver">系统 GPU 驱动程序</string>
|
||||||
<string name="installing_driver">正在安装驱动程序…</string>
|
<string name="installing_driver">正在安装驱动程序…</string>
|
||||||
|
|
||||||
|
@ -171,9 +171,7 @@
|
|||||||
<string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
|
<string name="select_gpu_driver_title">要取代您目前的 GPU 驅動程式嗎?</string>
|
||||||
<string name="select_gpu_driver_install">安裝</string>
|
<string name="select_gpu_driver_install">安裝</string>
|
||||||
<string name="select_gpu_driver_default">預設</string>
|
<string name="select_gpu_driver_default">預設</string>
|
||||||
<string name="select_gpu_driver_install_success">已安裝 %s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string>
|
<string name="select_gpu_driver_use_default">使用預設 GPU 驅動程式</string>
|
||||||
<string name="select_gpu_driver_error">選取的驅動程式無效,將使用系統預設驅動程式!</string>
|
|
||||||
<string name="system_gpu_driver">系統 GPU 驅動程式</string>
|
<string name="system_gpu_driver">系統 GPU 驅動程式</string>
|
||||||
<string name="installing_driver">正在安裝驅動程式…</string>
|
<string name="installing_driver">正在安裝驅動程式…</string>
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
<dimen name="menu_width">256dp</dimen>
|
<dimen name="menu_width">256dp</dimen>
|
||||||
<dimen name="card_width">165dp</dimen>
|
<dimen name="card_width">165dp</dimen>
|
||||||
<dimen name="icon_inset">24dp</dimen>
|
<dimen name="icon_inset">24dp</dimen>
|
||||||
|
<dimen name="spacing_bottom_list_fab">72dp</dimen>
|
||||||
|
<dimen name="spacing_fab">24dp</dimen>
|
||||||
|
|
||||||
<dimen name="dialog_margin">20dp</dimen>
|
<dimen name="dialog_margin">20dp</dimen>
|
||||||
<dimen name="elevated_app_bar">3dp</dimen>
|
<dimen name="elevated_app_bar">3dp</dimen>
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
<string name="invalid_keys_error">Invalid encryption keys</string>
|
<string name="invalid_keys_error">Invalid encryption keys</string>
|
||||||
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
<string name="dumping_keys_quickstart_link">https://yuzu-emu.org/help/quickstart/#dumping-decryption-keys</string>
|
||||||
<string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
|
<string name="install_keys_failure_description">The selected file is incorrect or corrupt. Please redump your keys.</string>
|
||||||
|
<string name="gpu_driver_manager">GPU Driver Manager</string>
|
||||||
<string name="install_gpu_driver">Install GPU driver</string>
|
<string name="install_gpu_driver">Install GPU driver</string>
|
||||||
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
<string name="install_gpu_driver_description">Install alternative drivers for potentially better performance or accuracy</string>
|
||||||
<string name="advanced_settings">Advanced settings</string>
|
<string name="advanced_settings">Advanced settings</string>
|
||||||
@ -234,15 +235,17 @@
|
|||||||
<string name="export_failed">Export failed</string>
|
<string name="export_failed">Export failed</string>
|
||||||
<string name="import_failed">Import failed</string>
|
<string name="import_failed">Import failed</string>
|
||||||
<string name="cancelling">Cancelling</string>
|
<string name="cancelling">Cancelling</string>
|
||||||
|
<string name="install">Install</string>
|
||||||
|
<string name="delete">Delete</string>
|
||||||
|
|
||||||
<!-- GPU driver installation -->
|
<!-- GPU driver installation -->
|
||||||
<string name="select_gpu_driver">Select GPU driver</string>
|
<string name="select_gpu_driver">Select GPU driver</string>
|
||||||
<string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string>
|
<string name="select_gpu_driver_title">Would you like to replace your current GPU driver?</string>
|
||||||
<string name="select_gpu_driver_install">Install</string>
|
<string name="select_gpu_driver_install">Install</string>
|
||||||
<string name="select_gpu_driver_default">Default</string>
|
<string name="select_gpu_driver_default">Default</string>
|
||||||
<string name="select_gpu_driver_install_success">Installed %s</string>
|
|
||||||
<string name="select_gpu_driver_use_default">Using default GPU driver</string>
|
<string name="select_gpu_driver_use_default">Using default GPU driver</string>
|
||||||
<string name="select_gpu_driver_error">Invalid driver selected, using system default!</string>
|
<string name="select_gpu_driver_error">Invalid driver selected</string>
|
||||||
|
<string name="driver_already_installed">Driver already installed</string>
|
||||||
<string name="system_gpu_driver">System GPU driver</string>
|
<string name="system_gpu_driver">System GPU driver</string>
|
||||||
<string name="installing_driver">Installing driver…</string>
|
<string name="installing_driver">Installing driver…</string>
|
||||||
|
|
||||||
|
@ -77,6 +77,7 @@ void AudioRenderer::Wait() {
|
|||||||
"{}, got {}",
|
"{}, got {}",
|
||||||
Message::RenderResponse, msg);
|
Message::RenderResponse, msg);
|
||||||
}
|
}
|
||||||
|
PostDSPClearCommandBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioRenderer::Send(Direction dir, u32 message) {
|
void AudioRenderer::Send(Direction dir, u32 message) {
|
||||||
@ -96,6 +97,14 @@ void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u
|
|||||||
command_buffers[session_id].reset_buffer = reset;
|
command_buffers[session_id].reset_buffer = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioRenderer::PostDSPClearCommandBuffer() noexcept {
|
||||||
|
for (auto& buffer : command_buffers) {
|
||||||
|
buffer.buffer = 0;
|
||||||
|
buffer.size = 0;
|
||||||
|
buffer.reset_buffer = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
|
u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
|
||||||
return command_buffers[session_id].remaining_command_count;
|
return command_buffers[session_id].remaining_command_count;
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,8 @@ private:
|
|||||||
*/
|
*/
|
||||||
void CreateSinkStreams();
|
void CreateSinkStreams();
|
||||||
|
|
||||||
|
void PostDSPClearCommandBuffer() noexcept;
|
||||||
|
|
||||||
/// Core system
|
/// Core system
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
/// The output sink the AudioRenderer will send samples to
|
/// The output sink the AudioRenderer will send samples to
|
||||||
|
@ -39,8 +39,12 @@
|
|||||||
#define Crash() exit(1)
|
#define Crash() exit(1)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define LTO_NOINLINE __attribute__((noinline))
|
||||||
|
|
||||||
#else // _MSC_VER
|
#else // _MSC_VER
|
||||||
|
|
||||||
|
#define LTO_NOINLINE
|
||||||
|
|
||||||
// Locale Cross-Compatibility
|
// Locale Cross-Compatibility
|
||||||
#define locale_t _locale_t
|
#define locale_t _locale_t
|
||||||
|
|
||||||
|
@ -211,6 +211,11 @@ struct Elf64_Rela {
|
|||||||
Elf64_Sxword r_addend; /* Addend */
|
Elf64_Sxword r_addend; /* Addend */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* RELR relocation table entry */
|
||||||
|
|
||||||
|
using Elf32_Relr = Elf32_Word;
|
||||||
|
using Elf64_Relr = Elf64_Xword;
|
||||||
|
|
||||||
/* How to extract and insert information held in the r_info field. */
|
/* How to extract and insert information held in the r_info field. */
|
||||||
|
|
||||||
static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
|
static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
|
||||||
@ -328,6 +333,9 @@ constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */
|
|||||||
constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
|
constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
|
||||||
constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
|
constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
|
||||||
constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
|
constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
|
||||||
|
constexpr u32 ElfDtRelrsz = 35; /* Size of RELR relative relocations */
|
||||||
|
constexpr u32 ElfDtRelr = 36; /* Address of RELR relative relocations */
|
||||||
|
constexpr u32 ElfDtRelrent = 37; /* Size of one RELR relative relocation */
|
||||||
|
|
||||||
} // namespace ELF
|
} // namespace ELF
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
@ -373,7 +373,7 @@ struct KernelCore::Impl {
|
|||||||
static inline thread_local u8 host_thread_id = UINT8_MAX;
|
static inline thread_local u8 host_thread_id = UINT8_MAX;
|
||||||
|
|
||||||
/// Sets the host thread ID for the caller.
|
/// Sets the host thread ID for the caller.
|
||||||
u32 SetHostThreadId(std::size_t core_id) {
|
LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) {
|
||||||
// This should only be called during core init.
|
// This should only be called during core init.
|
||||||
ASSERT(host_thread_id == UINT8_MAX);
|
ASSERT(host_thread_id == UINT8_MAX);
|
||||||
|
|
||||||
@ -384,13 +384,13 @@ struct KernelCore::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the host thread ID for the caller
|
/// Gets the host thread ID for the caller
|
||||||
u32 GetHostThreadId() const {
|
LTO_NOINLINE u32 GetHostThreadId() const {
|
||||||
return host_thread_id;
|
return host_thread_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
|
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
|
||||||
KThread* GetHostDummyThread(KThread* existing_thread) {
|
LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) {
|
||||||
const auto initialize{[](KThread* thread) {
|
const auto initialize{[](KThread* thread) LTO_NOINLINE {
|
||||||
ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
|
ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
|
||||||
return thread;
|
return thread;
|
||||||
}};
|
}};
|
||||||
@ -424,11 +424,11 @@ struct KernelCore::Impl {
|
|||||||
|
|
||||||
static inline thread_local bool is_phantom_mode_for_singlecore{false};
|
static inline thread_local bool is_phantom_mode_for_singlecore{false};
|
||||||
|
|
||||||
bool IsPhantomModeForSingleCore() const {
|
LTO_NOINLINE bool IsPhantomModeForSingleCore() const {
|
||||||
return is_phantom_mode_for_singlecore;
|
return is_phantom_mode_for_singlecore;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetIsPhantomModeForSingleCore(bool value) {
|
LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) {
|
||||||
ASSERT(!is_multicore);
|
ASSERT(!is_multicore);
|
||||||
is_phantom_mode_for_singlecore = value;
|
is_phantom_mode_for_singlecore = value;
|
||||||
}
|
}
|
||||||
@ -439,14 +439,14 @@ struct KernelCore::Impl {
|
|||||||
|
|
||||||
static inline thread_local KThread* current_thread{nullptr};
|
static inline thread_local KThread* current_thread{nullptr};
|
||||||
|
|
||||||
KThread* GetCurrentEmuThread() {
|
LTO_NOINLINE KThread* GetCurrentEmuThread() {
|
||||||
if (!current_thread) {
|
if (!current_thread) {
|
||||||
current_thread = GetHostDummyThread(nullptr);
|
current_thread = GetHostDummyThread(nullptr);
|
||||||
}
|
}
|
||||||
return current_thread;
|
return current_thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetCurrentEmuThread(KThread* thread) {
|
LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) {
|
||||||
current_thread = thread;
|
current_thread = thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ namespace Service::Capture {
|
|||||||
|
|
||||||
void LoopProcess(Core::System& system) {
|
void LoopProcess(Core::System& system) {
|
||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
auto album_manager = std::make_shared<AlbumManager>();
|
auto album_manager = std::make_shared<AlbumManager>(system);
|
||||||
|
|
||||||
server_manager->RegisterNamedService(
|
server_manager->RegisterNamedService(
|
||||||
"caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
|
"caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
|
||||||
|
@ -128,9 +128,9 @@ void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
|
|||||||
ctx.WriteBuffer(entries);
|
ctx.WriteBuffer(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 4};
|
||||||
rb.Push(result);
|
rb.Push(result);
|
||||||
rb.Push(entries.size());
|
rb.Push<u64>(entries.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
|
void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
|
||||||
|
@ -8,12 +8,15 @@
|
|||||||
#include "common/fs/file.h"
|
#include "common/fs/file.h"
|
||||||
#include "common/fs/path_util.h"
|
#include "common/fs/path_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "core/hle/service/caps/caps_manager.h"
|
#include "core/hle/service/caps/caps_manager.h"
|
||||||
#include "core/hle/service/caps/caps_result.h"
|
#include "core/hle/service/caps/caps_result.h"
|
||||||
|
#include "core/hle/service/time/time_manager.h"
|
||||||
|
#include "core/hle/service/time/time_zone_content_manager.h"
|
||||||
|
|
||||||
namespace Service::Capture {
|
namespace Service::Capture {
|
||||||
|
|
||||||
AlbumManager::AlbumManager() {}
|
AlbumManager::AlbumManager(Core::System& system_) : system{system_} {}
|
||||||
|
|
||||||
AlbumManager::~AlbumManager() = default;
|
AlbumManager::~AlbumManager() = default;
|
||||||
|
|
||||||
@ -83,6 +86,34 @@ Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, Albu
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
|
Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
|
||||||
|
ContentType contex_type, s64 start_posix_time,
|
||||||
|
s64 end_posix_time, u64 aruid) const {
|
||||||
|
if (!is_mounted) {
|
||||||
|
return ResultIsNotMounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ApplicationAlbumEntry> album_entries;
|
||||||
|
const auto start_date = ConvertToAlbumDateTime(start_posix_time);
|
||||||
|
const auto end_date = ConvertToAlbumDateTime(end_posix_time);
|
||||||
|
const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid);
|
||||||
|
|
||||||
|
if (result.IsError()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& album_entry : album_entries) {
|
||||||
|
ApplicationAlbumFileEntry entry{
|
||||||
|
.entry = album_entry,
|
||||||
|
.datetime = album_entry.datetime,
|
||||||
|
.unknown = {},
|
||||||
|
};
|
||||||
|
out_entries.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
|
||||||
ContentType contex_type, AlbumFileDateTime start_date,
|
ContentType contex_type, AlbumFileDateTime start_date,
|
||||||
AlbumFileDateTime end_date, u64 aruid) const {
|
AlbumFileDateTime end_date, u64 aruid) const {
|
||||||
if (!is_mounted) {
|
if (!is_mounted) {
|
||||||
@ -93,31 +124,25 @@ Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& ou
|
|||||||
if (file_id.type != contex_type) {
|
if (file_id.type != contex_type) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_id.date > start_date) {
|
if (file_id.date > start_date) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_id.date < end_date) {
|
if (file_id.date < end_date) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out_entries.size() >= SdAlbumFileLimit) {
|
if (out_entries.size() >= SdAlbumFileLimit) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto entry_size = Common::FS::GetSize(path);
|
const auto entry_size = Common::FS::GetSize(path);
|
||||||
ApplicationAlbumFileEntry entry{.entry =
|
ApplicationAlbumEntry entry{
|
||||||
{
|
.size = entry_size,
|
||||||
.size = entry_size,
|
.hash{},
|
||||||
.hash{},
|
.datetime = file_id.date,
|
||||||
.datetime = file_id.date,
|
.storage = file_id.storage,
|
||||||
.storage = file_id.storage,
|
.content = contex_type,
|
||||||
.content = contex_type,
|
.unknown = 1,
|
||||||
.unknown = 1,
|
};
|
||||||
},
|
|
||||||
.datetime = file_id.date,
|
|
||||||
.unknown = {}};
|
|
||||||
out_entries.push_back(entry);
|
out_entries.push_back(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,12 +299,12 @@ Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem:
|
|||||||
.application_id = static_cast<u64>(std::stoll(application, 0, 16)),
|
.application_id = static_cast<u64>(std::stoll(application, 0, 16)),
|
||||||
.date =
|
.date =
|
||||||
{
|
{
|
||||||
.year = static_cast<u16>(std::stoi(year)),
|
.year = static_cast<s16>(std::stoi(year)),
|
||||||
.month = static_cast<u8>(std::stoi(month)),
|
.month = static_cast<s8>(std::stoi(month)),
|
||||||
.day = static_cast<u8>(std::stoi(day)),
|
.day = static_cast<s8>(std::stoi(day)),
|
||||||
.hour = static_cast<u8>(std::stoi(hour)),
|
.hour = static_cast<s8>(std::stoi(hour)),
|
||||||
.minute = static_cast<u8>(std::stoi(minute)),
|
.minute = static_cast<s8>(std::stoi(minute)),
|
||||||
.second = static_cast<u8>(std::stoi(second)),
|
.second = static_cast<s8>(std::stoi(second)),
|
||||||
.unique_id = 0,
|
.unique_id = 0,
|
||||||
},
|
},
|
||||||
.storage = AlbumStorage::Sd,
|
.storage = AlbumStorage::Sd,
|
||||||
@ -339,4 +364,23 @@ Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::p
|
|||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
|
||||||
|
Time::TimeZone::CalendarInfo calendar_date{};
|
||||||
|
const auto& time_zone_manager =
|
||||||
|
system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
|
||||||
|
|
||||||
|
time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
|
||||||
|
|
||||||
|
return {
|
||||||
|
.year = calendar_date.time.year,
|
||||||
|
.month = calendar_date.time.month,
|
||||||
|
.day = calendar_date.time.day,
|
||||||
|
.hour = calendar_date.time.hour,
|
||||||
|
.minute = calendar_date.time.minute,
|
||||||
|
.second = calendar_date.time.second,
|
||||||
|
.unique_id = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
@ -37,7 +37,7 @@ namespace Service::Capture {
|
|||||||
|
|
||||||
class AlbumManager {
|
class AlbumManager {
|
||||||
public:
|
public:
|
||||||
explicit AlbumManager();
|
explicit AlbumManager(Core::System& system_);
|
||||||
~AlbumManager();
|
~AlbumManager();
|
||||||
|
|
||||||
Result DeleteAlbumFile(const AlbumFileId& file_id);
|
Result DeleteAlbumFile(const AlbumFileId& file_id);
|
||||||
@ -45,6 +45,9 @@ public:
|
|||||||
Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
|
Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
|
||||||
u8 flags) const;
|
u8 flags) const;
|
||||||
Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
|
Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
|
||||||
|
ContentType contex_type, s64 start_posix_time, s64 end_posix_time,
|
||||||
|
u64 aruid) const;
|
||||||
|
Result GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
|
||||||
ContentType contex_type, AlbumFileDateTime start_date,
|
ContentType contex_type, AlbumFileDateTime start_date,
|
||||||
AlbumFileDateTime end_date, u64 aruid) const;
|
AlbumFileDateTime end_date, u64 aruid) const;
|
||||||
Result GetAutoSavingStorage(bool& out_is_autosaving) const;
|
Result GetAutoSavingStorage(bool& out_is_autosaving) const;
|
||||||
@ -65,8 +68,12 @@ private:
|
|||||||
Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
|
Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
|
||||||
int height, ScreenShotDecoderFlag flag) const;
|
int height, ScreenShotDecoderFlag flag) const;
|
||||||
|
|
||||||
|
AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
|
||||||
|
|
||||||
bool is_mounted{};
|
bool is_mounted{};
|
||||||
std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
|
std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
|
||||||
|
|
||||||
|
Core::System& system;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Capture
|
} // namespace Service::Capture
|
||||||
|
@ -41,13 +41,13 @@ enum class ScreenShotDecoderFlag : u64 {
|
|||||||
|
|
||||||
// This is nn::capsrv::AlbumFileDateTime
|
// This is nn::capsrv::AlbumFileDateTime
|
||||||
struct AlbumFileDateTime {
|
struct AlbumFileDateTime {
|
||||||
u16 year{};
|
s16 year{};
|
||||||
u8 month{};
|
s8 month{};
|
||||||
u8 day{};
|
s8 day{};
|
||||||
u8 hour{};
|
s8 hour{};
|
||||||
u8 minute{};
|
s8 minute{};
|
||||||
u8 second{};
|
s8 second{};
|
||||||
u8 unique_id{};
|
s8 unique_id{};
|
||||||
|
|
||||||
friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
|
friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
|
||||||
friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
|
friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
|
||||||
|
@ -50,22 +50,35 @@ void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
|
|||||||
|
|
||||||
void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
|
void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto pid{rp.Pop<s32>()};
|
struct Parameters {
|
||||||
const auto content_type{rp.PopEnum<ContentType>()};
|
ContentType content_type;
|
||||||
const auto start_posix_time{rp.Pop<s64>()};
|
INSERT_PADDING_BYTES(7);
|
||||||
const auto end_posix_time{rp.Pop<s64>()};
|
s64 start_posix_time;
|
||||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
s64 end_posix_time;
|
||||||
|
u64 applet_resource_user_id;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
|
||||||
|
|
||||||
|
const auto parameters{rp.PopRaw<Parameters>()};
|
||||||
|
|
||||||
LOG_WARNING(Service_Capture,
|
LOG_WARNING(Service_Capture,
|
||||||
"(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
|
"(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, "
|
||||||
"end_posix_time={}, applet_resource_user_id={}",
|
"applet_resource_user_id={}",
|
||||||
pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id);
|
parameters.content_type, parameters.start_posix_time, parameters.end_posix_time,
|
||||||
|
parameters.applet_resource_user_id);
|
||||||
|
|
||||||
// TODO: Translate posix to DateTime
|
Result result = ResultSuccess;
|
||||||
|
|
||||||
|
if (result.IsSuccess()) {
|
||||||
|
result = manager->IsAlbumMounted(AlbumStorage::Sd);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<ApplicationAlbumFileEntry> entries;
|
std::vector<ApplicationAlbumFileEntry> entries;
|
||||||
const Result result =
|
if (result.IsSuccess()) {
|
||||||
manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id);
|
result = manager->GetAlbumFileList(entries, parameters.content_type,
|
||||||
|
parameters.start_posix_time, parameters.end_posix_time,
|
||||||
|
parameters.applet_resource_user_id);
|
||||||
|
}
|
||||||
|
|
||||||
if (!entries.empty()) {
|
if (!entries.empty()) {
|
||||||
ctx.WriteBuffer(entries);
|
ctx.WriteBuffer(entries);
|
||||||
@ -78,19 +91,38 @@ void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestCo
|
|||||||
|
|
||||||
void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
|
void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto pid{rp.Pop<s32>()};
|
struct Parameters {
|
||||||
const auto content_type{rp.PopEnum<ContentType>()};
|
ContentType content_type;
|
||||||
const auto start_date_time{rp.PopRaw<AlbumFileDateTime>()};
|
INSERT_PADDING_BYTES(1);
|
||||||
const auto end_date_time{rp.PopRaw<AlbumFileDateTime>()};
|
AlbumFileDateTime start_date_time;
|
||||||
const auto applet_resource_user_id{rp.Pop<u64>()};
|
AlbumFileDateTime end_date_time;
|
||||||
|
INSERT_PADDING_BYTES(6);
|
||||||
|
u64 applet_resource_user_id;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
|
||||||
|
|
||||||
|
const auto parameters{rp.PopRaw<Parameters>()};
|
||||||
|
|
||||||
LOG_WARNING(Service_Capture,
|
LOG_WARNING(Service_Capture,
|
||||||
"(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid,
|
"(STUBBED) called. content_type={}, start_date={}/{}/{}, "
|
||||||
content_type, applet_resource_user_id);
|
"end_date={}/{}/{}, applet_resource_user_id={}",
|
||||||
|
parameters.content_type, parameters.start_date_time.year,
|
||||||
|
parameters.start_date_time.month, parameters.start_date_time.day,
|
||||||
|
parameters.end_date_time.year, parameters.end_date_time.month,
|
||||||
|
parameters.end_date_time.day, parameters.applet_resource_user_id);
|
||||||
|
|
||||||
std::vector<ApplicationAlbumFileEntry> entries;
|
Result result = ResultSuccess;
|
||||||
const Result result = manager->GetAlbumFileList(entries, content_type, start_date_time,
|
|
||||||
end_date_time, applet_resource_user_id);
|
if (result.IsSuccess()) {
|
||||||
|
result = manager->IsAlbumMounted(AlbumStorage::Sd);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ApplicationAlbumEntry> entries;
|
||||||
|
if (result.IsSuccess()) {
|
||||||
|
result =
|
||||||
|
manager->GetAlbumFileList(entries, parameters.content_type, parameters.start_date_time,
|
||||||
|
parameters.end_date_time, parameters.applet_resource_user_id);
|
||||||
|
}
|
||||||
|
|
||||||
if (!entries.empty()) {
|
if (!entries.empty()) {
|
||||||
ctx.WriteBuffer(entries);
|
ctx.WriteBuffer(entries);
|
||||||
|
@ -156,6 +156,8 @@ public:
|
|||||||
|
|
||||||
bool LoadNRO(std::span<const u8> data) {
|
bool LoadNRO(std::span<const u8> data) {
|
||||||
local_memory.clear();
|
local_memory.clear();
|
||||||
|
|
||||||
|
relocbase = local_memory.size();
|
||||||
local_memory.insert(local_memory.end(), data.begin(), data.end());
|
local_memory.insert(local_memory.end(), data.begin(), data.end());
|
||||||
|
|
||||||
if (FixupRelocations()) {
|
if (FixupRelocations()) {
|
||||||
@ -181,8 +183,8 @@ public:
|
|||||||
// https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
|
// https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
|
||||||
// https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
|
// https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
|
||||||
VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
|
VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
|
||||||
VAddr rela_dyn = 0;
|
VAddr rela_dyn = 0, relr_dyn = 0;
|
||||||
size_t num_rela = 0;
|
size_t num_rela = 0, num_relr = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
|
const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
|
||||||
dynamic_offset += sizeof(Elf64_Dyn);
|
dynamic_offset += sizeof(Elf64_Dyn);
|
||||||
@ -196,6 +198,12 @@ public:
|
|||||||
if (dyn.d_tag == ElfDtRelasz) {
|
if (dyn.d_tag == ElfDtRelasz) {
|
||||||
num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
|
num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
|
||||||
}
|
}
|
||||||
|
if (dyn.d_tag == ElfDtRelr) {
|
||||||
|
relr_dyn = dyn.d_un.d_ptr;
|
||||||
|
}
|
||||||
|
if (dyn.d_tag == ElfDtRelrsz) {
|
||||||
|
num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < num_rela; i++) {
|
for (size_t i = 0; i < num_rela; i++) {
|
||||||
@ -207,6 +215,29 @@ public:
|
|||||||
callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
|
callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VAddr relr_where = 0;
|
||||||
|
for (size_t i = 0; i < num_relr; i++) {
|
||||||
|
const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))};
|
||||||
|
const auto incr{[&](VAddr where) {
|
||||||
|
callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
|
||||||
|
}};
|
||||||
|
|
||||||
|
if ((relr & 1) == 0) {
|
||||||
|
// where pointer
|
||||||
|
relr_where = relocbase + relr;
|
||||||
|
incr(relr_where);
|
||||||
|
relr_where += sizeof(Elf64_Addr);
|
||||||
|
} else {
|
||||||
|
// bitmap
|
||||||
|
for (int bit = 1; bit < 64; bit++) {
|
||||||
|
if ((relr & (1ULL << bit)) != 0) {
|
||||||
|
incr(relr_where + i * sizeof(Elf64_Addr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relr_where += 63 * sizeof(Elf64_Addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,6 +344,7 @@ public:
|
|||||||
Core::Memory::Memory& memory;
|
Core::Memory::Memory& memory;
|
||||||
VAddr top_of_stack;
|
VAddr top_of_stack;
|
||||||
VAddr heap_pointer;
|
VAddr heap_pointer;
|
||||||
|
VAddr relocbase;
|
||||||
};
|
};
|
||||||
|
|
||||||
void DynarmicCallbacks64::CallSVC(u32 swi) {
|
void DynarmicCallbacks64::CallSVC(u32 swi) {
|
||||||
|
@ -544,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
|||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
|
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads;
|
||||||
u64 total_size_bytes = 0;
|
u64 total_size_bytes = 0;
|
||||||
u64 largest_copy = 0;
|
u64 largest_copy = 0;
|
||||||
for (const IntervalSet& intervals : committed_ranges) {
|
for (const IntervalSet& intervals : committed_ranges) {
|
||||||
@ -914,6 +914,11 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
|
|||||||
|
|
||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
|
const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
|
||||||
|
|
||||||
|
if (is_written) {
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
||||||
runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
|
runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
|
||||||
++binding_index;
|
++binding_index;
|
||||||
@ -931,6 +936,11 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
|
|||||||
const u32 size = binding.size;
|
const u32 size = binding.size;
|
||||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||||
|
|
||||||
|
const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0;
|
||||||
|
if (is_written) {
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
const PixelFormat format = binding.format;
|
const PixelFormat format = binding.format;
|
||||||
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
|
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
|
||||||
@ -962,6 +972,8 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
|||||||
const u32 size = binding.size;
|
const u32 size = binding.size;
|
||||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||||
|
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
|
||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
host_bindings.buffers.push_back(&buffer);
|
host_bindings.buffers.push_back(&buffer);
|
||||||
host_bindings.offsets.push_back(offset);
|
host_bindings.offsets.push_back(offset);
|
||||||
@ -1011,6 +1023,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
|
|||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
const bool is_written =
|
const bool is_written =
|
||||||
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
|
((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
|
||||||
|
|
||||||
|
if (is_written) {
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
if constexpr (NEEDS_BIND_STORAGE_INDEX) {
|
||||||
runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
|
runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
|
||||||
++binding_index;
|
++binding_index;
|
||||||
@ -1028,6 +1045,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
|
|||||||
const u32 size = binding.size;
|
const u32 size = binding.size;
|
||||||
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
SynchronizeBuffer(buffer, binding.cpu_addr, size);
|
||||||
|
|
||||||
|
const bool is_written =
|
||||||
|
((channel_state->written_compute_texture_buffers >> index) & 1) != 0;
|
||||||
|
if (is_written) {
|
||||||
|
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
const PixelFormat format = binding.format;
|
const PixelFormat format = binding.format;
|
||||||
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
|
if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
|
||||||
@ -1201,16 +1224,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
|
|||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
|
void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
|
||||||
const u32 written_mask = channel_state->written_storage_buffers[stage];
|
|
||||||
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
|
ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
|
||||||
// Resolve buffer
|
// Resolve buffer
|
||||||
Binding& binding = channel_state->storage_buffers[stage][index];
|
Binding& binding = channel_state->storage_buffers[stage][index];
|
||||||
const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
||||||
binding.buffer_id = buffer_id;
|
binding.buffer_id = buffer_id;
|
||||||
// Mark buffer as written if needed
|
|
||||||
if (((written_mask >> index) & 1) != 0) {
|
|
||||||
MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1219,10 +1237,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
|
|||||||
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
|
ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
|
||||||
Binding& binding = channel_state->texture_buffers[stage][index];
|
Binding& binding = channel_state->texture_buffers[stage][index];
|
||||||
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
||||||
// Mark buffer as written if needed
|
|
||||||
if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) {
|
|
||||||
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1252,7 +1266,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
|
|||||||
.size = size,
|
.size = size,
|
||||||
.buffer_id = buffer_id,
|
.buffer_id = buffer_id,
|
||||||
};
|
};
|
||||||
MarkWrittenBuffer(buffer_id, *cpu_addr, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
@ -1279,10 +1292,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
|
|||||||
// Resolve buffer
|
// Resolve buffer
|
||||||
Binding& binding = channel_state->compute_storage_buffers[index];
|
Binding& binding = channel_state->compute_storage_buffers[index];
|
||||||
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
||||||
// Mark as written if needed
|
|
||||||
if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) {
|
|
||||||
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1291,18 +1300,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
|
|||||||
ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
|
ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
|
||||||
Binding& binding = channel_state->compute_texture_buffers[index];
|
Binding& binding = channel_state->compute_texture_buffers[index];
|
||||||
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
|
||||||
// Mark as written if needed
|
|
||||||
if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) {
|
|
||||||
MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
|
void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
|
||||||
if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) {
|
|
||||||
SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size);
|
|
||||||
}
|
|
||||||
memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
|
memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
|
||||||
|
|
||||||
const IntervalType base_interval{cpu_addr, cpu_addr + size};
|
const IntervalType base_interval{cpu_addr, cpu_addr + size};
|
||||||
|
@ -1048,6 +1048,10 @@ void Image::Scale(bool up_scale) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Image::ScaleUp(bool ignore) {
|
bool Image::ScaleUp(bool ignore) {
|
||||||
|
const auto& resolution = runtime->resolution;
|
||||||
|
if (!resolution.active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (True(flags & ImageFlagBits::Rescaled)) {
|
if (True(flags & ImageFlagBits::Rescaled)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1060,9 +1064,6 @@ bool Image::ScaleUp(bool ignore) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
flags |= ImageFlagBits::Rescaled;
|
flags |= ImageFlagBits::Rescaled;
|
||||||
if (!runtime->resolution.active) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
has_scaled = true;
|
has_scaled = true;
|
||||||
if (ignore) {
|
if (ignore) {
|
||||||
current_texture = upscaled_backup.handle;
|
current_texture = upscaled_backup.handle;
|
||||||
@ -1073,13 +1074,14 @@ bool Image::ScaleUp(bool ignore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Image::ScaleDown(bool ignore) {
|
bool Image::ScaleDown(bool ignore) {
|
||||||
|
const auto& resolution = runtime->resolution;
|
||||||
|
if (!resolution.active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (False(flags & ImageFlagBits::Rescaled)) {
|
if (False(flags & ImageFlagBits::Rescaled)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
flags &= ~ImageFlagBits::Rescaled;
|
flags &= ~ImageFlagBits::Rescaled;
|
||||||
if (!runtime->resolution.active) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (ignore) {
|
if (ignore) {
|
||||||
current_texture = texture.handle;
|
current_texture = texture.handle;
|
||||||
return true;
|
return true;
|
||||||
|
@ -118,6 +118,8 @@ public:
|
|||||||
|
|
||||||
void InsertUploadMemoryBarrier();
|
void InsertUploadMemoryBarrier();
|
||||||
|
|
||||||
|
void TransitionImageLayout(Image& image) {}
|
||||||
|
|
||||||
FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
|
FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
|
||||||
|
|
||||||
bool HasNativeBgr() const noexcept {
|
bool HasNativeBgr() const noexcept {
|
||||||
|
@ -1530,15 +1530,15 @@ bool Image::IsRescaled() const noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Image::ScaleUp(bool ignore) {
|
bool Image::ScaleUp(bool ignore) {
|
||||||
|
const auto& resolution = runtime->resolution;
|
||||||
|
if (!resolution.active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (True(flags & ImageFlagBits::Rescaled)) {
|
if (True(flags & ImageFlagBits::Rescaled)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ASSERT(info.type != ImageType::Linear);
|
ASSERT(info.type != ImageType::Linear);
|
||||||
flags |= ImageFlagBits::Rescaled;
|
flags |= ImageFlagBits::Rescaled;
|
||||||
const auto& resolution = runtime->resolution;
|
|
||||||
if (!resolution.active) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
has_scaled = true;
|
has_scaled = true;
|
||||||
if (!scaled_image) {
|
if (!scaled_image) {
|
||||||
const bool is_2d = info.type == ImageType::e2D;
|
const bool is_2d = info.type == ImageType::e2D;
|
||||||
@ -1567,15 +1567,15 @@ bool Image::ScaleUp(bool ignore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Image::ScaleDown(bool ignore) {
|
bool Image::ScaleDown(bool ignore) {
|
||||||
|
const auto& resolution = runtime->resolution;
|
||||||
|
if (!resolution.active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (False(flags & ImageFlagBits::Rescaled)) {
|
if (False(flags & ImageFlagBits::Rescaled)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ASSERT(info.type != ImageType::Linear);
|
ASSERT(info.type != ImageType::Linear);
|
||||||
flags &= ~ImageFlagBits::Rescaled;
|
flags &= ~ImageFlagBits::Rescaled;
|
||||||
const auto& resolution = runtime->resolution;
|
|
||||||
if (!resolution.active) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
current_image = *original_image;
|
current_image = *original_image;
|
||||||
if (ignore) {
|
if (ignore) {
|
||||||
return true;
|
return true;
|
||||||
@ -2013,4 +2013,32 @@ void TextureCacheRuntime::AccelerateImageUpload(
|
|||||||
ASSERT(false);
|
ASSERT(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextureCacheRuntime::TransitionImageLayout(Image& image) {
|
||||||
|
if (!image.ExchangeInitialization()) {
|
||||||
|
VkImageMemoryBarrier barrier{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.srcAccessMask = VK_ACCESS_NONE,
|
||||||
|
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
|
||||||
|
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||||
|
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||||
|
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||||
|
.image = image.Handle(),
|
||||||
|
.subresourceRange{
|
||||||
|
.aspectMask = image.AspectMask(),
|
||||||
|
.baseMipLevel = 0,
|
||||||
|
.levelCount = VK_REMAINING_MIP_LEVELS,
|
||||||
|
.baseArrayLayer = 0,
|
||||||
|
.layerCount = VK_REMAINING_ARRAY_LAYERS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
scheduler.RequestOutsideRenderPassOperationContext();
|
||||||
|
scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
||||||
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
@ -92,6 +92,8 @@ public:
|
|||||||
|
|
||||||
void InsertUploadMemoryBarrier() {}
|
void InsertUploadMemoryBarrier() {}
|
||||||
|
|
||||||
|
void TransitionImageLayout(Image& image);
|
||||||
|
|
||||||
bool HasBrokenTextureViewFormats() const noexcept {
|
bool HasBrokenTextureViewFormats() const noexcept {
|
||||||
// No known Vulkan driver has broken image views
|
// No known Vulkan driver has broken image views
|
||||||
return false;
|
return false;
|
||||||
|
@ -1016,6 +1016,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
|
|||||||
|
|
||||||
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
|
if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
|
||||||
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
|
LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
|
||||||
|
runtime.TransitionImageLayout(image);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {
|
if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {
|
||||||
|
@ -885,7 +885,7 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
|
|||||||
};
|
};
|
||||||
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
|
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
|
||||||
const Extent3D block =
|
const Extent3D block =
|
||||||
AdjustMipBlockSize(num_tiles, info.block, level, level_info.num_levels);
|
AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
|
||||||
const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2);
|
const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2);
|
||||||
size_t guest_layer_offset = 0;
|
size_t guest_layer_offset = 0;
|
||||||
|
|
||||||
@ -1062,7 +1062,7 @@ boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const I
|
|||||||
const Extent3D level_size = AdjustMipSize(size, level);
|
const Extent3D level_size = AdjustMipSize(size, level);
|
||||||
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
|
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
|
||||||
const Extent3D block =
|
const Extent3D block =
|
||||||
AdjustMipBlockSize(num_tiles, info.block, level, level_info.num_levels);
|
AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
|
||||||
params[level] = SwizzleParameters{
|
params[level] = SwizzleParameters{
|
||||||
.num_tiles = num_tiles,
|
.num_tiles = num_tiles,
|
||||||
.block = block,
|
.block = block,
|
||||||
|
@ -66,9 +66,10 @@ struct Range {
|
|||||||
switch (usage) {
|
switch (usage) {
|
||||||
case MemoryUsage::Upload:
|
case MemoryUsage::Upload:
|
||||||
case MemoryUsage::Stream:
|
case MemoryUsage::Stream:
|
||||||
return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
return VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
||||||
|
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||||
case MemoryUsage::Download:
|
case MemoryUsage::Download:
|
||||||
return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
|
return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
|
||||||
case MemoryUsage::DeviceLocal:
|
case MemoryUsage::DeviceLocal:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -252,8 +253,7 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
|
|||||||
|
|
||||||
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
|
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
|
||||||
const VmaAllocationCreateInfo alloc_ci = {
|
const VmaAllocationCreateInfo alloc_ci = {
|
||||||
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT |
|
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
|
||||||
MemoryUsageVmaFlags(usage),
|
|
||||||
.usage = MemoryUsageVma(usage),
|
.usage = MemoryUsageVma(usage),
|
||||||
.requiredFlags = 0,
|
.requiredFlags = 0,
|
||||||
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
|
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "yuzu/configuration/configure_vibration.h"
|
#include "yuzu/configuration/configure_vibration.h"
|
||||||
#include "yuzu/configuration/input_profiles.h"
|
#include "yuzu/configuration/input_profiles.h"
|
||||||
#include "yuzu/main.h"
|
#include "yuzu/main.h"
|
||||||
|
#include "yuzu/util/controller_navigation.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -132,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
|||||||
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
|
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ui->labelError->setVisible(false);
|
||||||
|
|
||||||
// Setup/load everything prior to setting up connections.
|
// Setup/load everything prior to setting up connections.
|
||||||
// This avoids unintentionally changing the states of elements while loading them in.
|
// This avoids unintentionally changing the states of elements while loading them in.
|
||||||
SetSupportedControllers();
|
SetSupportedControllers();
|
||||||
@ -143,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
|||||||
|
|
||||||
LoadConfiguration();
|
LoadConfiguration();
|
||||||
|
|
||||||
|
controller_navigation = new ControllerNavigation(system.HIDCore(), this);
|
||||||
|
|
||||||
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
|
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
|
||||||
SetExplainText(i);
|
SetExplainText(i);
|
||||||
UpdateControllerIcon(i);
|
UpdateControllerIcon(i);
|
||||||
@ -151,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
|||||||
|
|
||||||
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
|
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
|
// Hide eventual error message about number of controllers
|
||||||
|
ui->labelError->setVisible(false);
|
||||||
for (std::size_t index = 0; index <= i; ++index) {
|
for (std::size_t index = 0; index <= i; ++index) {
|
||||||
connected_controller_checkboxes[index]->setChecked(checked);
|
connected_controller_checkboxes[index]->setChecked(checked);
|
||||||
}
|
}
|
||||||
@ -199,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
|||||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
|
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
|
||||||
&QtControllerSelectorDialog::ApplyConfiguration);
|
&QtControllerSelectorDialog::ApplyConfiguration);
|
||||||
|
|
||||||
|
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
|
||||||
|
[this](Qt::Key key) {
|
||||||
|
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
|
||||||
|
QCoreApplication::postEvent(this, event);
|
||||||
|
});
|
||||||
|
|
||||||
// Enhancement: Check if the parameters have already been met before disconnecting controllers.
|
// Enhancement: Check if the parameters have already been met before disconnecting controllers.
|
||||||
// If all the parameters are met AND only allows a single player,
|
// If all the parameters are met AND only allows a single player,
|
||||||
// stop the constructor here as we do not need to continue.
|
// stop the constructor here as we do not need to continue.
|
||||||
@ -217,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
QtControllerSelectorDialog::~QtControllerSelectorDialog() {
|
QtControllerSelectorDialog::~QtControllerSelectorDialog() {
|
||||||
|
controller_navigation->UnloadController();
|
||||||
system.HIDCore().DisableAllControllerConfiguration();
|
system.HIDCore().DisableAllControllerConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
|
|||||||
dialog.exec();
|
dialog.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
|
||||||
|
const auto num_connected_players = static_cast<int>(
|
||||||
|
std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
|
||||||
|
[](const QGroupBox* player) { return player->isChecked(); }));
|
||||||
|
|
||||||
|
const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
|
||||||
|
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
|
||||||
|
|
||||||
|
if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
|
||||||
|
// Display error message when trying to validate using "Enter" and "OK" button is disabled
|
||||||
|
ui->labelError->setVisible(true);
|
||||||
|
return;
|
||||||
|
} else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
|
||||||
|
// Remove a player if possible
|
||||||
|
connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
|
||||||
|
return;
|
||||||
|
} else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
|
||||||
|
// Add a player, if possible
|
||||||
|
ui->labelError->setVisible(false);
|
||||||
|
connected_controller_checkboxes[num_connected_players]->setChecked(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QDialog::keyPressEvent(evt);
|
||||||
|
}
|
||||||
|
|
||||||
bool QtControllerSelectorDialog::CheckIfParametersMet() {
|
bool QtControllerSelectorDialog::CheckIfParametersMet() {
|
||||||
// Here, we check and validate the current configuration against all applicable parameters.
|
// Here, we check and validate the current configuration against all applicable parameters.
|
||||||
const auto num_connected_players = static_cast<int>(
|
const auto num_connected_players = static_cast<int>(
|
||||||
|
@ -34,6 +34,8 @@ class HIDCore;
|
|||||||
enum class NpadStyleIndex : u8;
|
enum class NpadStyleIndex : u8;
|
||||||
} // namespace Core::HID
|
} // namespace Core::HID
|
||||||
|
|
||||||
|
class ControllerNavigation;
|
||||||
|
|
||||||
class QtControllerSelectorDialog final : public QDialog {
|
class QtControllerSelectorDialog final : public QDialog {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@ -46,6 +48,8 @@ public:
|
|||||||
|
|
||||||
int exec() override;
|
int exec() override;
|
||||||
|
|
||||||
|
void keyPressEvent(QKeyEvent* evt) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Applies the current configuration.
|
// Applies the current configuration.
|
||||||
void ApplyConfiguration();
|
void ApplyConfiguration();
|
||||||
@ -110,6 +114,8 @@ private:
|
|||||||
|
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
|
|
||||||
|
ControllerNavigation* controller_navigation = nullptr;
|
||||||
|
|
||||||
// This is true if and only if all parameters are met. Otherwise, this is false.
|
// This is true if and only if all parameters are met. Otherwise, this is false.
|
||||||
// This determines whether the "OK" button can be clicked to exit the applet.
|
// This determines whether the "OK" button can be clicked to exit the applet.
|
||||||
bool parameters_met{false};
|
bool parameters_met{false};
|
||||||
|
@ -2624,13 +2624,53 @@
|
|||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item alignment="Qt::AlignBottom">
|
<item alignment="Qt::AlignBottom">
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QWidget" name="closeButtons" native="true">
|
||||||
<property name="enabled">
|
<layout class="QVBoxLayout" name="verticalLayout_46">
|
||||||
<bool>true</bool>
|
<property name="spacing">
|
||||||
</property>
|
<number>7</number>
|
||||||
<property name="standardButtons">
|
</property>
|
||||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
<property name="leftMargin">
|
||||||
</property>
|
<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>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -63,25 +63,15 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
|
|||||||
};
|
};
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
QImage source_image = image.convertToFormat(QImage::Format_RGB32);
|
const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
|
||||||
|
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
|
||||||
constexpr int bytes_per_pixel = 4;
|
constexpr int bytes_per_pixel = 4;
|
||||||
const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
|
|
||||||
|
|
||||||
BITMAPINFOHEADER info_header{};
|
const IconDir icon_dir{
|
||||||
info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
|
.id_reserved = 0,
|
||||||
info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
|
.id_type = 1,
|
||||||
info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB;
|
.id_count = static_cast<WORD>(scale_sizes.size()),
|
||||||
|
};
|
||||||
const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
|
|
||||||
const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
|
|
||||||
.height = static_cast<BYTE>(source_image.height() * 2),
|
|
||||||
.color_count = 0,
|
|
||||||
.reserved = 0,
|
|
||||||
.planes = 1,
|
|
||||||
.bit_count = bytes_per_pixel * 8,
|
|
||||||
.bytes_in_res =
|
|
||||||
static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
|
|
||||||
.image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
|
|
||||||
|
|
||||||
Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
|
Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
|
||||||
Common::FS::FileType::BinaryFile);
|
Common::FS::FileType::BinaryFile);
|
||||||
@ -92,20 +82,55 @@ bool SaveIconToFile(const std::string_view path, const QImage& image) {
|
|||||||
if (!icon_file.Write(icon_dir)) {
|
if (!icon_file.Write(icon_dir)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!icon_file.Write(icon_entry)) {
|
|
||||||
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++) {
|
||||||
if (!icon_file.Write(info_header)) {
|
const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < image.height(); y++) {
|
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
|
||||||
const auto* line = source_image.scanLine(source_image.height() - 1 - y);
|
const QImage scaled_image = source_image.scaled(
|
||||||
std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
|
scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
std::memcpy(line_data.data(), line, line_data.size());
|
const BITMAPINFOHEADER info_header{
|
||||||
if (!icon_file.Write(line_data)) {
|
.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)) {
|
||||||
return false;
|
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();
|
icon_file.Close();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user