Compare commits

..

85 Commits

Author SHA1 Message Date
Liam
1586f1c0b1 general: remove atomic signal and wait 2023-06-22 09:25:23 -04:00
bunnei
e3122c5b46 Merge pull request #10086 from Morph1984/coretiming-ng-1
core_timing: Use CNTPCT as the guest CPU tick
2023-06-21 21:12:46 -07:00
bunnei
7eb7d56b1b Merge pull request #10777 from liamwhite/no-barrier
video_core: optionally skip barriers on feedback loops
2023-06-21 21:10:08 -07:00
bunnei
8cb6b33809 Merge pull request #10841 from liamwhite/math-is-hard
vfs_concat: fix offset calculation when not aligned to file boundary
2023-06-21 21:07:08 -07:00
bunnei
8ad64bc253 Merge pull request #10863 from lat9nq/tz-end-of-string
time_zone_manager: Stop on comma
2023-06-21 21:05:03 -07:00
liamwhite
eea2145698 Merge pull request #10864 from t895/disable-mali-driver
android: Don't show custom driver button on mali and x86
2023-06-21 16:50:30 -04:00
Charles Lombardo
e684515578 android: Don't show custom driver button on mali and x86 2023-06-20 20:06:36 -04:00
lat9nq
ae1a8a7dc7 time_zone_manager: Add null terminator
We aren't null-terminating this string after the copy, and we need to.
2023-06-20 15:54:28 -04:00
lat9nq
fd5d7947f6 time_zone_manager: Stop on comma
This is a deviation from the reference time zone implementation. The
actual code will set a pointer to the time zone name here, but for us we
have a limited number of characters to work with, and the name of the
time zone here could be larger than 8 characters.

We can make the assumption that time zone names greater than five
characters in length include a comma that denotes more data. Nintendo
just truncates that data for the name, so we can do the same.

time_zone_manager: Check for length of array

Just to be double sure that we never break past the array length,
directly compare against it.
2023-06-20 15:54:05 -04:00
bunnei
a67bdeb2c2 Merge pull request #10853 from lat9nq/update_tzdb_to_nx
externals: Update tzdb_to_nx
2023-06-20 10:42:54 -07:00
liamwhite
f1e12e3b08 Merge pull request #10818 from vonchenplus/render_target_samples
video_core: add samples check when find render target
2023-06-20 09:55:23 -04:00
liamwhite
93061d1ea1 Merge pull request #10835 from lat9nq/intel-restrict-compute-disable
vulkan_device: Restrict compute disable only to affected Intel drivers
2023-06-20 09:55:14 -04:00
liamwhite
6d12e7320b Merge pull request #10840 from Kelebek1/unbug_blinks_brain
Use current GPU address when unmapping GPU pages, not the base
2023-06-20 09:55:01 -04:00
toast2903
78ff2862f6 vulkan_device: Remove brace initializer
Co-authored-by: Tobias <thm.frey@gmail.com>
2023-06-19 17:35:12 -04:00
lat9nq
197e13d93d video_core: Check broken compute earlier
Checks it as the system is determining what settings to enable. Reduces
the need to check settings while the system is running.
2023-06-19 17:33:30 -04:00
lat9nq
bedb5135c0 nx_tzdb: Rename GNU_DATE variable
The repository can handle either GNU date or Apple date now.
2023-06-19 15:30:11 -04:00
lat9nq
256c7ec0a7 externals: Update tzdb_to_nx
Includes a fix for the Apple date utility.
2023-06-19 15:27:54 -04:00
Liam
e5f1b22e16 vfs_concat: verify short read 2023-06-19 09:47:05 -04:00
Liam
b0beca52a3 vfs_concat: fix offset calculation when not aligned to file boundary 2023-06-18 22:21:29 -04:00
Kelebek1
711190bb67 Use current GPU address when unmapping GPU pages, not the base 2023-06-19 00:19:50 +01:00
lat9nq
b9a86b040b vk_device_info: Check only affected Intel drivers
Renames is_intel_proprietary to has_broken_compute for accuracy.

