Compare commits
85 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1586f1c0b1 | ||
|
|
e3122c5b46 | ||
|
|
7eb7d56b1b | ||
|
|
8cb6b33809 | ||
|
|
8ad64bc253 | ||
|
|
eea2145698 | ||
|
|
e684515578 | ||
|
|
ae1a8a7dc7 | ||
|
|
fd5d7947f6 | ||
|
|
a67bdeb2c2 | ||
|
|
f1e12e3b08 | ||
|
|
93061d1ea1 | ||
|
|
6d12e7320b | ||
|
|
78ff2862f6 | ||
|
|
197e13d93d | ||
|
|
bedb5135c0 | ||
|
|
256c7ec0a7 | ||
|
|
e5f1b22e16 | ||
|
|
b0beca52a3 | ||
|
|
711190bb67 | ||
|
|
b9a86b040b | ||
|
|
346c253cd2 | ||
|
|
ce191ba32b | ||
|
|
23371fa187 | ||
|
|
af7f3f078c | ||
|
|
66b8042b59 | ||
|
|
8acf728d5d | ||
|
|
6e293be20b | ||
|
|
20db91f0fc | ||
|
|
8a526b2c26 | ||
|
|
565a1226d7 | ||
|
|
fd0ef5411c | ||
|
|
c0fd793ef6 | ||
|
|
27a36cd51b | ||
|
|
e681f5678c | ||
|
|
e34e1b1c95 | ||
|
|
76a676883a | ||
|
|
3e47ebe2e9 | ||
|
|
c309a1c69b | ||
|
|
ec423c6919 | ||
|
|
24e1e4dcee | ||
|
|
975122f4bb | ||
|
|
a1adcc31d3 | ||
|
|
9f92104f3e | ||
|
|
330358cd16 | ||
|
|
fc6a2fe779 | ||
|
|
13a4de647d | ||
|
|
3ac2c74e85 | ||
|
|
5aca03d0ff | ||
|
|
4112031c81 | ||
|
|
c89be0dfab | ||
|
|
b77a247e8c | ||
|
|
2dc0ff79ec | ||
|
|
c7fc5b9348 | ||
|
|
41295ff8fe | ||
|
|
9dd52019a9 | ||
|
|
d0be850f25 | ||
|
|
d85129aa17 | ||
|
|
a29fa119e0 | ||
|
|
0c90a0926f | ||
|
|
a3b7b5b22a | ||
|
|
745d16132b | ||
|
|
3e8cd91d54 | ||
|
|
42c944b250 | ||
|
|
2c01669046 | ||
|
|
61b4588517 | ||
|
|
b1b13ddc6b | ||
|
|
8d6aefdcc4 | ||
|
|
0de6b9e3f5 | ||
|
|
a9e4dddad5 | ||
|
|
44f616edb9 | ||
|
|
b9bba3ac89 | ||
|
|
0eacf547c0 | ||
|
|
0c04e27df3 | ||
|
|
3e6d81a008 | ||
|
|
2e1e725443 | ||
|
|
907507886d | ||
|
|
9dcc7bde8b | ||
|
|
8e56a84566 | ||
|
|
bbd502f67a | ||
|
|
1492a65454 | ||
|
|
dd12dd4c67 | ||
|
|
013c34cb32 | ||
|
|
f9fc996083 | ||
|
|
fc0c4db20d |
3
externals/CMakeLists.txt
vendored
3
externals/CMakeLists.txt
vendored
@@ -63,8 +63,9 @@ if (YUZU_USE_EXTERNAL_SDL2)
|
||||
# Yuzu itself needs: Atomic Audio Events Joystick Haptic Sensor Threads Timers
|
||||
# Since 2.0.18 Atomic+Threads required for HIDAPI/libusb (see https://github.com/libsdl-org/SDL/issues/5095)
|
||||
# Yuzu-cmd also needs: Video (depends on Loadso/Dlopen)
|
||||
# CPUinfo also required for SDL Audio, at least until 2.28.0 (see https://github.com/libsdl-org/SDL/issues/7809)
|
||||
set(SDL_UNUSED_SUBSYSTEMS
|
||||
CPUinfo File Filesystem
|
||||
File Filesystem
|
||||
Locale Power Render)
|
||||
foreach(_SUB ${SDL_UNUSED_SUBSYSTEMS})
|
||||
string(TOUPPER ${_SUB} _OPT)
|
||||
|
||||
4
externals/nx_tzdb/CMakeLists.txt
vendored
4
externals/nx_tzdb/CMakeLists.txt
vendored
@@ -7,7 +7,7 @@ add_library(nx_tzdb INTERFACE)
|
||||
|
||||
find_program(GIT git)
|
||||
find_program(GNU_MAKE make)
|
||||
find_program(GNU_DATE date)
|
||||
find_program(DATE_PROG date)
|
||||
|
||||
set(CAN_BUILD_NX_TZDB true)
|
||||
|
||||
@@ -17,7 +17,7 @@ endif()
|
||||
if (NOT GNU_MAKE)
|
||||
set(CAN_BUILD_NX_TZDB false)
|
||||
endif()
|
||||
if (NOT GNU_DATE)
|
||||
if (NOT DATE_PROG)
|
||||
set(CAN_BUILD_NX_TZDB false)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
|
||||
|
||||
2
externals/nx_tzdb/tzdb_to_nx
vendored
2
externals/nx_tzdb/tzdb_to_nx
vendored
Submodule externals/nx_tzdb/tzdb_to_nx updated: 34df65eff2...8c272f21d1
2
externals/vcpkg
vendored
2
externals/vcpkg
vendored
Submodule externals/vcpkg updated: a487471068...cbf56573a9
@@ -2,7 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import kotlin.collections.setOf
|
||||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
@@ -10,6 +12,7 @@ plugins {
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "1.8.21"
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("org.jlleitschuh.gradle.ktlint") version "11.4.0"
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,16 +47,6 @@ android {
|
||||
jniLibs.useLegacyPackaging = true
|
||||
}
|
||||
|
||||
lint {
|
||||
// This is important as it will run lint but not abort on error
|
||||
// Lint has some overly obnoxious "errors" that should really be warnings
|
||||
abortOnError = false
|
||||
|
||||
//Uncomment disable lines for test builds...
|
||||
//disable 'MissingTranslation'bin
|
||||
//disable 'ExtraTranslation'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO If this is ever modified, change application_id in strings.xml
|
||||
applicationId = "org.yuzu.yuzu_emu"
|
||||
@@ -167,6 +160,24 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
|
||||
|
||||
ktlint {
|
||||
version.set("0.47.1")
|
||||
android.set(true)
|
||||
ignoreFailures.set(false)
|
||||
disabledRules.set(
|
||||
setOf(
|
||||
"no-wildcard-imports",
|
||||
"package-name",
|
||||
"import-ordering"
|
||||
)
|
||||
)
|
||||
reporters {
|
||||
reporter(ReporterType.CHECKSTYLE)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.10.1")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
|
||||
@@ -53,6 +53,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<activity
|
||||
android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
|
||||
android:theme="@style/Theme.Yuzu.Main"
|
||||
android:screenOrientation="userLandscape"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:exported="true">
|
||||
|
||||
@@ -14,18 +14,18 @@ import android.widget.TextView
|
||||
import androidx.annotation.Keep
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.exists
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri
|
||||
import org.yuzu.yuzu_emu.utils.Log.error
|
||||
import org.yuzu.yuzu_emu.utils.Log.verbose
|
||||
import org.yuzu.yuzu_emu.utils.Log.warning
|
||||
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Class which contains methods that interact
|
||||
@@ -76,7 +76,9 @@ object NativeLibrary {
|
||||
fun openContentUri(path: String?, openmode: String?): Int {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.openContentUri(path, openmode)
|
||||
} else openContentUri(appContext, path, openmode)
|
||||
} else {
|
||||
openContentUri(appContext, path, openmode)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@@ -84,7 +86,9 @@ object NativeLibrary {
|
||||
fun getSize(path: String?): Long {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.getFileSize(path)
|
||||
} else getFileSize(appContext, path)
|
||||
} else {
|
||||
getFileSize(appContext, path)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@@ -92,7 +96,9 @@ object NativeLibrary {
|
||||
fun exists(path: String?): Boolean {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.exists(path)
|
||||
} else exists(appContext, path)
|
||||
} else {
|
||||
exists(appContext, path)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@@ -100,7 +106,9 @@ object NativeLibrary {
|
||||
fun isDirectory(path: String?): Boolean {
|
||||
return if (isNativePath(path!!)) {
|
||||
YuzuApplication.documentsTree!!.isDirectory(path)
|
||||
} else isDirectory(appContext, path)
|
||||
} else {
|
||||
isDirectory(appContext, path)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -454,7 +462,9 @@ object NativeLibrary {
|
||||
Html.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() }
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
emulationActivity.finish()
|
||||
}
|
||||
.setOnDismissListener { emulationActivity.finish() }
|
||||
emulationActivity.runOnUiThread {
|
||||
val alert = builder.create()
|
||||
|
||||
@@ -7,12 +7,12 @@ import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.DocumentsTree
|
||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||
import java.io.File
|
||||
|
||||
fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir
|
||||
fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
|
||||
|
||||
class YuzuApplication : Application() {
|
||||
private fun createNotificationChannels() {
|
||||
@@ -21,7 +21,9 @@ class YuzuApplication : Application() {
|
||||
getString(R.string.emulation_notification_channel_name),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
emulationChannel.description = getString(R.string.emulation_notification_channel_description)
|
||||
emulationChannel.description = getString(
|
||||
R.string.emulation_notification_channel_description
|
||||
)
|
||||
emulationChannel.setSound(null, null)
|
||||
emulationChannel.vibrationPattern = null
|
||||
|
||||
@@ -48,7 +50,7 @@ class YuzuApplication : Application() {
|
||||
GpuDriverHelper.initializeDriverParameters(applicationContext)
|
||||
NativeLibrary.logDeviceInfo()
|
||||
|
||||
createNotificationChannels();
|
||||
createNotificationChannels()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
|
||||
@@ -45,7 +46,6 @@ import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||
import org.yuzu.yuzu_emu.utils.NfcReader
|
||||
import org.yuzu.yuzu_emu.utils.ThemeHelper
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
private lateinit var binding: ActivityEmulationBinding
|
||||
@@ -256,7 +256,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
}
|
||||
}
|
||||
|
||||
private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): PictureInPictureParams.Builder {
|
||||
private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder():
|
||||
PictureInPictureParams.Builder {
|
||||
val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) {
|
||||
0 -> Rational(16, 9)
|
||||
1 -> Rational(4, 3)
|
||||
@@ -267,7 +268,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
return this.apply { aspectRatio?.let { setAspectRatio(it) } }
|
||||
}
|
||||
|
||||
private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder(): PictureInPictureParams.Builder {
|
||||
private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder():
|
||||
PictureInPictureParams.Builder {
|
||||
val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf()
|
||||
val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
|
||||
@@ -310,7 +312,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
|
||||
.getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
pictureInPictureParamsBuilder.setAutoEnterEnabled(BooleanSetting.PICTURE_IN_PICTURE.boolean)
|
||||
pictureInPictureParamsBuilder.setAutoEnterEnabled(
|
||||
BooleanSetting.PICTURE_IN_PICTURE.boolean
|
||||
)
|
||||
}
|
||||
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
|
||||
}
|
||||
@@ -341,7 +345,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
} else {
|
||||
try {
|
||||
unregisterReceiver(pictureInPictureReceiver)
|
||||
} catch (ignored : Exception) {
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,9 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder
|
||||
import org.yuzu.yuzu_emu.databinding.CardGameBinding
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
|
||||
class GameAdapter(private val activity: AppCompatActivity) :
|
||||
@@ -60,7 +59,10 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||
override fun onClick(view: View) {
|
||||
val holder = view.tag as GameViewHolder
|
||||
|
||||
val gameExists = DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(holder.game.path))?.exists() == true
|
||||
val gameExists = DocumentFile.fromSingleUri(
|
||||
YuzuApplication.appContext,
|
||||
Uri.parse(holder.game.path)
|
||||
)?.exists() == true
|
||||
if (!gameExists) {
|
||||
Toast.makeText(
|
||||
YuzuApplication.appContext,
|
||||
|
||||
@@ -58,11 +58,12 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L
|
||||
)
|
||||
|
||||
when (option.titleId) {
|
||||
R.string.get_early_access -> binding.optionLayout.background =
|
||||
ContextCompat.getDrawable(
|
||||
binding.optionCard.context,
|
||||
R.drawable.premium_background
|
||||
)
|
||||
R.string.get_early_access ->
|
||||
binding.optionLayout.background =
|
||||
ContextCompat.getDrawable(
|
||||
binding.optionCard.context,
|
||||
R.drawable.premium_background
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ import android.view.WindowInsets
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.view.ViewCompat
|
||||
import java.io.Serializable
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment
|
||||
import java.io.Serializable
|
||||
|
||||
@Keep
|
||||
object SoftwareKeyboard {
|
||||
@@ -40,19 +40,22 @@ object SoftwareKeyboard {
|
||||
// There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result.
|
||||
val handler = Handler(Looper.myLooper()!!)
|
||||
val delayMs = 500
|
||||
handler.postDelayed(object : Runnable {
|
||||
override fun run() {
|
||||
val insets = ViewCompat.getRootWindowInsets(overlayView)
|
||||
val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
|
||||
if (isKeyboardVisible) {
|
||||
handler.postDelayed(this, delayMs.toLong())
|
||||
return
|
||||
}
|
||||
handler.postDelayed(
|
||||
object : Runnable {
|
||||
override fun run() {
|
||||
val insets = ViewCompat.getRootWindowInsets(overlayView)
|
||||
val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime())
|
||||
if (isKeyboardVisible) {
|
||||
handler.postDelayed(this, delayMs.toLong())
|
||||
return
|
||||
}
|
||||
|
||||
// No longer visible, submit the result.
|
||||
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
|
||||
}
|
||||
}, delayMs.toLong())
|
||||
// No longer visible, submit the result.
|
||||
NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER)
|
||||
}
|
||||
},
|
||||
delayMs.toLong()
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -20,7 +20,10 @@ object DiskShaderCacheProgress {
|
||||
emulationActivity.getString(R.string.loading),
|
||||
emulationActivity.getString(R.string.preparing_shaders)
|
||||
)
|
||||
fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG)
|
||||
fragment.show(
|
||||
emulationActivity.supportFragmentManager,
|
||||
ShaderProgressDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
synchronized(finishLock) { finishLock.wait() }
|
||||
}
|
||||
|
||||
@@ -62,7 +62,9 @@ class ShaderProgressDialogFragment : DialogFragment() {
|
||||
shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg ->
|
||||
alertDialog.setMessage(msg)
|
||||
}
|
||||
synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() }
|
||||
synchronized(DiskShaderCacheProgress.finishLock) {
|
||||
DiskShaderCacheProgress.finishLock.notifyAll()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -13,11 +13,11 @@ import android.os.ParcelFileDescriptor
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.DocumentsProvider
|
||||
import android.webkit.MimeTypeMap
|
||||
import java.io.*
|
||||
import org.yuzu.yuzu_emu.BuildConfig
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.getPublicFilesDir
|
||||
import java.io.*
|
||||
|
||||
class DocumentProvider : DocumentsProvider() {
|
||||
private val baseDirectory: File
|
||||
@@ -44,7 +44,7 @@ class DocumentProvider : DocumentsProvider() {
|
||||
DocumentsContract.Document.COLUMN_SIZE
|
||||
)
|
||||
|
||||
const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".user"
|
||||
const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".user"
|
||||
const val ROOT_ID: String = "root"
|
||||
}
|
||||
|
||||
@@ -58,7 +58,11 @@ class DocumentProvider : DocumentsProvider() {
|
||||
private fun getFile(documentId: String): File {
|
||||
if (documentId.startsWith(ROOT_ID)) {
|
||||
val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1))
|
||||
if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found")
|
||||
if (!file.exists()) {
|
||||
throw FileNotFoundException(
|
||||
"${file.absolutePath} ($documentId) not found"
|
||||
)
|
||||
}
|
||||
return file
|
||||
} else {
|
||||
throw FileNotFoundException("'$documentId' is not in any known root")
|
||||
@@ -80,7 +84,8 @@ class DocumentProvider : DocumentsProvider() {
|
||||
add(DocumentsContract.Root.COLUMN_SUMMARY, null)
|
||||
add(
|
||||
DocumentsContract.Root.COLUMN_FLAGS,
|
||||
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
|
||||
DocumentsContract.Root.FLAG_SUPPORTS_CREATE or
|
||||
DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD
|
||||
)
|
||||
add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name))
|
||||
add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory))
|
||||
@@ -127,11 +132,13 @@ class DocumentProvider : DocumentsProvider() {
|
||||
|
||||
try {
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) {
|
||||
if (!newFile.mkdir())
|
||||
if (!newFile.mkdir()) {
|
||||
throw IOException("Failed to create directory")
|
||||
}
|
||||
} else {
|
||||
if (!newFile.createNewFile())
|
||||
if (!newFile.createNewFile()) {
|
||||
throw IOException("Failed to create file")
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}")
|
||||
@@ -142,8 +149,9 @@ class DocumentProvider : DocumentsProvider() {
|
||||
|
||||
override fun deleteDocument(documentId: String?) {
|
||||
val file = getFile(documentId!!)
|
||||
if (!file.delete())
|
||||
if (!file.delete()) {
|
||||
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeDocument(documentId: String, parentDocumentId: String?) {
|
||||
@@ -151,38 +159,55 @@ class DocumentProvider : DocumentsProvider() {
|
||||
val file = getFile(documentId)
|
||||
|
||||
if (parent == file || file.parentFile == null || file.parentFile!! == parent) {
|
||||
if (!file.delete())
|
||||
if (!file.delete()) {
|
||||
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
|
||||
}
|
||||
} else {
|
||||
throw FileNotFoundException("Couldn't delete document with ID '$documentId'")
|
||||
}
|
||||
}
|
||||
|
||||
override fun renameDocument(documentId: String?, displayName: String?): String {
|
||||
if (displayName == null)
|
||||
throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null")
|
||||
if (displayName == null) {
|
||||
throw FileNotFoundException(
|
||||
"Couldn't rename document '$documentId' as the new name is null"
|
||||
)
|
||||
}
|
||||
|
||||
val sourceFile = getFile(documentId!!)
|
||||
val sourceParentFile = sourceFile.parentFile
|
||||
?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent")
|
||||
?: throw FileNotFoundException(
|
||||
"Couldn't rename document '$documentId' as it has no parent"
|
||||
)
|
||||
val destFile = sourceParentFile.resolve(displayName)
|
||||
|
||||
try {
|
||||
if (!sourceFile.renameTo(destFile))
|
||||
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'")
|
||||
if (!sourceFile.renameTo(destFile)) {
|
||||
throw FileNotFoundException(
|
||||
"Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'"
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}")
|
||||
throw FileNotFoundException(
|
||||
"Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': " +
|
||||
"${e.message}"
|
||||
)
|
||||
}
|
||||
|
||||
return getDocumentId(destFile)
|
||||
}
|
||||
|
||||
private fun copyDocument(
|
||||
sourceDocumentId: String, sourceParentDocumentId: String,
|
||||
sourceDocumentId: String,
|
||||
sourceParentDocumentId: String,
|
||||
targetParentDocumentId: String?
|
||||
): String {
|
||||
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId))
|
||||
throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'")
|
||||
if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) {
|
||||
throw FileNotFoundException(
|
||||
"Couldn't copy document '$sourceDocumentId' as its parent is not " +
|
||||
"'$sourceParentDocumentId'"
|
||||
)
|
||||
}
|
||||
|
||||
return copyDocument(sourceDocumentId, targetParentDocumentId)
|
||||
}
|
||||
@@ -193,8 +218,13 @@ class DocumentProvider : DocumentsProvider() {
|
||||
val newFile = parent.resolveWithoutConflict(oldFile.name)
|
||||
|
||||
try {
|
||||
if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true)))
|
||||
if (!(
|
||||
newFile.createNewFile() && newFile.setWritable(true) &&
|
||||
newFile.setReadable(true)
|
||||
)
|
||||
) {
|
||||
throw IOException("Couldn't create new file")
|
||||
}
|
||||
|
||||
FileInputStream(oldFile).use { inStream ->
|
||||
FileOutputStream(newFile).use { outStream ->
|
||||
@@ -209,12 +239,14 @@ class DocumentProvider : DocumentsProvider() {
|
||||
}
|
||||
|
||||
override fun moveDocument(
|
||||
sourceDocumentId: String, sourceParentDocumentId: String?,
|
||||
sourceDocumentId: String,
|
||||
sourceParentDocumentId: String?,
|
||||
targetParentDocumentId: String?
|
||||
): String {
|
||||
try {
|
||||
val newDocumentId = copyDocument(
|
||||
sourceDocumentId, sourceParentDocumentId!!,
|
||||
sourceDocumentId,
|
||||
sourceParentDocumentId!!,
|
||||
targetParentDocumentId
|
||||
)
|
||||
removeDocument(sourceDocumentId, sourceParentDocumentId)
|
||||
@@ -245,24 +277,30 @@ class DocumentProvider : DocumentsProvider() {
|
||||
add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId)
|
||||
add(
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
if (localFile == baseDirectory) context!!.getString(R.string.app_name) else localFile.name
|
||||
if (localFile == baseDirectory) {
|
||||
context!!.getString(R.string.app_name)
|
||||
} else {
|
||||
localFile.name
|
||||
}
|
||||
)
|
||||
add(DocumentsContract.Document.COLUMN_SIZE, localFile.length())
|
||||
add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile))
|
||||
add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified())
|
||||
add(DocumentsContract.Document.COLUMN_FLAGS, flags)
|
||||
if (localFile == baseDirectory)
|
||||
if (localFile == baseDirectory) {
|
||||
add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu)
|
||||
}
|
||||
}
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
private fun getTypeForFile(file: File): Any {
|
||||
return if (file.isDirectory)
|
||||
return if (file.isDirectory) {
|
||||
DocumentsContract.Document.MIME_TYPE_DIR
|
||||
else
|
||||
} else {
|
||||
getTypeForName(file.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTypeForName(name: String): Any {
|
||||
@@ -270,8 +308,9 @@ class DocumentProvider : DocumentsProvider() {
|
||||
if (lastDot >= 0) {
|
||||
val extension = name.substring(lastDot + 1)
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
if (mime != null)
|
||||
if (mime != null) {
|
||||
return mime
|
||||
}
|
||||
}
|
||||
return "application/octect-stream"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ enum class BooleanSetting(
|
||||
override val section: String,
|
||||
override val defaultValue: Boolean
|
||||
) : AbstractBooleanSetting {
|
||||
CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false),
|
||||
FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true),
|
||||
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true),
|
||||
PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true),
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false);
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
package org.yuzu.yuzu_emu.features.settings.model
|
||||
|
||||
import android.text.TextUtils
|
||||
import java.util.*
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import java.util.*
|
||||
|
||||
class Settings {
|
||||
private var gameId: String? = null
|
||||
|
||||
@@ -8,6 +8,7 @@ enum class StringSetting(
|
||||
override val section: String,
|
||||
override val defaultValue: String
|
||||
) : AbstractStringSetting {
|
||||
AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"),
|
||||
CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0");
|
||||
|
||||
override var string: String = defaultValue
|
||||
|
||||
@@ -3,12 +3,8 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
|
||||
class HeaderSetting(
|
||||
setting: AbstractSetting?,
|
||||
titleId: Int,
|
||||
descriptionId: Int
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
titleId: Int
|
||||
) : SettingsItem(null, titleId, 0) {
|
||||
override val type = TYPE_HEADER
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
|
||||
class SingleChoiceSetting(
|
||||
setting: AbstractIntSetting?,
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.FloatSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class SliderSetting(
|
||||
setting: AbstractSetting?,
|
||||
@@ -19,7 +17,7 @@ class SliderSetting(
|
||||
val max: Int,
|
||||
val units: String,
|
||||
val key: String? = null,
|
||||
val defaultValue: Int? = null,
|
||||
val defaultValue: Int? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_SLIDER
|
||||
|
||||
|
||||
@@ -5,24 +5,25 @@ package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
|
||||
class StringSingleChoiceSetting(
|
||||
val key: String? = null,
|
||||
setting: AbstractSetting?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val choicesId: Array<String>,
|
||||
private val valuesId: Array<String>?,
|
||||
val choices: Array<String>,
|
||||
val values: Array<String>?,
|
||||
val key: String? = null,
|
||||
private val defaultValue: String? = null
|
||||
) : SettingsItem(setting, titleId, descriptionId) {
|
||||
override val type = TYPE_STRING_SINGLE_CHOICE
|
||||
|
||||
fun getValueAt(index: Int): String? {
|
||||
if (valuesId == null) return null
|
||||
return if (index >= 0 && index < valuesId.size) {
|
||||
valuesId[index]
|
||||
} else ""
|
||||
if (values == null) return null
|
||||
return if (index >= 0 && index < values.size) {
|
||||
values[index]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
val selectedValue: String
|
||||
@@ -35,8 +36,8 @@ class StringSingleChoiceSetting(
|
||||
val selectValueIndex: Int
|
||||
get() {
|
||||
val selectedValue = selectedValue
|
||||
for (i in valuesId!!.indices) {
|
||||
if (valuesId[i] == selectedValue) {
|
||||
for (i in values!!.indices) {
|
||||
if (values[i] == selectedValue) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting
|
||||
|
||||
class SubmenuSetting(
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
|
||||
@@ -8,18 +8,18 @@ import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.core.view.updatePadding
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
|
||||
@@ -30,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import java.io.IOException
|
||||
|
||||
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
private val presenter = SettingsActivityPresenter(this)
|
||||
@@ -60,7 +59,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
setSupportActionBar(binding.toolbarSettings)
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) {
|
||||
if (InsetsHelper.getSystemGestureType(applicationContext) !=
|
||||
InsetsHelper.GESTURE_NAVIGATION
|
||||
) {
|
||||
binding.navigationBarShade.setBackgroundColor(
|
||||
ThemeHelper.getColorWithOpacity(
|
||||
MaterialColors.getColor(
|
||||
@@ -76,7 +77,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() = navigateBack()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
setInsets()
|
||||
}
|
||||
@@ -149,11 +151,13 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
private fun areSystemAnimationsEnabled(): Boolean {
|
||||
val duration = android.provider.Settings.Global.getFloat(
|
||||
contentResolver,
|
||||
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f
|
||||
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
|
||||
1f
|
||||
)
|
||||
val transition = android.provider.Settings.Global.getFloat(
|
||||
contentResolver,
|
||||
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f
|
||||
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
|
||||
1f
|
||||
)
|
||||
return duration != 0f && transition != 0f
|
||||
}
|
||||
@@ -208,7 +212,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView {
|
||||
get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.frameContent
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
view.updatePadding(
|
||||
|
||||
@@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.features.settings.ui
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import java.io.File
|
||||
|
||||
class SettingsActivityPresenter(private val activityView: SettingsActivityView) {
|
||||
val settings: Settings get() = activityView.settings
|
||||
@@ -46,9 +46,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
|
||||
|
||||
private fun prepareDirectoriesIfNeeded() {
|
||||
val configFile =
|
||||
File(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini")
|
||||
File(
|
||||
"${DirectoryInitialization.userDirectory}/config/" +
|
||||
"${SettingsFile.FILE_NAME_CONFIG}.ini"
|
||||
)
|
||||
if (!configFile.exists()) {
|
||||
Log.error(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini")
|
||||
Log.error(
|
||||
"${DirectoryInitialization.userDirectory}/config/" +
|
||||
"${SettingsFile.FILE_NAME_CONFIG}.ini"
|
||||
)
|
||||
Log.error("yuzu config file could not be found!")
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.datepicker.MaterialDatePicker
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@@ -139,7 +138,7 @@ class SettingsAdapter(
|
||||
clickedItem = item
|
||||
dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(item.nameId)
|
||||
.setSingleChoiceItems(item.choicesId, item.selectValueIndex, this)
|
||||
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
|
||||
.show()
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,10 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
settingsAdapter = SettingsAdapter(this, requireActivity())
|
||||
val dividerDecoration = MaterialDividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)
|
||||
val dividerDecoration = MaterialDividerItemDecoration(
|
||||
requireContext(),
|
||||
LinearLayoutManager.VERTICAL
|
||||
)
|
||||
dividerDecoration.isLastItemDecorated = false
|
||||
binding.listSettings.apply {
|
||||
adapter = settingsAdapter
|
||||
@@ -99,7 +102,9 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
|
||||
}
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.listSettings) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.listSettings
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
view.updatePadding(bottom = insets.bottom)
|
||||
windowInsets
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
|
||||
@@ -43,7 +42,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
}
|
||||
|
||||
fun putSetting(setting: AbstractSetting) {
|
||||
if (setting.section == null) {
|
||||
if (setting.section == null || setting.key == null) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -236,7 +235,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics))
|
||||
sl.apply {
|
||||
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_ACCURACY,
|
||||
@@ -355,18 +353,31 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
|
||||
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
|
||||
sl.add(
|
||||
SliderSetting(
|
||||
IntSetting.AUDIO_VOLUME,
|
||||
R.string.audio_volume,
|
||||
R.string.audio_volume_description,
|
||||
0,
|
||||
100,
|
||||
"%",
|
||||
IntSetting.AUDIO_VOLUME.key,
|
||||
IntSetting.AUDIO_VOLUME.defaultValue
|
||||
sl.apply {
|
||||
add(
|
||||
StringSingleChoiceSetting(
|
||||
StringSetting.AUDIO_OUTPUT_ENGINE,
|
||||
R.string.audio_output_engine,
|
||||
0,
|
||||
settingsActivity.resources.getStringArray(R.array.outputEngineEntries),
|
||||
settingsActivity.resources.getStringArray(R.array.outputEngineValues),
|
||||
StringSetting.AUDIO_OUTPUT_ENGINE.key,
|
||||
StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue
|
||||
)
|
||||
)
|
||||
)
|
||||
add(
|
||||
SliderSetting(
|
||||
IntSetting.AUDIO_VOLUME,
|
||||
R.string.audio_volume,
|
||||
R.string.audio_volume_description,
|
||||
0,
|
||||
100,
|
||||
"%",
|
||||
IntSetting.AUDIO_VOLUME.key,
|
||||
IntSetting.AUDIO_VOLUME.defaultValue
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
|
||||
@@ -469,6 +480,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug))
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.gpu))
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDERER_BACKEND,
|
||||
@@ -489,6 +501,39 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
IntSetting.RENDERER_DEBUG.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
add(HeaderSetting(R.string.cpu))
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.CPU_DEBUG_MODE,
|
||||
R.string.cpu_debug_mode,
|
||||
R.string.cpu_debug_mode_description,
|
||||
BooleanSetting.CPU_DEBUG_MODE.key,
|
||||
BooleanSetting.CPU_DEBUG_MODE.defaultValue
|
||||
)
|
||||
)
|
||||
|
||||
val fastmem = object : AbstractBooleanSetting {
|
||||
override var boolean: Boolean
|
||||
get() =
|
||||
BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean
|
||||
set(value) {
|
||||
BooleanSetting.FASTMEM.boolean = value
|
||||
BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value
|
||||
}
|
||||
override val key: String? = null
|
||||
override val section: String = Settings.SECTION_CPU
|
||||
override val isRuntimeEditable: Boolean = false
|
||||
override val valueAsString: String = ""
|
||||
override val defaultValue: Any = true
|
||||
}
|
||||
add(
|
||||
SwitchSetting(
|
||||
fastmem,
|
||||
R.string.fastmem,
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) :
|
||||
SettingViewHolder(binding.root, adapter) {
|
||||
|
||||
@@ -26,6 +26,14 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
for (i in values.indices) {
|
||||
if (values[i] == item.selectedValue) {
|
||||
binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i]
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if (item is StringSingleChoiceSetting) {
|
||||
for (i in item.values!!.indices) {
|
||||
if (item.values[i] == item.selectedValue) {
|
||||
binding.textSettingDescription.text = item.choices[i]
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -6,8 +6,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
|
||||
|
||||
class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) :
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.utils
|
||||
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import org.ini4j.Wini
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.R
|
||||
@@ -13,8 +15,6 @@ import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView
|
||||
import org.yuzu.yuzu_emu.utils.BiMap
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Contains static methods for interacting with .ini files in which settings are stored.
|
||||
@@ -137,9 +137,12 @@ object SettingsFile {
|
||||
for (settingKey in sortedKeySet) {
|
||||
val setting = settings[settingKey]
|
||||
NativeLibrary.setUserSetting(
|
||||
gameId, mapSectionNameFromIni(
|
||||
gameId,
|
||||
mapSectionNameFromIni(
|
||||
section.name
|
||||
), setting!!.key, setting.valueAsString
|
||||
),
|
||||
setting!!.key,
|
||||
setting.valueAsString
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -148,13 +151,17 @@ object SettingsFile {
|
||||
private fun mapSectionNameFromIni(generalSectionName: String): String? {
|
||||
return if (sectionsMap.getForward(generalSectionName) != null) {
|
||||
sectionsMap.getForward(generalSectionName)
|
||||
} else generalSectionName
|
||||
} else {
|
||||
generalSectionName
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapSectionNameToIni(generalSectionName: String): String {
|
||||
return if (sectionsMap.getBackward(generalSectionName) != null) {
|
||||
sectionsMap.getBackward(generalSectionName).toString()
|
||||
} else generalSectionName
|
||||
} else {
|
||||
generalSectionName
|
||||
}
|
||||
}
|
||||
|
||||
fun getSettingsFile(fileName: String): File {
|
||||
@@ -237,5 +244,21 @@ object SettingsFile {
|
||||
val setting = settings[key]
|
||||
parser.put(header, setting!!.key, setting.valueAsString)
|
||||
}
|
||||
|
||||
BooleanSetting.values().forEach {
|
||||
if (!keySet.contains(it.key)) {
|
||||
parser.put(header, it.key, it.valueAsString)
|
||||
}
|
||||
}
|
||||
IntSetting.values().forEach {
|
||||
if (!keySet.contains(it.key)) {
|
||||
parser.put(header, it.key, it.valueAsString)
|
||||
}
|
||||
}
|
||||
StringSetting.values().forEach {
|
||||
if (!keySet.contains(it.key)) {
|
||||
parser.put(header, it.key, it.valueAsString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,11 @@ class AboutFragment : Fragment() {
|
||||
true
|
||||
}
|
||||
|
||||
binding.buttonContributors.setOnClickListener { openLink(getString(R.string.contributors_link)) }
|
||||
binding.buttonContributors.setOnClickListener {
|
||||
openLink(
|
||||
getString(R.string.contributors_link)
|
||||
)
|
||||
}
|
||||
binding.buttonLicenses.setOnClickListener {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
|
||||
@@ -101,7 +105,9 @@ class AboutFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
|
||||
@@ -49,7 +49,11 @@ class EarlyAccessFragment : Fragment() {
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack()
|
||||
}
|
||||
|
||||
binding.getEarlyAccessButton.setOnClickListener { openLink(getString(R.string.play_store_link)) }
|
||||
binding.getEarlyAccessButton.setOnClickListener {
|
||||
openLink(
|
||||
getString(R.string.play_store_link)
|
||||
)
|
||||
}
|
||||
|
||||
setInsets()
|
||||
}
|
||||
@@ -60,7 +64,9 @@ class EarlyAccessFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
|
||||
@@ -83,21 +83,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
onReturnFromSettings = context.activityResultRegistry.register(
|
||||
"SettingsResult", ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
binding.surfaceEmulation.setAspectRatio(
|
||||
when (IntSetting.RENDERER_ASPECT_RATIO.int) {
|
||||
0 -> Rational(16, 9)
|
||||
1 -> Rational(4, 3)
|
||||
2 -> Rational(21, 9)
|
||||
3 -> Rational(16, 10)
|
||||
4 -> null // Stretch
|
||||
else -> Rational(16, 9)
|
||||
}
|
||||
)
|
||||
emulationActivity?.buildPictureInPictureParams()
|
||||
updateScreenLayout()
|
||||
}
|
||||
"SettingsResult",
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { updateScreenLayout() }
|
||||
} else {
|
||||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
||||
}
|
||||
@@ -191,9 +179,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
requireActivity(),
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open()
|
||||
if (binding.drawerLayout.isOpen) {
|
||||
binding.drawerLayout.close()
|
||||
} else {
|
||||
binding.drawerLayout.open()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
@@ -236,17 +229,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
DirectoryInitialization.start(requireContext())
|
||||
}
|
||||
|
||||
binding.surfaceEmulation.setAspectRatio(
|
||||
when (IntSetting.RENDERER_ASPECT_RATIO.int) {
|
||||
0 -> Rational(16, 9)
|
||||
1 -> Rational(4, 3)
|
||||
2 -> Rational(21, 9)
|
||||
3 -> Rational(16, 10)
|
||||
4 -> null // Stretch
|
||||
else -> Rational(16, 9)
|
||||
}
|
||||
)
|
||||
|
||||
updateScreenLayout()
|
||||
|
||||
emulationState.run(emulationActivity!!.isActivityRecreated)
|
||||
@@ -309,44 +291,66 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
private fun updateScreenLayout() {
|
||||
private fun updateOrientation() {
|
||||
emulationActivity?.let {
|
||||
it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) {
|
||||
Settings.LayoutOption_MobileLandscape -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
Settings.LayoutOption_MobilePortrait -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
||||
Settings.LayoutOption_MobileLandscape ->
|
||||
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
Settings.LayoutOption_MobilePortrait ->
|
||||
ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
|
||||
Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
}
|
||||
}
|
||||
onConfigurationChanged(resources.configuration)
|
||||
}
|
||||
|
||||
private fun updateFoldableLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) {
|
||||
val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
|
||||
if (it.isSeparating) {
|
||||
emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
|
||||
// Restrict emulation and overlays to the top of the screen
|
||||
binding.emulationContainer.layoutParams.height = it.bounds.top
|
||||
binding.overlayContainer.layoutParams.height = it.bounds.top
|
||||
// Restrict input and menu drawer to the bottom of the screen
|
||||
binding.inputContainer.layoutParams.height = it.bounds.bottom
|
||||
binding.inGameMenu.layoutParams.height = it.bounds.bottom
|
||||
|
||||
isInFoldableLayout = true
|
||||
binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE
|
||||
refreshInputOverlay()
|
||||
}
|
||||
private fun updateScreenLayout() {
|
||||
binding.surfaceEmulation.setAspectRatio(
|
||||
when (IntSetting.RENDERER_ASPECT_RATIO.int) {
|
||||
0 -> Rational(16, 9)
|
||||
1 -> Rational(4, 3)
|
||||
2 -> Rational(21, 9)
|
||||
3 -> Rational(16, 10)
|
||||
4 -> null // Stretch
|
||||
else -> Rational(16, 9)
|
||||
}
|
||||
it.isSeparating
|
||||
} ?: false
|
||||
)
|
||||
emulationActivity?.buildPictureInPictureParams()
|
||||
updateOrientation()
|
||||
}
|
||||
|
||||
private fun updateFoldableLayout(
|
||||
emulationActivity: EmulationActivity,
|
||||
newLayoutInfo: WindowLayoutInfo
|
||||
) {
|
||||
val isFolding =
|
||||
(newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let {
|
||||
if (it.isSeparating) {
|
||||
emulationActivity.requestedOrientation =
|
||||
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) {
|
||||
// Restrict emulation and overlays to the top of the screen
|
||||
binding.emulationContainer.layoutParams.height = it.bounds.top
|
||||
binding.overlayContainer.layoutParams.height = it.bounds.top
|
||||
// Restrict input and menu drawer to the bottom of the screen
|
||||
binding.inputContainer.layoutParams.height = it.bounds.bottom
|
||||
binding.inGameMenu.layoutParams.height = it.bounds.bottom
|
||||
|
||||
isInFoldableLayout = true
|
||||
binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE
|
||||
refreshInputOverlay()
|
||||
}
|
||||
}
|
||||
it.isSeparating
|
||||
} ?: false
|
||||
if (!isFolding) {
|
||||
binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
isInFoldableLayout = false
|
||||
updateScreenLayout()
|
||||
updateOrientation()
|
||||
onConfigurationChanged(resources.configuration)
|
||||
}
|
||||
binding.emulationContainer.requestLayout()
|
||||
binding.inputContainer.requestLayout()
|
||||
@@ -516,18 +520,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
inputScaleSlider.apply {
|
||||
valueTo = 150F
|
||||
value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
|
||||
addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
inputScaleValue.text = "${value.toInt()}%"
|
||||
setControlScale(value.toInt())
|
||||
})
|
||||
addOnChangeListener(
|
||||
Slider.OnChangeListener { _, value, _ ->
|
||||
inputScaleValue.text = "${value.toInt()}%"
|
||||
setControlScale(value.toInt())
|
||||
}
|
||||
)
|
||||
}
|
||||
inputOpacitySlider.apply {
|
||||
valueTo = 100F
|
||||
value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat()
|
||||
addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
inputOpacityValue.text = "${value.toInt()}%"
|
||||
setControlOpacity(value.toInt())
|
||||
})
|
||||
addOnChangeListener(
|
||||
Slider.OnChangeListener { _, value, _ ->
|
||||
inputOpacityValue.text = "${value.toInt()}%"
|
||||
setControlOpacity(value.toInt())
|
||||
}
|
||||
)
|
||||
}
|
||||
inputScaleValue.text = "${inputScaleSlider.value.toInt()}%"
|
||||
inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%"
|
||||
@@ -559,7 +567,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
private fun setInsets() {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.inGameMenu
|
||||
) { v: View, windowInsets: WindowInsetsCompat ->
|
||||
val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
var left = 0
|
||||
var right = 0
|
||||
@@ -679,8 +689,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
state = State.PAUSED
|
||||
}
|
||||
|
||||
State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.")
|
||||
else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.")
|
||||
State.PAUSED -> Log.warning(
|
||||
"[EmulationFragment] Surface cleared while emulation paused."
|
||||
)
|
||||
else -> Log.warning(
|
||||
"[EmulationFragment] Surface cleared while emulation stopped."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,77 +68,109 @@ class HomeSettingsFragment : Fragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
mainActivity = requireActivity() as MainActivity
|
||||
|
||||
val optionsList: MutableList<HomeSetting> = mutableListOf(
|
||||
HomeSetting(
|
||||
R.string.advanced_settings,
|
||||
R.string.settings_description,
|
||||
R.drawable.ic_settings
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") },
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
R.string.open_user_folder_description,
|
||||
R.drawable.ic_folder_open
|
||||
) { openFileManager() },
|
||||
HomeSetting(
|
||||
R.string.preferences_theme,
|
||||
R.string.theme_and_color_description,
|
||||
R.drawable.ic_palette
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") },
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_exit
|
||||
) { driverInstaller() },
|
||||
HomeSetting(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
R.drawable.ic_nfc
|
||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
|
||||
HomeSetting(
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
R.drawable.ic_system_update_alt
|
||||
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) },
|
||||
HomeSetting(
|
||||
R.string.select_games_folder,
|
||||
R.string.select_games_folder_description,
|
||||
R.drawable.ic_add
|
||||
) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
||||
HomeSetting(
|
||||
R.string.manage_save_data,
|
||||
R.string.import_export_saves_description,
|
||||
R.drawable.ic_save
|
||||
) {
|
||||
ImportExportSavesFragment().show(
|
||||
parentFragmentManager,
|
||||
ImportExportSavesFragment.TAG
|
||||
val optionsList: MutableList<HomeSetting> = mutableListOf<HomeSetting>().apply {
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.advanced_settings,
|
||||
R.string.settings_description,
|
||||
R.drawable.ic_settings
|
||||
) { SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
R.string.open_user_folder_description,
|
||||
R.drawable.ic_folder_open
|
||||
) { openFileManager() }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.preferences_theme,
|
||||
R.string.theme_and_color_description,
|
||||
R.drawable.ic_palette
|
||||
) { SettingsActivity.launch(requireContext(), Settings.SECTION_THEME, "") }
|
||||
)
|
||||
|
||||
if (GpuDriverHelper.supportsCustomDriverLoading()) {
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_gpu_driver,
|
||||
R.string.install_gpu_driver_description,
|
||||
R.drawable.ic_exit
|
||||
) { driverInstaller() }
|
||||
)
|
||||
},
|
||||
HomeSetting(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
R.drawable.ic_unlock
|
||||
) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
|
||||
HomeSetting(
|
||||
R.string.install_firmware,
|
||||
R.string.install_firmware_description,
|
||||
R.drawable.ic_firmware
|
||||
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) },
|
||||
HomeSetting(
|
||||
R.string.share_log,
|
||||
R.string.share_log_description,
|
||||
R.drawable.ic_log
|
||||
) { shareLog() },
|
||||
HomeSetting(
|
||||
R.string.about,
|
||||
R.string.about_description,
|
||||
R.drawable.ic_info_outline
|
||||
) {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
}
|
||||
)
|
||||
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_amiibo_keys,
|
||||
R.string.install_amiibo_keys_description,
|
||||
R.drawable.ic_nfc
|
||||
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_game_content,
|
||||
R.string.install_game_content_description,
|
||||
R.drawable.ic_system_update_alt
|
||||
) { mainActivity.installGameUpdate.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.select_games_folder,
|
||||
R.string.select_games_folder_description,
|
||||
R.drawable.ic_add
|
||||
) {
|
||||
mainActivity.getGamesDirectory.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
||||
)
|
||||
}
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.manage_save_data,
|
||||
R.string.import_export_saves_description,
|
||||
R.drawable.ic_save
|
||||
) {
|
||||
ImportExportSavesFragment().show(
|
||||
parentFragmentManager,
|
||||
ImportExportSavesFragment.TAG
|
||||
)
|
||||
}
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
R.drawable.ic_unlock
|
||||
) { mainActivity.getProdKey.launch(arrayOf("*/*")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.install_firmware,
|
||||
R.string.install_firmware_description,
|
||||
R.drawable.ic_firmware
|
||||
) { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.share_log,
|
||||
R.string.share_log_description,
|
||||
R.drawable.ic_log
|
||||
) { shareLog() }
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.about,
|
||||
R.string.about_description,
|
||||
R.drawable.ic_info_outline
|
||||
) {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
parentFragmentManager.primaryNavigationFragment?.findNavController()
|
||||
?.navigate(R.id.action_homeSettingsFragment_to_aboutFragment)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (!BuildConfig.PREMIUM) {
|
||||
optionsList.add(
|
||||
@@ -225,7 +257,11 @@ class HomeSettingsFragment : Fragment() {
|
||||
val intent = Intent(action)
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
return intent
|
||||
}
|
||||
|
||||
@@ -307,7 +343,9 @@ class HomeSettingsFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation)
|
||||
|
||||
@@ -15,6 +15,14 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FilenameFilter
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -24,14 +32,6 @@ import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||
import org.yuzu.yuzu_emu.getPublicFilesDir
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FilenameFilter
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class ImportExportSavesFragment : DialogFragment() {
|
||||
private val context = YuzuApplication.appContext
|
||||
@@ -98,7 +98,7 @@ class ImportExportSavesFragment : DialogFragment() {
|
||||
val outputZipFile = File(
|
||||
tempFolder,
|
||||
"yuzu saves - ${
|
||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
|
||||
}.zip"
|
||||
)
|
||||
outputZipFile.createNewFile()
|
||||
@@ -106,12 +106,14 @@ class ImportExportSavesFragment : DialogFragment() {
|
||||
saveFolder.walkTopDown().forEach { file ->
|
||||
val zipFileName =
|
||||
file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/")
|
||||
if (zipFileName == "")
|
||||
if (zipFileName == "") {
|
||||
return@forEach
|
||||
}
|
||||
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
|
||||
zos.putNextEntry(entry)
|
||||
if (file.isFile)
|
||||
if (file.isFile) {
|
||||
file.inputStream().use { fis -> fis.copyTo(zos) }
|
||||
}
|
||||
}
|
||||
}
|
||||
lastZipCreated = outputZipFile
|
||||
@@ -137,7 +139,8 @@ class ImportExportSavesFragment : DialogFragment() {
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val file = DocumentFile.fromSingleUri(
|
||||
context, DocumentsContract.buildDocumentUri(
|
||||
context,
|
||||
DocumentsContract.buildDocumentUri(
|
||||
DocumentProvider.AUTHORITY,
|
||||
"${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}"
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||
|
||||
|
||||
class IndeterminateProgressDialogFragment : DialogFragment() {
|
||||
private val taskViewModel: TaskViewModel by activityViewModels()
|
||||
|
||||
|
||||
@@ -113,7 +113,9 @@ class LicensesFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels
|
||||
import androidx.preference.PreferenceManager
|
||||
import info.debatty.java.stringsimilarity.Jaccard
|
||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||
import java.util.Locale
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||
@@ -29,8 +30,6 @@ import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
||||
import org.yuzu.yuzu_emu.utils.Log
|
||||
import java.util.Locale
|
||||
|
||||
class SearchFragment : Fragment() {
|
||||
private var _binding: FragmentSearchBinding? = null
|
||||
@@ -130,15 +129,15 @@ class SearchFragment : Fragment() {
|
||||
R.id.chip_homebrew -> baseList.filter { it.isHomebrew }
|
||||
|
||||
R.id.chip_retail -> baseList.filter {
|
||||
FileUtil.hasExtension(it.path, "xci")
|
||||
|| FileUtil.hasExtension(it.path, "nsp")
|
||||
FileUtil.hasExtension(it.path, "xci") ||
|
||||
FileUtil.hasExtension(it.path, "nsp")
|
||||
}
|
||||
|
||||
else -> baseList
|
||||
}
|
||||
|
||||
if (binding.searchText.text.toString().isEmpty()
|
||||
&& binding.chipGroup.checkedChipId != View.NO_ID
|
||||
if (binding.searchText.text.toString().isEmpty() &&
|
||||
binding.chipGroup.checkedChipId != View.NO_ID
|
||||
) {
|
||||
gamesViewModel.setSearchedGames(filteredList)
|
||||
return
|
||||
@@ -173,14 +172,16 @@ class SearchFragment : Fragment() {
|
||||
private fun focusSearch() {
|
||||
if (_binding != null) {
|
||||
binding.searchText.requestFocus()
|
||||
val imm =
|
||||
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
|
||||
val imm = requireActivity()
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
|
||||
imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med)
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.adapters.SetupAdapter
|
||||
@@ -35,7 +36,6 @@ import org.yuzu.yuzu_emu.model.SetupPage
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
import java.io.File
|
||||
|
||||
class SetupFragment : Fragment() {
|
||||
private var _binding: FragmentSetupBinding? = null
|
||||
@@ -82,7 +82,8 @@ class SetupFragment : Fragment() {
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
requireActivity().window.navigationBarColor =
|
||||
ContextCompat.getColor(requireContext(), android.R.color.transparent)
|
||||
@@ -148,14 +149,20 @@ class SetupFragment : Fragment() {
|
||||
R.drawable.ic_add,
|
||||
true,
|
||||
R.string.add_games,
|
||||
{ mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
||||
{
|
||||
mainActivity.getGamesDirectory.launch(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
|
||||
)
|
||||
},
|
||||
true,
|
||||
R.string.add_games_warning,
|
||||
R.string.add_games_warning_description,
|
||||
R.string.add_games_warning_help,
|
||||
{
|
||||
val preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
PreferenceManager.getDefaultSharedPreferences(
|
||||
YuzuApplication.appContext
|
||||
)
|
||||
preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()
|
||||
}
|
||||
)
|
||||
@@ -260,7 +267,9 @@ class SetupFragment : Fragment() {
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private val permissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||
if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
|
||||
if (!it &&
|
||||
!shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
||||
) {
|
||||
PermissionDeniedDialogFragment().show(
|
||||
childFragmentManager,
|
||||
PermissionDeniedDialogFragment.TAG
|
||||
@@ -315,7 +324,9 @@ class SetupFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
view.setPadding(
|
||||
|
||||
@@ -44,7 +44,9 @@ class AutofitGridLayoutManager(
|
||||
override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) {
|
||||
val width = width
|
||||
val height = height
|
||||
if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) {
|
||||
if (columnWidth > 0 && width > 0 && height > 0 &&
|
||||
(isColumnWidthChanged || lastWidth != width || lastHeight != height)
|
||||
) {
|
||||
val totalSpace: Int = if (orientation == VERTICAL) {
|
||||
width - paddingRight - paddingLeft
|
||||
} else {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import java.util.HashSet
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.HashSet
|
||||
|
||||
@Parcelize
|
||||
@Serializable
|
||||
@@ -23,8 +23,9 @@ class Game(
|
||||
val keyLastPlayedTime get() = "${gameId}_LastPlayed"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is Game)
|
||||
if (other !is Game) {
|
||||
return false
|
||||
}
|
||||
|
||||
return hashCode() == other.hashCode()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -20,7 +21,6 @@ import kotlinx.serialization.json.Json
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
import java.util.Locale
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
class GamesViewModel : ViewModel() {
|
||||
@@ -99,8 +99,9 @@ class GamesViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun reloadGames(directoryChanged: Boolean) {
|
||||
if (isReloading.value == true)
|
||||
if (isReloading.value == true) {
|
||||
return
|
||||
}
|
||||
_isReloading.postValue(true)
|
||||
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.overlay
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Point
|
||||
@@ -24,6 +23,8 @@ import android.view.WindowInsets
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.NativeLibrary.ButtonType
|
||||
import org.yuzu.yuzu_emu.NativeLibrary.StickType
|
||||
@@ -31,14 +32,13 @@ import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Draws the interactive input overlay on top of the
|
||||
* [SurfaceView] that is rendering emulation.
|
||||
*/
|
||||
class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs),
|
||||
class InputOverlay(context: Context, attrs: AttributeSet?) :
|
||||
SurfaceView(context, attrs),
|
||||
OnTouchListener {
|
||||
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
|
||||
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
|
||||
@@ -95,7 +95,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
|
||||
var shouldUpdateView = false
|
||||
val playerIndex =
|
||||
if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
|
||||
if (NativeLibrary.isHandheldOnly()) {
|
||||
NativeLibrary.ConsoleDevice
|
||||
} else {
|
||||
NativeLibrary.Player1Device
|
||||
}
|
||||
|
||||
for (button in overlayButtons) {
|
||||
if (!button.updateStatus(event)) {
|
||||
@@ -158,8 +162,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
shouldUpdateView = true
|
||||
}
|
||||
|
||||
if (shouldUpdateView)
|
||||
if (shouldUpdateView) {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) {
|
||||
return true
|
||||
@@ -243,9 +248,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
// If no button is being moved now, remember the currently touched button to move.
|
||||
if (buttonBeingConfigured == null &&
|
||||
button.bounds.contains(
|
||||
fingerPositionX,
|
||||
fingerPositionY
|
||||
)
|
||||
fingerPositionX,
|
||||
fingerPositionY
|
||||
)
|
||||
) {
|
||||
buttonBeingConfigured = button
|
||||
buttonBeingConfigured!!.onConfigureTouch(event)
|
||||
@@ -309,9 +314,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
MotionEvent.ACTION_DOWN,
|
||||
MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null &&
|
||||
joystick.bounds.contains(
|
||||
fingerPositionX,
|
||||
fingerPositionY
|
||||
)
|
||||
fingerPositionX,
|
||||
fingerPositionY
|
||||
)
|
||||
) {
|
||||
joystickBeingConfigured = joystick
|
||||
joystickBeingConfigured!!.onConfigureTouch(event)
|
||||
@@ -668,7 +673,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
R.integer.SWITCH_STICK_L_Y_FOLDABLE
|
||||
)
|
||||
|
||||
private fun getResourceValue(orientation: String, position: Int) : Float {
|
||||
private fun getResourceValue(orientation: String, position: Int): Float {
|
||||
return when (orientation) {
|
||||
PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
|
||||
FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
|
||||
@@ -820,7 +825,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
* @param context Context for getting the vector drawable
|
||||
* @param drawableId The ID of the drawable to scale.
|
||||
* @param scale The scale factor for the bitmap.
|
||||
* @return The scaled [Bitmap]
|
||||
* @return The scaled [Bitmap]
|
||||
*/
|
||||
private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap {
|
||||
val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable
|
||||
@@ -854,7 +859,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
* Gets the safe screen size for drawing the overlay
|
||||
*
|
||||
* @param context Context for getting the window metrics
|
||||
* @return A pair of points, the first being the top left corner of the safe area,
|
||||
* @return A pair of points, the first being the top left corner of the safe area,
|
||||
* the second being the bottom right corner of the safe area
|
||||
*/
|
||||
private fun getSafeScreenSize(context: Context): Pair<Point, Point> {
|
||||
@@ -872,10 +877,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout
|
||||
if (insets != null) {
|
||||
if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2)
|
||||
if (insets.boundingRectTop.bottom != 0 &&
|
||||
insets.boundingRectTop.bottom > maxY / 2
|
||||
) {
|
||||
maxY = insets.boundingRectTop.bottom.toFloat()
|
||||
if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2)
|
||||
}
|
||||
if (insets.boundingRectRight.left != 0 &&
|
||||
insets.boundingRectRight.left > maxX / 2
|
||||
) {
|
||||
maxX = insets.boundingRectRight.left.toFloat()
|
||||
}
|
||||
|
||||
minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left
|
||||
minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom
|
||||
|
||||
@@ -133,7 +133,10 @@ class InputOverlayDrawableDpad(
|
||||
downButtonState = axisY > VIRT_AXIS_DEADZONE
|
||||
leftButtonState = axisX < -VIRT_AXIS_DEADZONE
|
||||
rightButtonState = axisX > VIRT_AXIS_DEADZONE
|
||||
return oldUpState != upButtonState || oldDownState != downButtonState || oldLeftState != leftButtonState || oldRightState != rightButtonState
|
||||
return oldUpState != upButtonState ||
|
||||
oldDownState != downButtonState ||
|
||||
oldLeftState != leftButtonState ||
|
||||
oldRightState != rightButtonState
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.MotionEvent
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings
|
||||
|
||||
/**
|
||||
* Custom [BitmapDrawable] that is capable
|
||||
@@ -241,14 +241,22 @@ class InputOverlayDrawableJoystick(
|
||||
private fun setInnerBounds() {
|
||||
var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt()
|
||||
var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt()
|
||||
if (x > virtBounds.centerX() + virtBounds.width() / 2) x =
|
||||
virtBounds.centerX() + virtBounds.width() / 2
|
||||
if (x < virtBounds.centerX() - virtBounds.width() / 2) x =
|
||||
virtBounds.centerX() - virtBounds.width() / 2
|
||||
if (y > virtBounds.centerY() + virtBounds.height() / 2) y =
|
||||
virtBounds.centerY() + virtBounds.height() / 2
|
||||
if (y < virtBounds.centerY() - virtBounds.height() / 2) y =
|
||||
virtBounds.centerY() - virtBounds.height() / 2
|
||||
if (x > virtBounds.centerX() + virtBounds.width() / 2) {
|
||||
x =
|
||||
virtBounds.centerX() + virtBounds.width() / 2
|
||||
}
|
||||
if (x < virtBounds.centerX() - virtBounds.width() / 2) {
|
||||
x =
|
||||
virtBounds.centerX() - virtBounds.width() / 2
|
||||
}
|
||||
if (y > virtBounds.centerY() + virtBounds.height() / 2) {
|
||||
y =
|
||||
virtBounds.centerY() + virtBounds.height() / 2
|
||||
}
|
||||
if (y < virtBounds.centerY() - virtBounds.height() / 2) {
|
||||
y =
|
||||
virtBounds.centerY() - virtBounds.height() / 2
|
||||
}
|
||||
val width = pressedStateInnerBitmap.bounds.width() / 2
|
||||
val height = pressedStateInnerBitmap.bounds.height() / 2
|
||||
defaultStateInnerBitmap.setBounds(
|
||||
|
||||
@@ -99,7 +99,9 @@ class GamesFragment : Fragment() {
|
||||
}
|
||||
shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
|
||||
if (shouldSwapData) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!)
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(
|
||||
gamesViewModel.games.value!!
|
||||
)
|
||||
gamesViewModel.setShouldSwapData(false)
|
||||
}
|
||||
}
|
||||
@@ -128,7 +130,9 @@ class GamesFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { view: View, windowInsets: WindowInsetsCompat ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
|
||||
val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large)
|
||||
|
||||
@@ -26,6 +26,9 @@ import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -43,9 +46,6 @@ import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.utils.*
|
||||
import java.io.File
|
||||
import java.io.FilenameFilter
|
||||
import java.io.IOException
|
||||
|
||||
class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
@@ -86,7 +86,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
ThemeHelper.SYSTEM_BAR_ALPHA
|
||||
)
|
||||
)
|
||||
if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) {
|
||||
if (InsetsHelper.getSystemGestureType(applicationContext) !=
|
||||
InsetsHelper.GESTURE_NAVIGATION
|
||||
) {
|
||||
binding.navigationBarShade.setBackgroundColor(
|
||||
ThemeHelper.getColorWithOpacity(
|
||||
MaterialColors.getColor(
|
||||
@@ -172,7 +174,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
binding.navigationView.height.toFloat() * 2
|
||||
translationY(0f)
|
||||
} else {
|
||||
if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
if (ViewCompat.getLayoutDirection(binding.navigationView) ==
|
||||
ViewCompat.LAYOUT_DIRECTION_LTR
|
||||
) {
|
||||
binding.navigationView.translationX =
|
||||
binding.navigationView.width.toFloat() * -2
|
||||
translationX(0f)
|
||||
@@ -189,7 +193,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
if (smallLayout) {
|
||||
translationY(binding.navigationView.height.toFloat() * 2)
|
||||
} else {
|
||||
if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
if (ViewCompat.getLayoutDirection(binding.navigationView) ==
|
||||
ViewCompat.LAYOUT_DIRECTION_LTR
|
||||
) {
|
||||
translationX(binding.navigationView.width.toFloat() * -2)
|
||||
} else {
|
||||
translationX(binding.navigationView.width.toFloat() * 2)
|
||||
@@ -234,7 +240,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(
|
||||
binding.root
|
||||
) { _: View, windowInsets: WindowInsetsCompat ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams
|
||||
mlpStatusShade.height = insets.top
|
||||
@@ -256,8 +264,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getGamesDirectory =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
contentResolver.takePersistableUriPermission(
|
||||
result,
|
||||
@@ -281,8 +290,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getProdKey =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (!FileUtil.hasExtension(result, "keys")) {
|
||||
MessageDialogFragment.newInstance(
|
||||
@@ -324,8 +334,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getFirmware =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val inputZip = contentResolver.openInputStream(result)
|
||||
if (inputZip == null) {
|
||||
@@ -376,8 +387,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getAmiiboKey =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
if (!FileUtil.hasExtension(result, "bin")) {
|
||||
MessageDialogFragment.newInstance(
|
||||
@@ -418,8 +430,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val getDriver =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null)
|
||||
if (result == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val takeFlags =
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
@@ -470,8 +483,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
|
||||
val installGameUpdate =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
if (it == null)
|
||||
if (it == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
IndeterminateProgressDialogFragment.newInstance(
|
||||
this@MainActivity,
|
||||
|
||||
@@ -19,7 +19,9 @@ class ControllerMappingHelper {
|
||||
// The two analog triggers generate analog motion events as well as a keycode.
|
||||
// We always prefer to use the analog values, so throw away the button press
|
||||
keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
|
||||
} else false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Context
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
||||
object DirectoryInitialization {
|
||||
private var userPath: String? = null
|
||||
|
||||
@@ -5,10 +5,10 @@ package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
|
||||
class DocumentsTree {
|
||||
private var root: DocumentsNode? = null
|
||||
@@ -29,7 +29,9 @@ class DocumentsTree {
|
||||
val node = resolvePath(filepath)
|
||||
return if (node == null || node.isDirectory) {
|
||||
0
|
||||
} else FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
|
||||
} else {
|
||||
FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun exists(filepath: String): Boolean {
|
||||
@@ -111,7 +113,9 @@ class DocumentsTree {
|
||||
fun isNativePath(path: String): Boolean {
|
||||
return if (path.isNotEmpty()) {
|
||||
path[0] == '/'
|
||||
} else false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
@@ -19,6 +17,8 @@ import java.io.InputStream
|
||||
import java.net.URLDecoder
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
|
||||
|
||||
object FileUtil {
|
||||
const val PATH_TREE = "tree"
|
||||
|
||||
@@ -54,7 +54,7 @@ class ForegroundService : Service() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent == null) {
|
||||
return START_NOT_STICKY;
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
if (intent.action == ACTION_STOP) {
|
||||
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
|
||||
|
||||
@@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.utils
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import androidx.preference.PreferenceManager
|
||||
import java.util.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import java.util.*
|
||||
|
||||
object GameHelper {
|
||||
const val KEY_GAME_PATH = "game_path"
|
||||
|
||||
@@ -5,14 +5,14 @@ package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipInputStream
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage
|
||||
|
||||
object GpuDriverHelper {
|
||||
private const val META_JSON_FILENAME = "meta.json"
|
||||
@@ -113,6 +113,8 @@ object GpuDriverHelper {
|
||||
initializeDriverParameters(context)
|
||||
}
|
||||
|
||||
external fun supportsCustomDriverLoading(): Boolean
|
||||
|
||||
// Parse the custom driver metadata to retrieve the name.
|
||||
val customDriverName: String?
|
||||
get() {
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
class GpuDriverMetadata(metadataFilePath: String) {
|
||||
var name: String? = null
|
||||
|
||||
@@ -5,8 +5,8 @@ package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import kotlin.math.sqrt
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
||||
class InputHandler {
|
||||
fun initialize() {
|
||||
@@ -68,7 +68,11 @@ class InputHandler {
|
||||
6 -> NativeLibrary.Player6Device
|
||||
7 -> NativeLibrary.Player7Device
|
||||
8 -> NativeLibrary.Player8Device
|
||||
else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device
|
||||
else -> if (NativeLibrary.isHandheldOnly()) {
|
||||
NativeLibrary.ConsoleDevice
|
||||
} else {
|
||||
NativeLibrary.Player1Device
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +111,11 @@ class InputHandler {
|
||||
}
|
||||
|
||||
private fun getAxisToButton(axis: Float): Int {
|
||||
return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED
|
||||
return if (axis > 0.5f) {
|
||||
NativeLibrary.ButtonState.PRESSED
|
||||
} else {
|
||||
NativeLibrary.ButtonState.RELEASED
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) {
|
||||
@@ -287,7 +295,6 @@ class InputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
|
||||
// Joycon support is half dead. Right joystick doesn't work
|
||||
val playerNumber = getPlayerNumber(event.device.controllerNumber)
|
||||
@@ -355,6 +362,4 @@ class InputHandler {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
|
||||
object InsetsHelper {
|
||||
const val THREE_BUTTON_NAVIGATION = 0
|
||||
@@ -20,12 +18,8 @@ object InsetsHelper {
|
||||
resources.getIdentifier("config_navBarInteractionMode", "integer", "android")
|
||||
return if (resourceId != 0) {
|
||||
resources.getInteger(resourceId)
|
||||
} else 0
|
||||
}
|
||||
|
||||
fun getBottomPaddingRequired(activity: Activity): Int {
|
||||
val visibleFrame = Rect()
|
||||
activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame)
|
||||
return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import android.nfc.tech.NfcA
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
|
||||
class NfcReader(private val activity: Activity) {
|
||||
private var nfcAdapter: NfcAdapter? = null
|
||||
@@ -25,10 +25,13 @@ class NfcReader(private val activity: Activity) {
|
||||
|
||||
pendingIntent = PendingIntent.getActivity(
|
||||
activity,
|
||||
0, Intent(activity, activity.javaClass),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
0,
|
||||
Intent(activity, activity.javaClass),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
else PendingIntent.FLAG_UPDATE_CURRENT
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
}
|
||||
)
|
||||
|
||||
val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
|
||||
@@ -45,9 +48,9 @@ class NfcReader(private val activity: Activity) {
|
||||
|
||||
fun onNewIntent(intent: Intent) {
|
||||
val action = intent.action
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED != action
|
||||
&& NfcAdapter.ACTION_TECH_DISCOVERED != action
|
||||
&& NfcAdapter.ACTION_NDEF_DISCOVERED != action
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED != action &&
|
||||
NfcAdapter.ACTION_TECH_DISCOVERED != action &&
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED != action
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -84,7 +87,7 @@ class NfcReader(private val activity: Activity) {
|
||||
}
|
||||
|
||||
private fun ntag215ReadAll(amiibo: NfcA): ByteArray? {
|
||||
val bufferSize = amiibo.maxTransceiveLength;
|
||||
val bufferSize = amiibo.maxTransceiveLength
|
||||
val tagSize = 0x21C
|
||||
val pageSize = 4
|
||||
val lastPage = tagSize / pageSize - 1
|
||||
@@ -103,7 +106,7 @@ class NfcReader(private val activity: Activity) {
|
||||
val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1)
|
||||
System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize)
|
||||
} catch (e: IOException) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
return tagData
|
||||
|
||||
@@ -11,30 +11,34 @@ import java.io.Serializable
|
||||
|
||||
object SerializableHelper {
|
||||
inline fun <reified T : Serializable> Bundle.serializable(key: String): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getSerializable(key, T::class.java)
|
||||
else
|
||||
} else {
|
||||
getSerializable(key) as? T
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Serializable> Intent.serializable(key: String): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getSerializableExtra(key, T::class.java)
|
||||
else
|
||||
} else {
|
||||
getSerializableExtra(key) as? T
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getParcelable(key, T::class.java)
|
||||
else
|
||||
} else {
|
||||
getParcelable(key) as? T
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getParcelableExtra(key, T::class.java)
|
||||
else
|
||||
} else {
|
||||
getParcelableExtra(key) as? T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,19 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlin.math.roundToInt
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.ui.main.ThemeProvider
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object ThemeHelper {
|
||||
const val SYSTEM_BAR_ALPHA = 0.9f
|
||||
@@ -36,8 +34,8 @@ object ThemeHelper {
|
||||
// Using a specific night mode check because this could apply incorrectly when using the
|
||||
// light app mode, dark system mode, and black backgrounds. Launching the settings activity
|
||||
// will then show light mode colors/navigation bars but with black backgrounds.
|
||||
if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false)
|
||||
&& isNightMode(activity)
|
||||
if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) &&
|
||||
isNightMode(activity)
|
||||
) {
|
||||
activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark)
|
||||
}
|
||||
@@ -46,8 +44,10 @@ object ThemeHelper {
|
||||
@ColorInt
|
||||
fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int {
|
||||
return Color.argb(
|
||||
(alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color),
|
||||
Color.green(color), Color.blue(color)
|
||||
(alphaFactor * Color.alpha(color)).roundToInt(),
|
||||
Color.red(color),
|
||||
Color.green(color),
|
||||
Color.blue(color)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ class FixedRatioSurfaceView @JvmOverloads constructor(
|
||||
newWidth = width
|
||||
newHeight = (width / aspectRatio).roundToInt()
|
||||
}
|
||||
val left = (width - newWidth) / 2;
|
||||
val top = (height - newHeight) / 2;
|
||||
val left = (width - newWidth) / 2
|
||||
val top = (height - newHeight) / 2
|
||||
setLeftTopRightBottom(left, top, left + newWidth, top + newHeight)
|
||||
} else {
|
||||
setLeftTopRightBottom(0, 0, width, height)
|
||||
|
||||
@@ -560,6 +560,26 @@ void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeGpuDriver(
|
||||
GetJString(env, custom_driver_name), GetJString(env, file_redirect_dir));
|
||||
}
|
||||
|
||||
[[maybe_unused]] static bool CheckKgslPresent() {
|
||||
constexpr auto KgslPath{"/dev/kgsl-3d0"};
|
||||
|
||||
return access(KgslPath, F_OK) == 0;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool SupportsCustomDriver() {
|
||||
return android_get_device_api_level() >= 28 && CheckKgslPresent();
|
||||
}
|
||||
|
||||
jboolean JNICALL Java_org_yuzu_yuzu_1emu_utils_GpuDriverHelper_supportsCustomDriverLoading(
|
||||
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject instance) {
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
// If the KGSL device exists custom drivers can be loaded using adrenotools
|
||||
return SupportsCustomDriver();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_reloadKeys(JNIEnv* env,
|
||||
[[maybe_unused]] jclass clazz) {
|
||||
Core::Crypto::KeyManager::Instance().ReloadKeys();
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:minHeight="72dp"
|
||||
android:paddingVertical="@dimen/spacing_large"
|
||||
android:paddingStart="@dimen/spacing_large"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingVertical="@dimen/spacing_large">
|
||||
android:paddingEnd="24dp">
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_widget"
|
||||
@@ -19,32 +19,35 @@
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.BodySmall"
|
||||
android:id="@+id/text_setting_description"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignStart="@+id/text_setting_name"
|
||||
android:layout_below="@+id/text_setting_name"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:layout_toStartOf="@+id/switch_widget"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/frame_limit_enable_description" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.HeadlineMedium"
|
||||
android:id="@+id/text_setting_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_toStartOf="@+id/switch_widget"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="viewStart"
|
||||
app:lineHeight="28dp"
|
||||
tools:text="@string/frame_limit_enable" />
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_setting_name"
|
||||
style="@style/TextAppearance.Material3.HeadlineMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="16sp"
|
||||
app:lineHeight="28dp"
|
||||
tools:text="@string/frame_limit_enable" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/text_setting_description"
|
||||
style="@style/TextAppearance.Material3.BodySmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/frame_limit_enable_description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/text_header_name"
|
||||
style="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:paddingHorizontal="@dimen/spacing_large">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
style="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:id="@+id/text_header_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:textAlignment="viewStart"
|
||||
android:textStyle="bold"
|
||||
tools:text="CPU Settings" />
|
||||
|
||||
</FrameLayout>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:paddingHorizontal="@dimen/spacing_large"
|
||||
android:paddingVertical="16dp"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?attr/colorPrimary"
|
||||
android:textStyle="bold"
|
||||
tools:text="CPU Settings" />
|
||||
|
||||
@@ -236,4 +236,15 @@
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="outputEngineEntries">
|
||||
<item>@string/auto</item>
|
||||
<item>@string/cubeb</item>
|
||||
<item>@string/string_null</item>
|
||||
</string-array>
|
||||
<string-array name="outputEngineValues">
|
||||
<item>auto</item>
|
||||
<item>cubeb</item>
|
||||
<item>null</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
|
||||
<!-- General application strings -->
|
||||
<string name="app_name" translatable="false">yuzu</string>
|
||||
@@ -158,7 +158,6 @@
|
||||
<string name="set_custom_rtc">Set custom RTC</string>
|
||||
|
||||
<!-- Graphics settings strings -->
|
||||
<string name="renderer_api">API</string>
|
||||
<string name="renderer_accuracy">Accuracy level</string>
|
||||
<string name="renderer_resolution">Resolution (Handheld/Docked)</string>
|
||||
<string name="renderer_vsync">VSync mode</string>
|
||||
@@ -172,12 +171,21 @@
|
||||
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, reducing stutter but may introduce glitches.</string>
|
||||
<string name="renderer_reactive_flushing">Use reactive flushing</string>
|
||||
<string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string>
|
||||
<string name="renderer_debug">Graphics debugging</string>
|
||||
<string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string>
|
||||
<string name="use_disk_shader_cache">Disk shader cache</string>
|
||||
<string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string>
|
||||
|
||||
<!-- Debug settings strings -->
|
||||
<string name="cpu">CPU</string>
|
||||
<string name="cpu_debug_mode">CPU Debugging</string>
|
||||
<string name="cpu_debug_mode_description">Puts the CPU in a slow debugging mode.</string>
|
||||
<string name="gpu">GPU</string>
|
||||
<string name="renderer_api">API</string>
|
||||
<string name="renderer_debug">Graphics debugging</string>
|
||||
<string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string>
|
||||
<string name="fastmem">Fastmem</string>
|
||||
|
||||
<!-- Audio settings strings -->
|
||||
<string name="audio_output_engine">Output engine</string>
|
||||
<string name="audio_volume">Volume</string>
|
||||
<string name="audio_volume_description">Specifies the volume of audio output.</string>
|
||||
|
||||
@@ -196,6 +204,7 @@
|
||||
<string name="learn_more">Learn more</string>
|
||||
<string name="auto">Auto</string>
|
||||
<string name="submit">Submit</string>
|
||||
<string name="string_null">Null</string>
|
||||
|
||||
<!-- GPU driver installation -->
|
||||
<string name="select_gpu_driver">Select GPU driver</string>
|
||||
@@ -366,6 +375,9 @@
|
||||
<string name="theme_mode_light">Light</string>
|
||||
<string name="theme_mode_dark">Dark</string>
|
||||
|
||||
<!-- Audio output engines -->
|
||||
<string name="cubeb">cubeb</string>
|
||||
|
||||
<!-- Black backgrounds theme -->
|
||||
<string name="use_black_backgrounds">Black backgrounds</string>
|
||||
<string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
|
||||
|
||||
@@ -144,6 +143,7 @@ void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
|
||||
|
||||
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
|
||||
|
||||
// 0.12 seconds (2304000 / 19200000)
|
||||
constexpr u64 max_process_time{2'304'000ULL};
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
@@ -184,8 +184,7 @@ void AudioRenderer::ThreadFunc(std::stop_token stop_token) {
|
||||
u64 max_time{max_process_time};
|
||||
if (index == 1 && command_buffer.applet_resource_user_id ==
|
||||
mailbox->GetCommandBuffer(0).applet_resource_user_id) {
|
||||
max_time = max_process_time -
|
||||
Core::Timing::CyclesToNs(render_times_taken[0]).count();
|
||||
max_time = max_process_time - render_times_taken[0];
|
||||
if (render_times_taken[0] > max_process_time) {
|
||||
max_time = 0;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer::ADSP {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "audio_core/renderer/command/performance/performance.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
@@ -18,20 +17,18 @@ void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
|
||||
auto base{entry_address.translated_address};
|
||||
if (state == PerformanceState::Start) {
|
||||
auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
|
||||
*start_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
*start_time_ptr =
|
||||
static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
|
||||
processor.current_processing_time);
|
||||
} else if (state == PerformanceState::Stop) {
|
||||
auto processed_time_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
|
||||
auto entry_count_ptr{
|
||||
reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
|
||||
|
||||
*processed_time_ptr = static_cast<u32>(
|
||||
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
|
||||
processor.start_time - processor.current_processing_time)
|
||||
.count());
|
||||
*processed_time_ptr =
|
||||
static_cast<u32>(processor.system->CoreTiming().GetClockTicks() - processor.start_time -
|
||||
processor.current_processing_time);
|
||||
(*entry_count_ptr)++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
namespace AudioCore::Sink {
|
||||
|
||||
|
||||
@@ -172,6 +172,8 @@ if(ARCHITECTURE_x86_64)
|
||||
x64/cpu_wait.h
|
||||
x64/native_clock.cpp
|
||||
x64/native_clock.h
|
||||
x64/rdtsc.cpp
|
||||
x64/rdtsc.h
|
||||
x64/xbyak_abi.h
|
||||
x64/xbyak_util.h
|
||||
)
|
||||
|
||||
@@ -483,6 +483,7 @@ struct Values {
|
||||
AstcRecompression::Uncompressed, AstcRecompression::Uncompressed, AstcRecompression::Bc3,
|
||||
"astc_recompression"};
|
||||
SwitchableSetting<bool> use_video_framerate{false, "use_video_framerate"};
|
||||
SwitchableSetting<bool> barrier_feedback_loops{true, "barrier_feedback_loops"};
|
||||
|
||||
SwitchableSetting<u8> bg_red{0, "bg_red"};
|
||||
SwitchableSetting<u8> bg_green{0, "bg_green"};
|
||||
|
||||
@@ -28,13 +28,12 @@ static s64 GetSystemTimeNS() {
|
||||
// GetSystemTimePreciseAsFileTime returns the file time in 100ns units.
|
||||
static constexpr s64 Multiplier = 100;
|
||||
// Convert Windows epoch to Unix epoch.
|
||||
static constexpr s64 WindowsEpochToUnixEpochNS = 0x19DB1DED53E8000LL;
|
||||
static constexpr s64 WindowsEpochToUnixEpoch = 0x19DB1DED53E8000LL;
|
||||
|
||||
FILETIME filetime;
|
||||
GetSystemTimePreciseAsFileTime(&filetime);
|
||||
return Multiplier * ((static_cast<s64>(filetime.dwHighDateTime) << 32) +
|
||||
static_cast<s64>(filetime.dwLowDateTime)) -
|
||||
WindowsEpochToUnixEpochNS;
|
||||
static_cast<s64>(filetime.dwLowDateTime) - WindowsEpochToUnixEpoch);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public:
|
||||
is_set = false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsSet() {
|
||||
[[nodiscard]] bool IsSet() const {
|
||||
return is_set;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,88 +2,75 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
class StandardWallClock final : public WallClock {
|
||||
public:
|
||||
explicit StandardWallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_)
|
||||
: WallClock{emulated_cpu_frequency_, emulated_clock_frequency_, false},
|
||||
start_time{SteadyClock::Now()} {}
|
||||
explicit StandardWallClock() : start_time{SteadyClock::Now()} {}
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() override {
|
||||
std::chrono::nanoseconds GetTimeNS() const override {
|
||||
return SteadyClock::Now() - start_time;
|
||||
}
|
||||
|
||||
std::chrono::microseconds GetTimeUS() override {
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(GetTimeNS());
|
||||
std::chrono::microseconds GetTimeUS() const override {
|
||||
return static_cast<std::chrono::microseconds>(GetHostTicksElapsed() / NsToUsRatio::den);
|
||||
}
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() override {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(GetTimeNS());
|
||||
std::chrono::milliseconds GetTimeMS() const override {
|
||||
return static_cast<std::chrono::milliseconds>(GetHostTicksElapsed() / NsToMsRatio::den);
|
||||
}
|
||||
|
||||
u64 GetClockCycles() override {
|
||||
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_clock_frequency);
|
||||
return Common::Divide128On32(temp, NS_RATIO).first;
|
||||
u64 GetCNTPCT() const override {
|
||||
return GetHostTicksElapsed() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
|
||||
}
|
||||
|
||||
u64 GetCPUCycles() override {
|
||||
const u128 temp = Common::Multiply64Into128(GetTimeNS().count(), emulated_cpu_frequency);
|
||||
return Common::Divide128On32(temp, NS_RATIO).first;
|
||||
u64 GetGPUTick() const override {
|
||||
return GetHostTicksElapsed() * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
void Pause([[maybe_unused]] bool is_paused) override {
|
||||
// Do nothing in this clock type.
|
||||
u64 GetHostTicksNow() const override {
|
||||
return static_cast<u64>(SteadyClock::Now().time_since_epoch().count());
|
||||
}
|
||||
|
||||
u64 GetHostTicksElapsed() const override {
|
||||
return static_cast<u64>(GetTimeNS().count());
|
||||
}
|
||||
|
||||
bool IsNative() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
SteadyClock::time_point start_time;
|
||||
};
|
||||
|
||||
std::unique_ptr<WallClock> CreateOptimalClock() {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
||||
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
const auto& caps = GetCPUCaps();
|
||||
u64 rtsc_frequency = 0;
|
||||
if (caps.invariant_tsc) {
|
||||
rtsc_frequency = caps.tsc_frequency ? caps.tsc_frequency : EstimateRDTSCFrequency();
|
||||
}
|
||||
|
||||
// Fallback to StandardWallClock if the hardware TSC does not have the precision greater than:
|
||||
// - A nanosecond
|
||||
// - The emulated CPU frequency
|
||||
// - The emulated clock counter frequency (CNTFRQ)
|
||||
if (rtsc_frequency <= WallClock::NS_RATIO || rtsc_frequency <= emulated_cpu_frequency ||
|
||||
rtsc_frequency <= emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
|
||||
emulated_clock_frequency);
|
||||
if (caps.invariant_tsc && caps.tsc_frequency >= WallClock::GPUTickFreq) {
|
||||
return std::make_unique<X64::NativeClock>(caps.tsc_frequency);
|
||||
} else {
|
||||
return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency,
|
||||
rtsc_frequency);
|
||||
// Fallback to StandardWallClock if the hardware TSC
|
||||
// - Is not invariant
|
||||
// - Is not more precise than GPUTickFreq
|
||||
return std::make_unique<StandardWallClock>();
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
|
||||
return std::make_unique<StandardWallClock>();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock() {
|
||||
return std::make_unique<StandardWallClock>();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <ratio>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -12,50 +13,82 @@ namespace Common {
|
||||
|
||||
class WallClock {
|
||||
public:
|
||||
static constexpr u64 NS_RATIO = 1'000'000'000;
|
||||
static constexpr u64 US_RATIO = 1'000'000;
|
||||
static constexpr u64 MS_RATIO = 1'000;
|
||||
static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz
|
||||
static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz
|
||||
static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz
|
||||
|
||||
virtual ~WallClock() = default;
|
||||
|
||||
/// Returns current wall time in nanoseconds
|
||||
[[nodiscard]] virtual std::chrono::nanoseconds GetTimeNS() = 0;
|
||||
/// @returns The time in nanoseconds since the construction of this clock.
|
||||
virtual std::chrono::nanoseconds GetTimeNS() const = 0;
|
||||
|
||||
/// Returns current wall time in microseconds
|
||||
[[nodiscard]] virtual std::chrono::microseconds GetTimeUS() = 0;
|
||||
/// @returns The time in microseconds since the construction of this clock.
|
||||
virtual std::chrono::microseconds GetTimeUS() const = 0;
|
||||
|
||||
/// Returns current wall time in milliseconds
|
||||
[[nodiscard]] virtual std::chrono::milliseconds GetTimeMS() = 0;
|
||||
/// @returns The time in milliseconds since the construction of this clock.
|
||||
virtual std::chrono::milliseconds GetTimeMS() const = 0;
|
||||
|
||||
/// Returns current wall time in emulated clock cycles
|
||||
[[nodiscard]] virtual u64 GetClockCycles() = 0;
|
||||
/// @returns The guest CNTPCT ticks since the construction of this clock.
|
||||
virtual u64 GetCNTPCT() const = 0;
|
||||
|
||||
/// Returns current wall time in emulated cpu cycles
|
||||
[[nodiscard]] virtual u64 GetCPUCycles() = 0;
|
||||
/// @returns The guest GPU ticks since the construction of this clock.
|
||||
virtual u64 GetGPUTick() const = 0;
|
||||
|
||||
virtual void Pause(bool is_paused) = 0;
|
||||
/// @returns The raw host timer ticks since an indeterminate epoch.
|
||||
virtual u64 GetHostTicksNow() const = 0;
|
||||
|
||||
/// Tells if the wall clock, uses the host CPU's hardware clock
|
||||
[[nodiscard]] bool IsNative() const {
|
||||
return is_native;
|
||||
/// @returns The raw host timer ticks since the construction of this clock.
|
||||
virtual u64 GetHostTicksElapsed() const = 0;
|
||||
|
||||
/// @returns Whether the clock directly uses the host's hardware clock.
|
||||
virtual bool IsNative() const = 0;
|
||||
|
||||
static inline u64 NSToCNTPCT(u64 ns) {
|
||||
return ns * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den;
|
||||
}
|
||||
|
||||
static inline u64 NSToGPUTick(u64 ns) {
|
||||
return ns * NsToGPUTickRatio::num / NsToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
// Cycle Timing
|
||||
|
||||
static inline u64 CPUTickToNS(u64 cpu_tick) {
|
||||
return cpu_tick * CPUTickToNsRatio::num / CPUTickToNsRatio::den;
|
||||
}
|
||||
|
||||
static inline u64 CPUTickToUS(u64 cpu_tick) {
|
||||
return cpu_tick * CPUTickToUsRatio::num / CPUTickToUsRatio::den;
|
||||
}
|
||||
|
||||
static inline u64 CPUTickToCNTPCT(u64 cpu_tick) {
|
||||
return cpu_tick * CPUTickToCNTPCTRatio::num / CPUTickToCNTPCTRatio::den;
|
||||
}
|
||||
|
||||
static inline u64 CPUTickToGPUTick(u64 cpu_tick) {
|
||||
return cpu_tick * CPUTickToGPUTickRatio::num / CPUTickToGPUTickRatio::den;
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit WallClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_, bool is_native_)
|
||||
: emulated_cpu_frequency{emulated_cpu_frequency_},
|
||||
emulated_clock_frequency{emulated_clock_frequency_}, is_native{is_native_} {}
|
||||
using NsRatio = std::nano;
|
||||
using UsRatio = std::micro;
|
||||
using MsRatio = std::milli;
|
||||
|
||||
u64 emulated_cpu_frequency;
|
||||
u64 emulated_clock_frequency;
|
||||
using NsToUsRatio = std::ratio_divide<std::nano, std::micro>;
|
||||
using NsToMsRatio = std::ratio_divide<std::nano, std::milli>;
|
||||
using NsToCNTPCTRatio = std::ratio<CNTFRQ, std::nano::den>;
|
||||
using NsToGPUTickRatio = std::ratio<GPUTickFreq, std::nano::den>;
|
||||
|
||||
private:
|
||||
bool is_native;
|
||||
// Cycle Timing
|
||||
|
||||
using CPUTickToNsRatio = std::ratio<std::nano::den, CPUTickFreq>;
|
||||
using CPUTickToUsRatio = std::ratio<std::micro::den, CPUTickFreq>;
|
||||
using CPUTickToCNTPCTRatio = std::ratio<CNTFRQ, CPUTickFreq>;
|
||||
using CPUTickToGPUTickRatio = std::ratio<GPUTickFreq, CPUTickFreq>;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::unique_ptr<WallClock> CreateBestMatchingClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency);
|
||||
std::unique_ptr<WallClock> CreateOptimalClock();
|
||||
|
||||
[[nodiscard]] std::unique_ptr<WallClock> CreateStandardWallClock(u64 emulated_cpu_frequency,
|
||||
u64 emulated_clock_frequency);
|
||||
std::unique_ptr<WallClock> CreateStandardWallClock();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
@@ -187,6 +188,8 @@ static CPUCaps Detect() {
|
||||
caps.tsc_frequency = static_cast<u64>(caps.crystal_frequency) *
|
||||
caps.tsc_crystal_ratio_numerator /
|
||||
caps.tsc_crystal_ratio_denominator;
|
||||
} else {
|
||||
caps.tsc_frequency = X64::EstimateRDTSCFrequency();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,19 +9,11 @@
|
||||
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/cpu_wait.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__forceinline static u64 FencedRDTSC() {
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
const u64 result = __rdtsc();
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
return result;
|
||||
}
|
||||
|
||||
__forceinline static void TPAUSE() {
|
||||
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
|
||||
// For reference:
|
||||
@@ -32,16 +24,6 @@ __forceinline static void TPAUSE() {
|
||||
_tpause(0, FencedRDTSC() + PauseCycles);
|
||||
}
|
||||
#else
|
||||
static u64 FencedRDTSC() {
|
||||
u64 eax;
|
||||
u64 edx;
|
||||
asm volatile("lfence\n\t"
|
||||
"rdtsc\n\t"
|
||||
"lfence\n\t"
|
||||
: "=a"(eax), "=d"(edx));
|
||||
return (edx << 32) | eax;
|
||||
}
|
||||
|
||||
static void TPAUSE() {
|
||||
// 100,000 cycles is a reasonable amount of time to wait to save on CPU resources.
|
||||
// For reference:
|
||||
|
||||
@@ -1,164 +1,50 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "common/atomic_ops.h"
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
namespace Common::X64 {
|
||||
|
||||
namespace Common {
|
||||
NativeClock::NativeClock(u64 rdtsc_frequency_)
|
||||
: start_ticks{FencedRDTSC()}, rdtsc_frequency{rdtsc_frequency_},
|
||||
ns_rdtsc_factor{GetFixedPoint64Factor(NsRatio::den, rdtsc_frequency)},
|
||||
us_rdtsc_factor{GetFixedPoint64Factor(UsRatio::den, rdtsc_frequency)},
|
||||
ms_rdtsc_factor{GetFixedPoint64Factor(MsRatio::den, rdtsc_frequency)},
|
||||
cntpct_rdtsc_factor{GetFixedPoint64Factor(CNTFRQ, rdtsc_frequency)},
|
||||
gputick_rdtsc_factor{GetFixedPoint64Factor(GPUTickFreq, rdtsc_frequency)} {}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__forceinline static u64 FencedRDTSC() {
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
const u64 result = __rdtsc();
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
static u64 FencedRDTSC() {
|
||||
u64 eax;
|
||||
u64 edx;
|
||||
asm volatile("lfence\n\t"
|
||||
"rdtsc\n\t"
|
||||
"lfence\n\t"
|
||||
: "=a"(eax), "=d"(edx));
|
||||
return (edx << 32) | eax;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <u64 Nearest>
|
||||
static u64 RoundToNearest(u64 value) {
|
||||
const auto mod = value % Nearest;
|
||||
return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
|
||||
std::chrono::nanoseconds NativeClock::GetTimeNS() const {
|
||||
return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_rdtsc_factor)};
|
||||
}
|
||||
|
||||
u64 EstimateRDTSCFrequency() {
|
||||
// Discard the first result measuring the rdtsc.
|
||||
FencedRDTSC();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
FencedRDTSC();
|
||||
|
||||
// Get the current time.
|
||||
const auto start_time = Common::RealTimeClock::Now();
|
||||
const u64 tsc_start = FencedRDTSC();
|
||||
// Wait for 250 milliseconds.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{250});
|
||||
const auto end_time = Common::RealTimeClock::Now();
|
||||
const u64 tsc_end = FencedRDTSC();
|
||||
// Calculate differences.
|
||||
const u64 timer_diff = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
|
||||
const u64 tsc_diff = tsc_end - tsc_start;
|
||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||
return RoundToNearest<1000>(tsc_freq);
|
||||
std::chrono::microseconds NativeClock::GetTimeUS() const {
|
||||
return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_rdtsc_factor)};
|
||||
}
|
||||
|
||||
namespace X64 {
|
||||
NativeClock::NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
|
||||
u64 rtsc_frequency_)
|
||||
: WallClock(emulated_cpu_frequency_, emulated_clock_frequency_, true), rtsc_frequency{
|
||||
rtsc_frequency_} {
|
||||
// Thread to re-adjust the RDTSC frequency after 10 seconds has elapsed.
|
||||
time_sync_thread = std::jthread{[this](std::stop_token token) {
|
||||
// Get the current time.
|
||||
const auto start_time = Common::RealTimeClock::Now();
|
||||
const u64 tsc_start = FencedRDTSC();
|
||||
// Wait for 10 seconds.
|
||||
if (!Common::StoppableTimedWait(token, std::chrono::seconds{10})) {
|
||||
return;
|
||||
}
|
||||
const auto end_time = Common::RealTimeClock::Now();
|
||||
const u64 tsc_end = FencedRDTSC();
|
||||
// Calculate differences.
|
||||
const u64 timer_diff = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
|
||||
const u64 tsc_diff = tsc_end - tsc_start;
|
||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||
rtsc_frequency = tsc_freq;
|
||||
CalculateAndSetFactors();
|
||||
}};
|
||||
|
||||
time_point.inner.last_measure = FencedRDTSC();
|
||||
time_point.inner.accumulated_ticks = 0U;
|
||||
CalculateAndSetFactors();
|
||||
std::chrono::milliseconds NativeClock::GetTimeMS() const {
|
||||
return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_rdtsc_factor)};
|
||||
}
|
||||
|
||||
u64 NativeClock::GetRTSC() {
|
||||
TimePoint new_time_point{};
|
||||
TimePoint current_time_point{};
|
||||
|
||||
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
|
||||
do {
|
||||
const u64 current_measure = FencedRDTSC();
|
||||
u64 diff = current_measure - current_time_point.inner.last_measure;
|
||||
diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
|
||||
new_time_point.inner.last_measure = current_measure > current_time_point.inner.last_measure
|
||||
? current_measure
|
||||
: current_time_point.inner.last_measure;
|
||||
new_time_point.inner.accumulated_ticks = current_time_point.inner.accumulated_ticks + diff;
|
||||
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
|
||||
current_time_point.pack, current_time_point.pack));
|
||||
return new_time_point.inner.accumulated_ticks;
|
||||
u64 NativeClock::GetCNTPCT() const {
|
||||
return MultiplyHigh(GetHostTicksElapsed(), cntpct_rdtsc_factor);
|
||||
}
|
||||
|
||||
void NativeClock::Pause(bool is_paused) {
|
||||
if (!is_paused) {
|
||||
TimePoint current_time_point{};
|
||||
TimePoint new_time_point{};
|
||||
|
||||
current_time_point.pack = Common::AtomicLoad128(time_point.pack.data());
|
||||
do {
|
||||
new_time_point.pack = current_time_point.pack;
|
||||
new_time_point.inner.last_measure = FencedRDTSC();
|
||||
} while (!Common::AtomicCompareAndSwap(time_point.pack.data(), new_time_point.pack,
|
||||
current_time_point.pack, current_time_point.pack));
|
||||
}
|
||||
u64 NativeClock::GetGPUTick() const {
|
||||
return MultiplyHigh(GetHostTicksElapsed(), gputick_rdtsc_factor);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds NativeClock::GetTimeNS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::nanoseconds{MultiplyHigh(rtsc_value, ns_rtsc_factor)};
|
||||
u64 NativeClock::GetHostTicksNow() const {
|
||||
return FencedRDTSC();
|
||||
}
|
||||
|
||||
std::chrono::microseconds NativeClock::GetTimeUS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::microseconds{MultiplyHigh(rtsc_value, us_rtsc_factor)};
|
||||
u64 NativeClock::GetHostTicksElapsed() const {
|
||||
return FencedRDTSC() - start_ticks;
|
||||
}
|
||||
|
||||
std::chrono::milliseconds NativeClock::GetTimeMS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::milliseconds{MultiplyHigh(rtsc_value, ms_rtsc_factor)};
|
||||
bool NativeClock::IsNative() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 NativeClock::GetClockCycles() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return MultiplyHigh(rtsc_value, clock_rtsc_factor);
|
||||
}
|
||||
|
||||
u64 NativeClock::GetCPUCycles() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return MultiplyHigh(rtsc_value, cpu_rtsc_factor);
|
||||
}
|
||||
|
||||
void NativeClock::CalculateAndSetFactors() {
|
||||
ns_rtsc_factor = GetFixedPoint64Factor(NS_RATIO, rtsc_frequency);
|
||||
us_rtsc_factor = GetFixedPoint64Factor(US_RATIO, rtsc_frequency);
|
||||
ms_rtsc_factor = GetFixedPoint64Factor(MS_RATIO, rtsc_frequency);
|
||||
clock_rtsc_factor = GetFixedPoint64Factor(emulated_clock_frequency, rtsc_frequency);
|
||||
cpu_rtsc_factor = GetFixedPoint64Factor(emulated_cpu_frequency, rtsc_frequency);
|
||||
}
|
||||
|
||||
} // namespace X64
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Common::X64
|
||||
|
||||
@@ -3,58 +3,39 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
namespace Common {
|
||||
namespace Common::X64 {
|
||||
|
||||
namespace X64 {
|
||||
class NativeClock final : public WallClock {
|
||||
public:
|
||||
explicit NativeClock(u64 emulated_cpu_frequency_, u64 emulated_clock_frequency_,
|
||||
u64 rtsc_frequency_);
|
||||
explicit NativeClock(u64 rdtsc_frequency_);
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() override;
|
||||
std::chrono::nanoseconds GetTimeNS() const override;
|
||||
|
||||
std::chrono::microseconds GetTimeUS() override;
|
||||
std::chrono::microseconds GetTimeUS() const override;
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() override;
|
||||
std::chrono::milliseconds GetTimeMS() const override;
|
||||
|
||||
u64 GetClockCycles() override;
|
||||
u64 GetCNTPCT() const override;
|
||||
|
||||
u64 GetCPUCycles() override;
|
||||
u64 GetGPUTick() const override;
|
||||
|
||||
void Pause(bool is_paused) override;
|
||||
u64 GetHostTicksNow() const override;
|
||||
|
||||
u64 GetHostTicksElapsed() const override;
|
||||
|
||||
bool IsNative() const override;
|
||||
|
||||
private:
|
||||
u64 GetRTSC();
|
||||
u64 start_ticks;
|
||||
u64 rdtsc_frequency;
|
||||
|
||||
void CalculateAndSetFactors();
|
||||
|
||||
union alignas(16) TimePoint {
|
||||
TimePoint() : pack{} {}
|
||||
u128 pack{};
|
||||
struct Inner {
|
||||
u64 last_measure{};
|
||||
u64 accumulated_ticks{};
|
||||
} inner;
|
||||
};
|
||||
|
||||
TimePoint time_point;
|
||||
|
||||
// factors
|
||||
u64 clock_rtsc_factor{};
|
||||
u64 cpu_rtsc_factor{};
|
||||
u64 ns_rtsc_factor{};
|
||||
u64 us_rtsc_factor{};
|
||||
u64 ms_rtsc_factor{};
|
||||
|
||||
u64 rtsc_frequency;
|
||||
|
||||
std::jthread time_sync_thread;
|
||||
u64 ns_rdtsc_factor;
|
||||
u64 us_rdtsc_factor;
|
||||
u64 ms_rdtsc_factor;
|
||||
u64 cntpct_rdtsc_factor;
|
||||
u64 gputick_rdtsc_factor;
|
||||
};
|
||||
} // namespace X64
|
||||
|
||||
u64 EstimateRDTSCFrequency();
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Common::X64
|
||||
|
||||
39
src/common/x64/rdtsc.cpp
Normal file
39
src/common/x64/rdtsc.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "common/steady_clock.h"
|
||||
#include "common/uint128.h"
|
||||
#include "common/x64/rdtsc.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
template <u64 Nearest>
|
||||
static u64 RoundToNearest(u64 value) {
|
||||
const auto mod = value % Nearest;
|
||||
return mod >= (Nearest / 2) ? (value - mod + Nearest) : (value - mod);
|
||||
}
|
||||
|
||||
u64 EstimateRDTSCFrequency() {
|
||||
// Discard the first result measuring the rdtsc.
|
||||
FencedRDTSC();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{1});
|
||||
FencedRDTSC();
|
||||
|
||||
// Get the current time.
|
||||
const auto start_time = RealTimeClock::Now();
|
||||
const u64 tsc_start = FencedRDTSC();
|
||||
// Wait for 100 milliseconds.
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{100});
|
||||
const auto end_time = RealTimeClock::Now();
|
||||
const u64 tsc_end = FencedRDTSC();
|
||||
// Calculate differences.
|
||||
const u64 timer_diff = static_cast<u64>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count());
|
||||
const u64 tsc_diff = tsc_end - tsc_start;
|
||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||
return RoundToNearest<100'000>(tsc_freq);
|
||||
}
|
||||
|
||||
} // namespace Common::X64
|
||||
37
src/common/x64/rdtsc.h
Normal file
37
src/common/x64/rdtsc.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__forceinline static u64 FencedRDTSC() {
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
const u64 result = __rdtsc();
|
||||
_mm_lfence();
|
||||
_ReadWriteBarrier();
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
static inline u64 FencedRDTSC() {
|
||||
u64 eax;
|
||||
u64 edx;
|
||||
asm volatile("lfence\n\t"
|
||||
"rdtsc\n\t"
|
||||
"lfence\n\t"
|
||||
: "=a"(eax), "=d"(edx));
|
||||
return (edx << 32) | eax;
|
||||
}
|
||||
#endif
|
||||
|
||||
u64 EstimateRDTSCFrequency();
|
||||
|
||||
} // namespace Common::X64
|
||||
@@ -14,7 +14,6 @@ add_library(core STATIC
|
||||
core.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
core_timing_util.h
|
||||
cpu_manager.cpp
|
||||
cpu_manager.h
|
||||
crypto/aes_util.cpp
|
||||
|
||||
@@ -16,12 +16,11 @@
|
||||
|
||||
#include "common/microprofile.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hardware_properties.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
constexpr s64 MAX_SLICE_LENGTH = 4000;
|
||||
constexpr s64 MAX_SLICE_LENGTH = 10000;
|
||||
|
||||
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
|
||||
return std::make_shared<EventType>(std::move(callback), std::move(name));
|
||||
@@ -45,9 +44,7 @@ struct CoreTiming::Event {
|
||||
}
|
||||
};
|
||||
|
||||
CoreTiming::CoreTiming()
|
||||
: cpu_clock{Common::CreateBestMatchingClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)},
|
||||
event_clock{Common::CreateStandardWallClock(Hardware::BASE_CLOCK_RATE, Hardware::CNTFREQ)} {}
|
||||
CoreTiming::CoreTiming() : clock{Common::CreateOptimalClock()} {}
|
||||
|
||||
CoreTiming::~CoreTiming() {
|
||||
Reset();
|
||||
@@ -68,7 +65,7 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
|
||||
on_thread_init = std::move(on_thread_init_);
|
||||
event_fifo_id = 0;
|
||||
shutting_down = false;
|
||||
ticks = 0;
|
||||
cpu_ticks = 0;
|
||||
const auto empty_timed_callback = [](std::uintptr_t, u64, std::chrono::nanoseconds)
|
||||
-> std::optional<std::chrono::nanoseconds> { return std::nullopt; };
|
||||
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
|
||||
@@ -173,38 +170,30 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
|
||||
}
|
||||
|
||||
void CoreTiming::AddTicks(u64 ticks_to_add) {
|
||||
ticks += ticks_to_add;
|
||||
downcount -= static_cast<s64>(ticks);
|
||||
cpu_ticks += ticks_to_add;
|
||||
downcount -= static_cast<s64>(cpu_ticks);
|
||||
}
|
||||
|
||||
void CoreTiming::Idle() {
|
||||
if (!event_queue.empty()) {
|
||||
const u64 next_event_time = event_queue.front().time;
|
||||
const u64 next_ticks = nsToCycles(std::chrono::nanoseconds(next_event_time)) + 10U;
|
||||
if (next_ticks > ticks) {
|
||||
ticks = next_ticks;
|
||||
}
|
||||
return;
|
||||
}
|
||||
ticks += 1000U;
|
||||
cpu_ticks += 1000U;
|
||||
}
|
||||
|
||||
void CoreTiming::ResetTicks() {
|
||||
downcount = MAX_SLICE_LENGTH;
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetCPUTicks() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return cpu_clock->GetCPUCycles();
|
||||
}
|
||||
return ticks;
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetClockTicks() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return cpu_clock->GetClockCycles();
|
||||
return clock->GetCNTPCT();
|
||||
}
|
||||
return CpuCyclesToClockCycles(ticks);
|
||||
return Common::WallClock::CPUTickToCNTPCT(cpu_ticks);
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetGPUTicks() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return clock->GetGPUTick();
|
||||
}
|
||||
return Common::WallClock::CPUTickToGPUTick(cpu_ticks);
|
||||
}
|
||||
|
||||
std::optional<s64> CoreTiming::Advance() {
|
||||
@@ -297,9 +286,7 @@ void CoreTiming::ThreadLoop() {
|
||||
}
|
||||
|
||||
paused_set = true;
|
||||
event_clock->Pause(true);
|
||||
pause_event.Wait();
|
||||
event_clock->Pause(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,25 +302,18 @@ void CoreTiming::Reset() {
|
||||
has_started = false;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds CoreTiming::GetCPUTimeNs() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return cpu_clock->GetTimeNS();
|
||||
}
|
||||
return CyclesToNs(ticks);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return event_clock->GetTimeNS();
|
||||
return clock->GetTimeNS();
|
||||
}
|
||||
return CyclesToNs(ticks);
|
||||
return std::chrono::nanoseconds{Common::WallClock::CPUTickToNS(cpu_ticks)};
|
||||
}
|
||||
|
||||
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
|
||||
if (is_multicore) [[likely]] {
|
||||
return event_clock->GetTimeUS();
|
||||
return clock->GetTimeUS();
|
||||
}
|
||||
return CyclesToUs(ticks);
|
||||
return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -116,14 +116,11 @@ public:
|
||||
return downcount;
|
||||
}
|
||||
|
||||
/// Returns current time in emulated CPU cycles
|
||||
u64 GetCPUTicks() const;
|
||||
|
||||
/// Returns current time in emulated in Clock cycles
|
||||
/// Returns the current CNTPCT tick value.
|
||||
u64 GetClockTicks() const;
|
||||
|
||||
/// Returns current time in nanoseconds.
|
||||
std::chrono::nanoseconds GetCPUTimeNs() const;
|
||||
/// Returns the current GPU tick value.
|
||||
u64 GetGPUTicks() const;
|
||||
|
||||
/// Returns current time in microseconds.
|
||||
std::chrono::microseconds GetGlobalTimeUs() const;
|
||||
@@ -142,8 +139,7 @@ private:
|
||||
|
||||
void Reset();
|
||||
|
||||
std::unique_ptr<Common::WallClock> cpu_clock;
|
||||
std::unique_ptr<Common::WallClock> event_clock;
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
|
||||
s64 global_timer = 0;
|
||||
|
||||
@@ -171,7 +167,7 @@ private:
|
||||
s64 pause_end_time{};
|
||||
|
||||
/// Cycle timing
|
||||
u64 ticks{};
|
||||
u64 cpu_ticks{};
|
||||
s64 downcount{};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hardware_properties.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
namespace detail {
|
||||
constexpr u64 CNTFREQ_ADJUSTED = Hardware::CNTFREQ / 1000;
|
||||
constexpr u64 BASE_CLOCK_RATE_ADJUSTED = Hardware::BASE_CLOCK_RATE / 1000;
|
||||
} // namespace detail
|
||||
|
||||
[[nodiscard]] constexpr s64 msToCycles(std::chrono::milliseconds ms) {
|
||||
return ms.count() * detail::BASE_CLOCK_RATE_ADJUSTED;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr s64 usToCycles(std::chrono::microseconds us) {
|
||||
return us.count() * detail::BASE_CLOCK_RATE_ADJUSTED / 1000;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr s64 nsToCycles(std::chrono::nanoseconds ns) {
|
||||
return ns.count() * detail::BASE_CLOCK_RATE_ADJUSTED / 1000000;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u64 msToClockCycles(std::chrono::milliseconds ms) {
|
||||
return static_cast<u64>(ms.count()) * detail::CNTFREQ_ADJUSTED;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u64 usToClockCycles(std::chrono::microseconds us) {
|
||||
return us.count() * detail::CNTFREQ_ADJUSTED / 1000;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u64 nsToClockCycles(std::chrono::nanoseconds ns) {
|
||||
return ns.count() * detail::CNTFREQ_ADJUSTED / 1000000;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr u64 CpuCyclesToClockCycles(u64 ticks) {
|
||||
return ticks * detail::CNTFREQ_ADJUSTED / detail::BASE_CLOCK_RATE_ADJUSTED;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::chrono::milliseconds CyclesToMs(s64 cycles) {
|
||||
return std::chrono::milliseconds(cycles / detail::BASE_CLOCK_RATE_ADJUSTED);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::chrono::nanoseconds CyclesToNs(s64 cycles) {
|
||||
return std::chrono::nanoseconds(cycles * 1000000 / detail::BASE_CLOCK_RATE_ADJUSTED);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::chrono::microseconds CyclesToUs(s64 cycles) {
|
||||
return std::chrono::microseconds(cycles * 1000 / detail::BASE_CLOCK_RATE_ADJUSTED);
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
||||
@@ -150,23 +150,29 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t
|
||||
while (cur_length > 0 && it != concatenation_map.end()) {
|
||||
// Check if we can read the file at this position.
|
||||
const auto& file = it->file;
|
||||
const u64 file_offset = it->offset;
|
||||
const u64 map_offset = it->offset;
|
||||
const u64 file_size = file->GetSize();
|
||||
|
||||
if (cur_offset >= file_offset + file_size) {
|
||||
if (cur_offset > map_offset + file_size) {
|
||||
// Entirely out of bounds read.
|
||||
break;
|
||||
}
|
||||
|
||||
// Read the file at this position.
|
||||
const u64 intended_read_size = std::min<u64>(cur_length, file_size);
|
||||
const u64 file_seek = cur_offset - map_offset;
|
||||
const u64 intended_read_size = std::min<u64>(cur_length, file_size - file_seek);
|
||||
const u64 actual_read_size =
|
||||
file->Read(data + (cur_offset - offset), intended_read_size, cur_offset - file_offset);
|
||||
file->Read(data + (cur_offset - offset), intended_read_size, file_seek);
|
||||
|
||||
// Update tracking.
|
||||
cur_offset += actual_read_size;
|
||||
cur_length -= actual_read_size;
|
||||
it++;
|
||||
|
||||
// If we encountered a short read, we're done.
|
||||
if (actual_read_size < intended_read_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cur_offset - offset;
|
||||
|
||||
@@ -184,7 +184,8 @@ u64 KScheduler::UpdateHighestPriorityThread(KThread* highest_thread) {
|
||||
prev_highest_thread != highest_thread) [[likely]] {
|
||||
if (prev_highest_thread != nullptr) [[likely]] {
|
||||
IncrementScheduledCount(prev_highest_thread);
|
||||
prev_highest_thread->SetLastScheduledTick(m_kernel.System().CoreTiming().GetCPUTicks());
|
||||
prev_highest_thread->SetLastScheduledTick(
|
||||
m_kernel.System().CoreTiming().GetClockTicks());
|
||||
}
|
||||
if (m_state.should_count_idle) {
|
||||
if (highest_thread != nullptr) [[likely]] {
|
||||
@@ -351,7 +352,7 @@ void KScheduler::SwitchThread(KThread* next_thread) {
|
||||
|
||||
// Update the CPU time tracking variables.
|
||||
const s64 prev_tick = m_last_context_switch_time;
|
||||
const s64 cur_tick = m_kernel.System().CoreTiming().GetCPUTicks();
|
||||
const s64 cur_tick = m_kernel.System().CoreTiming().GetClockTicks();
|
||||
const s64 tick_diff = cur_tick - prev_tick;
|
||||
cur_thread->AddCpuTime(m_core_id, tick_diff);
|
||||
if (cur_process != nullptr) {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cinttypes>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
@@ -1313,7 +1315,8 @@ void KThread::RequestDummyThreadWait() {
|
||||
ASSERT(this->IsDummyThread());
|
||||
|
||||
// We will block when the scheduler lock is released.
|
||||
m_dummy_thread_runnable.store(false);
|
||||
std::scoped_lock lock{m_dummy_thread_mutex};
|
||||
m_dummy_thread_runnable = false;
|
||||
}
|
||||
|
||||
void KThread::DummyThreadBeginWait() {
|
||||
@@ -1323,7 +1326,8 @@ void KThread::DummyThreadBeginWait() {
|
||||
}
|
||||
|
||||
// Block until runnable is no longer false.
|
||||
m_dummy_thread_runnable.wait(false);
|
||||
std::unique_lock lock{m_dummy_thread_mutex};
|
||||
m_dummy_thread_cv.wait(lock, [this] { return m_dummy_thread_runnable; });
|
||||
}
|
||||
|
||||
void KThread::DummyThreadEndWait() {
|
||||
@@ -1331,8 +1335,11 @@ void KThread::DummyThreadEndWait() {
|
||||
ASSERT(this->IsDummyThread());
|
||||
|
||||
// Wake up the waiting thread.
|
||||
m_dummy_thread_runnable.store(true);
|
||||
m_dummy_thread_runnable.notify_one();
|
||||
{
|
||||
std::scoped_lock lock{m_dummy_thread_mutex};
|
||||
m_dummy_thread_runnable = true;
|
||||
}
|
||||
m_dummy_thread_cv.notify_one();
|
||||
}
|
||||
|
||||
void KThread::BeginWait(KThreadQueue* queue) {
|
||||
|
||||
@@ -892,7 +892,9 @@ private:
|
||||
std::shared_ptr<Common::Fiber> m_host_context{};
|
||||
ThreadType m_thread_type{};
|
||||
StepState m_step_state{};
|
||||
std::atomic<bool> m_dummy_thread_runnable{true};
|
||||
bool m_dummy_thread_runnable{true};
|
||||
std::mutex m_dummy_thread_mutex{};
|
||||
std::condition_variable m_dummy_thread_cv{};
|
||||
|
||||
// For debugging
|
||||
std::vector<KSynchronizationObject*> m_wait_objects_for_debugging{};
|
||||
|
||||
@@ -199,9 +199,9 @@ Result GetInfo(Core::System& system, u64* result, InfoType info_id_type, Handle
|
||||
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
|
||||
const u64 thread_ticks = current_thread->GetCpuTime();
|
||||
|
||||
out_ticks = thread_ticks + (core_timing.GetCPUTicks() - prev_ctx_ticks);
|
||||
out_ticks = thread_ticks + (core_timing.GetClockTicks() - prev_ctx_ticks);
|
||||
} else if (same_thread && info_sub_id == system.Kernel().CurrentPhysicalCoreIndex()) {
|
||||
out_ticks = core_timing.GetCPUTicks() - prev_ctx_ticks;
|
||||
out_ticks = core_timing.GetClockTicks() - prev_ctx_ticks;
|
||||
}
|
||||
|
||||
*result = out_ticks;
|
||||
|
||||
@@ -12,16 +12,8 @@ namespace Kernel::Svc {
|
||||
int64_t GetSystemTick(Core::System& system) {
|
||||
LOG_TRACE(Kernel_SVC, "called");
|
||||
|
||||
auto& core_timing = system.CoreTiming();
|
||||
|
||||
// Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick)
|
||||
const u64 result{core_timing.GetClockTicks()};
|
||||
|
||||
if (!system.Kernel().IsMulticore()) {
|
||||
core_timing.AddTicks(400U);
|
||||
}
|
||||
|
||||
return static_cast<int64_t>(result);
|
||||
return static_cast<int64_t>(system.CoreTiming().GetClockTicks());
|
||||
}
|
||||
|
||||
int64_t GetSystemTick64(Core::System& system) {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
|
||||
@@ -36,12 +36,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
|
||||
|
||||
// Validate UUID
|
||||
constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
|
||||
if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) !=
|
||||
ntag_file.uuid.uid[3]) {
|
||||
if ((CT ^ ntag_file.uuid.part1[0] ^ ntag_file.uuid.part1[1] ^ ntag_file.uuid.part1[2]) !=
|
||||
ntag_file.uuid.crc_check1) {
|
||||
return false;
|
||||
}
|
||||
if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^
|
||||
ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) {
|
||||
if ((ntag_file.uuid.part2[0] ^ ntag_file.uuid.part2[1] ^ ntag_file.uuid.part2[2] ^
|
||||
ntag_file.uuid.nintendo_id) != ntag_file.uuid_crc_check2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -74,8 +74,9 @@ bool IsAmiiboValid(const NTAG215File& ntag_file) {
|
||||
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
|
||||
NTAG215File encoded_data{};
|
||||
|
||||
encoded_data.uid = nfc_data.uuid.uid;
|
||||
encoded_data.nintendo_id = nfc_data.uuid.nintendo_id;
|
||||
encoded_data.uid = nfc_data.uuid;
|
||||
encoded_data.uid_crc_check2 = nfc_data.uuid_crc_check2;
|
||||
encoded_data.internal_number = nfc_data.internal_number;
|
||||
encoded_data.static_lock = nfc_data.static_lock;
|
||||
encoded_data.compability_container = nfc_data.compability_container;
|
||||
encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
|
||||
@@ -94,7 +95,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
|
||||
encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc;
|
||||
encoded_data.application_area = nfc_data.user_memory.application_area;
|
||||
encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
|
||||
encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
|
||||
encoded_data.model_info = nfc_data.user_memory.model_info;
|
||||
encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
|
||||
encoded_data.dynamic_lock = nfc_data.dynamic_lock;
|
||||
@@ -108,9 +108,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
|
||||
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
|
||||
EncryptedNTAG215File nfc_data{};
|
||||
|
||||
nfc_data.uuid.uid = encoded_data.uid;
|
||||
nfc_data.uuid.nintendo_id = encoded_data.nintendo_id;
|
||||
nfc_data.uuid.lock_bytes = encoded_data.lock_bytes;
|
||||
nfc_data.uuid = encoded_data.uid;
|
||||
nfc_data.uuid_crc_check2 = encoded_data.uid_crc_check2;
|
||||
nfc_data.internal_number = encoded_data.internal_number;
|
||||
nfc_data.static_lock = encoded_data.static_lock;
|
||||
nfc_data.compability_container = encoded_data.compability_container;
|
||||
nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
|
||||
@@ -139,23 +139,12 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
|
||||
return nfc_data;
|
||||
}
|
||||
|
||||
u32 GetTagPassword(const TagUuid& uuid) {
|
||||
// Verify that the generated password is correct
|
||||
u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]);
|
||||
password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8;
|
||||
password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16;
|
||||
password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24;
|
||||
return password;
|
||||
}
|
||||
|
||||
HashSeed GetSeed(const NTAG215File& data) {
|
||||
HashSeed seed{
|
||||
.magic = data.write_counter,
|
||||
.padding = {},
|
||||
.uid_1 = data.uid,
|
||||
.nintendo_id_1 = data.nintendo_id,
|
||||
.uid_2 = data.uid,
|
||||
.nintendo_id_2 = data.nintendo_id,
|
||||
.keygen_salt = data.keygen_salt,
|
||||
};
|
||||
|
||||
@@ -177,10 +166,11 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
|
||||
output.insert(output.end(), key.magic_bytes.begin(),
|
||||
key.magic_bytes.begin() + key.magic_length);
|
||||
|
||||
output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
|
||||
output.emplace_back(seed.nintendo_id_1);
|
||||
output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
|
||||
output.emplace_back(seed.nintendo_id_2);
|
||||
std::array<u8, sizeof(NFP::TagUuid)> seed_uuid{};
|
||||
memcpy(seed_uuid.data(), &seed.uid_1, sizeof(NFP::TagUuid));
|
||||
output.insert(output.end(), seed_uuid.begin(), seed_uuid.end());
|
||||
memcpy(seed_uuid.data(), &seed.uid_2, sizeof(NFP::TagUuid));
|
||||
output.insert(output.end(), seed_uuid.begin(), seed_uuid.end());
|
||||
|
||||
for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
|
||||
output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
|
||||
@@ -264,8 +254,8 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou
|
||||
|
||||
// Copy the rest of the data directly
|
||||
out_data.uid = in_data.uid;
|
||||
out_data.nintendo_id = in_data.nintendo_id;
|
||||
out_data.lock_bytes = in_data.lock_bytes;
|
||||
out_data.uid_crc_check2 = in_data.uid_crc_check2;
|
||||
out_data.internal_number = in_data.internal_number;
|
||||
out_data.static_lock = in_data.static_lock;
|
||||
out_data.compability_container = in_data.compability_container;
|
||||
|
||||
|
||||
@@ -24,10 +24,8 @@ using DrgbOutput = std::array<u8, 0x20>;
|
||||
struct HashSeed {
|
||||
u16_be magic;
|
||||
std::array<u8, 0xE> padding;
|
||||
NFC::UniqueSerialNumber uid_1;
|
||||
u8 nintendo_id_1;
|
||||
NFC::UniqueSerialNumber uid_2;
|
||||
u8 nintendo_id_2;
|
||||
TagUuid uid_1;
|
||||
TagUuid uid_2;
|
||||
std::array<u8, 0x20> keygen_salt;
|
||||
};
|
||||
static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
|
||||
@@ -69,9 +67,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
|
||||
/// Converts from encoded file format to encrypted file format
|
||||
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
|
||||
|
||||
/// Returns password needed to allow write access to protected memory
|
||||
u32 GetTagPassword(const TagUuid& uuid);
|
||||
|
||||
// Generates Seed needed for key derivation
|
||||
HashSeed GetSeed(const NTAG215File& data);
|
||||
|
||||
|
||||
@@ -242,34 +242,39 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
|
||||
return ResultWrongDeviceState;
|
||||
}
|
||||
|
||||
UniqueSerialNumber uuid = encrypted_tag_data.uuid.uid;
|
||||
|
||||
// Generate random UUID to bypass amiibo load limits
|
||||
if (Settings::values.random_amiibo_id) {
|
||||
Common::TinyMT rng{};
|
||||
rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
|
||||
rng.GenerateRandomBytes(uuid.data(), sizeof(UniqueSerialNumber));
|
||||
uuid[3] = 0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2];
|
||||
}
|
||||
UniqueSerialNumber uuid{};
|
||||
u8 uuid_length{};
|
||||
NfcProtocol protocol{NfcProtocol::TypeA};
|
||||
TagType tag_type{TagType::Type2};
|
||||
|
||||
if (is_mifare) {
|
||||
tag_info = {
|
||||
.uuid = uuid,
|
||||
.uuid_extension = {},
|
||||
.uuid_length = static_cast<u8>(uuid.size()),
|
||||
.protocol = NfcProtocol::TypeA,
|
||||
.tag_type = TagType::Type4,
|
||||
tag_type = TagType::Mifare;
|
||||
uuid_length = sizeof(NFP::NtagTagUuid);
|
||||
memcpy(uuid.data(), mifare_data.data(), uuid_length);
|
||||
} else {
|
||||
tag_type = TagType::Type2;
|
||||
uuid_length = sizeof(NFP::NtagTagUuid);
|
||||
NFP::NtagTagUuid nUuid{
|
||||
.part1 = encrypted_tag_data.uuid.part1,
|
||||
.part2 = encrypted_tag_data.uuid.part2,
|
||||
.nintendo_id = encrypted_tag_data.uuid.nintendo_id,
|
||||
};
|
||||
return ResultSuccess;
|
||||
memcpy(uuid.data(), &nUuid, uuid_length);
|
||||
|
||||
// Generate random UUID to bypass amiibo load limits
|
||||
if (Settings::values.random_amiibo_id) {
|
||||
Common::TinyMT rng{};
|
||||
rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
|
||||
rng.GenerateRandomBytes(uuid.data(), uuid_length);
|
||||
}
|
||||
}
|
||||
|
||||
// Protocol and tag type may change here
|
||||
tag_info = {
|
||||
.uuid = uuid,
|
||||
.uuid_extension = {},
|
||||
.uuid_length = static_cast<u8>(uuid.size()),
|
||||
.protocol = NfcProtocol::TypeA,
|
||||
.tag_type = TagType::Type2,
|
||||
.uuid_length = uuid_length,
|
||||
.protocol = protocol,
|
||||
.tag_type = tag_type,
|
||||
};
|
||||
|
||||
return ResultSuccess;
|
||||
@@ -277,8 +282,38 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
|
||||
|
||||
Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters,
|
||||
std::span<MifareReadBlockData> read_block_data) const {
|
||||
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
if (device_state == DeviceState::TagRemoved) {
|
||||
return ResultTagRemoved;
|
||||
}
|
||||
return ResultWrongDeviceState;
|
||||
}
|
||||
|
||||
Result result = ResultSuccess;
|
||||
|
||||
TagInfo tag_info{};
|
||||
result = GetTagInfo(tag_info, true);
|
||||
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) {
|
||||
return ResultInvalidTagType;
|
||||
}
|
||||
|
||||
if (parameters.size() == 0) {
|
||||
return ResultInvalidArgument;
|
||||
}
|
||||
|
||||
const auto unknown = parameters[0].sector_key.unknown;
|
||||
for (std::size_t i = 0; i < parameters.size(); i++) {
|
||||
if (unknown != parameters[i].sector_key.unknown) {
|
||||
return ResultInvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < parameters.size(); i++) {
|
||||
result = ReadMifare(parameters[i], read_block_data[i]);
|
||||
if (result.IsError()) {
|
||||
@@ -293,17 +328,8 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter,
|
||||
MifareReadBlockData& read_block_data) const {
|
||||
const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
|
||||
read_block_data.sector_number = parameter.sector_number;
|
||||
|
||||
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
if (device_state == DeviceState::TagRemoved) {
|
||||
return ResultTagRemoved;
|
||||
}
|
||||
return ResultWrongDeviceState;
|
||||
}
|
||||
|
||||
if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
|
||||
return Mifare::ResultReadError;
|
||||
return ResultMifareError288;
|
||||
}
|
||||
|
||||
// TODO: Use parameter.sector_key to read encrypted data
|
||||
@@ -315,6 +341,28 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter,
|
||||
Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) {
|
||||
Result result = ResultSuccess;
|
||||
|
||||
TagInfo tag_info{};
|
||||
result = GetTagInfo(tag_info, true);
|
||||
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) {
|
||||
return ResultInvalidTagType;
|
||||
}
|
||||
|
||||
if (parameters.size() == 0) {
|
||||
return ResultInvalidArgument;
|
||||
}
|
||||
|
||||
const auto unknown = parameters[0].sector_key.unknown;
|
||||
for (std::size_t i = 0; i < parameters.size(); i++) {
|
||||
if (unknown != parameters[i].sector_key.unknown) {
|
||||
return ResultInvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < parameters.size(); i++) {
|
||||
result = WriteMifare(parameters[i]);
|
||||
if (result.IsError()) {
|
||||
@@ -324,7 +372,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet
|
||||
|
||||
if (!npad_device->WriteNfc(mifare_data)) {
|
||||
LOG_ERROR(Service_NFP, "Error writing to file");
|
||||
return Mifare::ResultReadError;
|
||||
return ResultMifareError288;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -342,7 +390,7 @@ Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) {
|
||||
}
|
||||
|
||||
if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
|
||||
return Mifare::ResultReadError;
|
||||
return ResultMifareError288;
|
||||
}
|
||||
|
||||
// TODO: Use parameter.sector_key to encrypt the data
|
||||
@@ -366,7 +414,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
|
||||
|
||||
if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
|
||||
LOG_ERROR(Service_NFP, "Not an amiibo");
|
||||
return ResultNotAnAmiibo;
|
||||
return ResultInvalidTagType;
|
||||
}
|
||||
|
||||
// The loaded amiibo is not encrypted
|
||||
@@ -381,14 +429,14 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
|
||||
}
|
||||
|
||||
if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
|
||||
bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess();
|
||||
bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
|
||||
LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup);
|
||||
return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
|
||||
}
|
||||
|
||||
std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
|
||||
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
|
||||
WriteBackupData(encrypted_tag_data.uuid.uid, data);
|
||||
WriteBackupData(encrypted_tag_data.uuid, data);
|
||||
|
||||
device_state = DeviceState::TagMounted;
|
||||
mount_target = mount_target_;
|
||||
@@ -492,7 +540,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
|
||||
}
|
||||
|
||||
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
|
||||
WriteBackupData(encrypted_tag_data.uuid.uid, data);
|
||||
WriteBackupData(encrypted_tag_data.uuid, data);
|
||||
}
|
||||
|
||||
if (!npad_device->WriteNfc(data)) {
|
||||
@@ -520,7 +568,7 @@ Result NfcDevice::Restore() {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = ReadBackupData(tag_info.uuid, data);
|
||||
result = ReadBackupData(tag_info.uuid, tag_info.uuid_length, data);
|
||||
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
@@ -548,7 +596,7 @@ Result NfcDevice::Restore() {
|
||||
}
|
||||
|
||||
if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) {
|
||||
return ResultNotAnAmiibo;
|
||||
return ResultInvalidTagType;
|
||||
}
|
||||
|
||||
if (!is_plain_amiibo) {
|
||||
@@ -1194,10 +1242,12 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) {
|
||||
return FlushWithBreak(break_type);
|
||||
}
|
||||
|
||||
Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const {
|
||||
Result NfcDevice::HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const {
|
||||
ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size");
|
||||
constexpr auto backup_dir = "backup";
|
||||
const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
|
||||
const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
|
||||
const auto file_name =
|
||||
fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, ""));
|
||||
|
||||
if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) {
|
||||
return ResultUnableToAccessBackupFile;
|
||||
@@ -1206,10 +1256,19 @@ Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const {
|
||||
Result NfcDevice::HasBackup(const NFP::TagUuid& tag_uid) const {
|
||||
UniqueSerialNumber uuid{};
|
||||
memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid));
|
||||
return HasBackup(uuid, sizeof(NFP::TagUuid));
|
||||
}
|
||||
|
||||
Result NfcDevice::ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
|
||||
std::span<u8> data) const {
|
||||
ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size");
|
||||
constexpr auto backup_dir = "backup";
|
||||
const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
|
||||
const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
|
||||
const auto file_name =
|
||||
fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, ""));
|
||||
|
||||
const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
|
||||
Common::FS::FileAccessMode::Read,
|
||||
@@ -1228,12 +1287,21 @@ Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) {
|
||||
Result NfcDevice::ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const {
|
||||
UniqueSerialNumber uuid{};
|
||||
memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid));
|
||||
return ReadBackupData(uuid, sizeof(NFP::TagUuid), data);
|
||||
}
|
||||
|
||||
Result NfcDevice::WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
|
||||
std::span<const u8> data) {
|
||||
ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size");
|
||||
constexpr auto backup_dir = "backup";
|
||||
const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
|
||||
const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, ""));
|
||||
const auto file_name =
|
||||
fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, ""));
|
||||
|
||||
if (HasBackup(uid).IsError()) {
|
||||
if (HasBackup(uid, uuid_size).IsError()) {
|
||||
if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) {
|
||||
return ResultBackupPathAlreadyExist;
|
||||
}
|
||||
@@ -1260,6 +1328,12 @@ Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfcDevice::WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data) {
|
||||
UniqueSerialNumber uuid{};
|
||||
memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid));
|
||||
return WriteBackupData(uuid, sizeof(NFP::TagUuid), data);
|
||||
}
|
||||
|
||||
Result NfcDevice::WriteNtf(std::span<const u8> data) {
|
||||
if (device_state != DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
|
||||
@@ -86,9 +86,14 @@ public:
|
||||
Result GetAll(NFP::NfpData& data) const;
|
||||
Result SetAll(const NFP::NfpData& data);
|
||||
Result BreakTag(NFP::BreakType break_type);
|
||||
Result HasBackup(const NFC::UniqueSerialNumber& uid) const;
|
||||
Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const;
|
||||
Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data);
|
||||
Result HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const;
|
||||
Result HasBackup(const NFP::TagUuid& tag_uid) const;
|
||||
Result ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
|
||||
std::span<u8> data) const;
|
||||
Result ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const;
|
||||
Result WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
|
||||
std::span<const u8> data);
|
||||
Result WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data);
|
||||
Result WriteNtf(std::span<const u8> data);
|
||||
|
||||
u64 GetHandle() const;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user