vk_device_info: Use vulkan::device to check compute
2023-06-18 16:15:51 -04:00
lat9nq
346c253cd2 video_core: Formalize HasBrokenCompute
Also limits it to only affected Intel proprietrary driver versions.

vulkan_device: Move broken compute determination

vk_device: Remove errant back quote
2023-06-18 16:15:47 -04:00
liamwhite
ce191ba32b Merge pull request #10825 from 8bitDream/vcpkg-zlib
externals: Update vcpkg to 2023.06.17
2023-06-18 09:43:12 -04:00
liamwhite
23371fa187 Merge pull request #10829 from lat9nq/remove-external-mem
vulkan_device: Remove external memory extension
2023-06-18 09:43:03 -04:00
liamwhite
af7f3f078c Merge pull request #10486 from lat9nq/vk-device-find-once
yuzu-qt: Load Vulkan device info at startup
2023-06-18 09:42:55 -04:00
liamwhite
66b8042b59 Merge pull request #10798 from vonchenplus/draw_texture_scale
video_core: drawtexture support upscale
2023-06-18 09:42:41 -04:00
liamwhite
8acf728d5d Merge pull request #10809 from Kelebek1/reduce_vertex_bindings
Synchronize vertex buffer even when it doesn't require binding
2023-06-18 09:42:32 -04:00
bunnei
6e293be20b Merge pull request #10797 from lat9nq/tzdb-patch
time: Various time zone fixes
2023-06-17 23:47:16 -07:00
bunnei
20db91f0fc Merge pull request #10828 from liamwhite/somehow-still-using-llvm-14
renderer_vulkan: add missing include
2023-06-17 23:45:44 -07:00
lat9nq
8a526b2c26 vulkan_device: Remove external memory extension
Unused in yuzu. Enables yuzu to boot games in Wine using Vulkan.
2023-06-18 01:20:08 -04:00
Liam
565a1226d7 renderer_vulkan: add missing include 2023-06-17 23:57:47 -04:00
Abandoned Cart
fd0ef5411c externals: Update vcpkg to 2023.06.17
Fixes for zlib and qt5
2023-06-17 21:46:09 -04:00
Morph
c0fd793ef6 Merge pull request #10813 from lat9nq/no-atomic-bool
k_thread: Use a mutex and cond_var to sync bool
2023-06-17 20:29:57 -04:00
Fernando S
27a36cd51b Merge pull request #10744 from Wollnashorn/af-for-all
video_core: Improved anisotropic filtering heuristics
2023-06-18 00:02:05 +02:00
Kelebek1
e681f5678c Synchronize vertex buffer even when it doesn't require binding 2023-06-17 17:47:00 -04:00
lat9nq
e34e1b1c95 k_thread: Use a mutex and cond_var to sync bool
std::atomic<bool> is broken on MinGW and causes deadlocks there.
Use a normal cond var in its stead.
2023-06-17 15:25:36 -04:00
FengChen
76a676883a video_core: add samples check when find render target 2023-06-17 23:48:51 +08:00
Wollnashorn
3e47ebe2e9 video_core: Only apply AF to 2D (array) image types 2023-06-17 14:20:44 +02:00
Wollnashorn
c309a1c69b video_core: Removed AF for all mip modes option as it's default now 2023-06-17 11:19:39 +02:00
bunnei
ec423c6919 Merge pull request #10783 from liamwhite/memory
video_core: preallocate fewer IR blocks
2023-06-16 16:53:25 -07:00
bunnei
24e1e4dcee Merge pull request #10808 from t895/settings-stuffs
android: Expose settings
2023-06-16 16:52:54 -07:00
bunnei
975122f4bb Merge pull request #10807 from t895/ktlint-fixes
android: Ktlint fixes
2023-06-16 16:47:14 -07:00
liamwhite
a1adcc31d3 Merge pull request #10731 from german77/misc_fixes
service: nfc: Accuracy fixes
2023-06-16 18:18:24 -04:00
Charles Lombardo
9f92104f3e android: Expose audio output engine setting 2023-06-16 16:42:56 -04:00
Charles Lombardo
330358cd16 android: Bump ktlint version to 0.47.1 2023-06-16 16:32:08 -04:00
Charles Lombardo
fc6a2fe779 android: Disable import-ordering ktlint check 2023-06-16 16:31:49 -04:00
Charles Lombardo
13a4de647d android: Expose CPU debugging option 2023-06-16 16:25:06 -04:00
Charles Lombardo
3ac2c74e85 android: Expose fastmem option 2023-06-16 16:24:40 -04:00
Charles Lombardo
5aca03d0ff android: Support changing multiple settings at once 2023-06-16 15:49:49 -04:00
bunnei
4112031c81 Merge pull request #10801 from 8bitDream/fix_aspect
android: Fix aspect ratio when rotating screen
2023-06-16 11:45:32 -07:00
Abandoned Cart
c89be0dfab android: Fix aspect ratio when rotating screen 2023-06-16 10:11:18 -04:00
Feng Chen
b77a247e8c video_core: drawtexture support upscale 2023-06-16 20:51:15 +08:00
Wollnashorn
2dc0ff79ec video_core: Use sampler IDs instead pointers in the pipeline config
The previous approach of storing pointers returned by `GetGraphicsSampler`/`GetComputeSampler` caused UB, as these functions can cause reallocation of the sampler slot vector and therefore invalidate the pointers
2023-06-16 13:45:14 +02:00
liamwhite
c7fc5b9348 Merge pull request #10739 from zeltermann/sdl-cpuinfo
Re-enable SDL's `CPUinfo` subsystem
2023-06-16 00:08:53 -04:00
liamwhite
41295ff8fe Merge pull request #10795 from german77/foomiibo
input_common: Add foomiibo support
2023-06-16 00:08:30 -04:00
Charles Lombardo
9dd52019a9 Merge pull request #10767 from t895/lint
android: Linting
2023-06-16 00:06:55 -04:00
Charles Lombardo
d0be850f25 android: Apply ktlint codestyle 2023-06-15 22:36:54 -04:00
Charles Lombardo
d85129aa17 Android: Use ktlint for Kotlin code style 2023-06-15 22:22:49 -04:00
Charles Lombardo
a29fa119e0 android: Enable android linting 2023-06-15 22:19:58 -04:00
Narr the Reg
0c90a0926f input_common: Add amiibo with originality signature support 2023-06-15 18:22:13 -06:00
Wollnashorn
a3b7b5b22a video_core: Fallback to default anisotropy instead to 1x anisotropy 2023-06-15 23:16:26 +02:00
Wollnashorn
745d16132b video_core: Disable AF for non-color image formats 2023-06-15 20:59:33 +02:00
Wollnashorn
3e8cd91d54 video_core: Fixed compilation errors because of name shadowing 2023-06-15 18:46:40 +02:00
Wollnashorn
42c944b250 video_core: Add per-image anisotropy heuristics (format & mip count) 2023-06-15 18:19:32 +02:00
Liam
2c01669046 video_core: preallocate fewer IR blocks 2023-06-14 21:37:57 -04:00
Narr the Reg
61b4588517 service: nfc: Read tag protocol only for nfc backend 2023-06-14 18:16:23 -06:00
Narr the Reg
b1b13ddc6b service: nfc: Accuracy fixes 2023-06-14 18:08:35 -06:00
Liam
8d6aefdcc4 video_core: optionally skip barriers on feedback loops 2023-06-14 14:11:46 -04:00
Wollnashorn
0de6b9e3f5 video_core: Apply AF only to samplers with normal LOD range [0, 1+x] 2023-06-14 13:27:27 +02:00
Wollnashorn
a9e4dddad5 video_core: Fix default anisotropic heuristic 2023-06-14 11:21:22 +02:00
Wollnashorn
44f616edb9 video_core: Never apply AF to None mipmap mode
Should fix some artifacts with the "apply anisotropic filtering for all mipmap modes" option
2023-06-14 03:57:39 +02:00
Wollnashorn
b9bba3ac89 video_core: Disable anisotropic filtering for samplers with depth compare 2023-06-13 21:32:32 +02:00
Wollnashorn
0eacf547c0 video_core: Option to apply anisotropic filtering for all mipmap modes 2023-06-13 03:21:01 +02:00
zeltermann
0c04e27df3 Re-enable SDL's CPUinfo subsystem
See https://github.com/libsdl-org/SDL/issues/7809.
Disabling CPUinfo triggers a bug in SDL's audio subsystem, which breaks
SDL's JACK output on Linux. We're lucky it hasn't broken anything else.
2023-06-12 21:36:07 +07:00
Morph
3e6d81a008 nvdisp: Fix SingleCore frametime reporting 2023-06-07 22:04:02 -04:00
Morph
2e1e725443 core_timing: Fix SingleCore cycle timer 2023-06-07 21:44:42 -04:00
Morph
907507886d (wall, native)_clock: Add GetGPUTick
Allows us to directly calculate the GPU tick without double conversion to and from the host clock tick.
2023-06-07 21:44:42 -04:00
Morph
9dcc7bde8b time: Use compile time division for TimeSpanType conversion 2023-06-07 21:44:42 -04:00
Morph
8e56a84566 core_timing: Use CNTPCT as the guest CPU tick
Previously, we were mixing the raw CPU frequency and CNTFRQ.
The raw CPU frequency (1020 MHz) should've never been used as CNTPCT (whose frequency is CNTFRQ) is the only counter available.
2023-06-07 21:44:42 -04:00
Morph
bbd502f67a nvnflinger: Acquire lock prior to signaling the vsync variable 2023-06-07 21:44:42 -04:00
Morph
1492a65454 (wall, native)_clock: Rework NativeClock 2023-06-07 21:44:42 -04:00
Morph
dd12dd4c67 x64: Deduplicate RDTSC usage 2023-06-07 21:44:42 -04:00
lat9nq
013c34cb32 vk_device_info: Clean up includes [IWYU] 2023-06-06 01:54:44 -04:00
lat9nq
f9fc996083 vk_device_info: Add SPDX data 2023-06-06 01:54:44 -04:00
lat9nq
fc0c4db20d yuzu-qt: Load Vulkan device info at startup
Loading it when the configuration opens now incurs a noticeable delay.
We also don't need to rediscover the same data repeatedly each time the
configuration opens.

Moves vulkan device info discovery to yuzu's startup as opposed to the
configure_graphics constructor.
2023-06-06 01:54:44 -04:00
163 changed files with 1832 additions and 1255 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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">

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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) {
}
}
}

View File

@@ -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,

View File

@@ -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
)
}
}
}

View File

@@ -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

View File

@@ -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() }
}

View File

@@ -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() {

View File

@@ -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"
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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?,

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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,

View File

@@ -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(

View File

@@ -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!")
}

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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
)
)
}
}
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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) :

View File

@@ -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)
}
}
}
}

View File

@@ -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())

View File

@@ -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())

View File

@@ -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."
)
}
}
}

View File

@@ -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)

View File

@@ -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}"
)

View File

@@ -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()

View File

@@ -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())

View File

@@ -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)

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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()
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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(

View File

@@ -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)

View File

@@ -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,

View File

@@ -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
}
}
/**

View File

@@ -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

View File

@@ -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
}
}
}
}

View File

@@ -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"

View File

@@ -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)

View File

@@ -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"

View File

@@ -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() {

View File

@@ -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

View File

@@ -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 {
)
}
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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

View File

@@ -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
}
}
}

View File

@@ -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)
)
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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)++;
}
}

View File

@@ -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 {

View File

@@ -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
)

View File

@@ -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"};

View File

@@ -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

View File

@@ -55,7 +55,7 @@ public:
is_set = false;
}
[[nodiscard]] bool IsSet() {
[[nodiscard]] bool IsSet() const {
return is_set;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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:

View File

@@ -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

View File

@@ -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
View 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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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{};
};

View File

@@ -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

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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{};

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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