diff options
Diffstat (limited to 'src')
94 files changed, 1148 insertions, 597 deletions
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index a637db78a..bab4f4d0f 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts | |||
| @@ -2,7 +2,9 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-3.0-or-later | 2 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | 3 | ||
| 4 | import android.annotation.SuppressLint | 4 | import android.annotation.SuppressLint |
| 5 | import kotlin.collections.setOf | ||
| 5 | import org.jetbrains.kotlin.konan.properties.Properties | 6 | import org.jetbrains.kotlin.konan.properties.Properties |
| 7 | import org.jlleitschuh.gradle.ktlint.reporter.ReporterType | ||
| 6 | 8 | ||
| 7 | plugins { | 9 | plugins { |
| 8 | id("com.android.application") | 10 | id("com.android.application") |
| @@ -10,6 +12,7 @@ plugins { | |||
| 10 | id("kotlin-parcelize") | 12 | id("kotlin-parcelize") |
| 11 | kotlin("plugin.serialization") version "1.8.21" | 13 | kotlin("plugin.serialization") version "1.8.21" |
| 12 | id("androidx.navigation.safeargs.kotlin") | 14 | id("androidx.navigation.safeargs.kotlin") |
| 15 | id("org.jlleitschuh.gradle.ktlint") version "11.4.0" | ||
| 13 | } | 16 | } |
| 14 | 17 | ||
| 15 | /** | 18 | /** |
| @@ -44,16 +47,6 @@ android { | |||
| 44 | jniLibs.useLegacyPackaging = true | 47 | jniLibs.useLegacyPackaging = true |
| 45 | } | 48 | } |
| 46 | 49 | ||
| 47 | lint { | ||
| 48 | // This is important as it will run lint but not abort on error | ||
| 49 | // Lint has some overly obnoxious "errors" that should really be warnings | ||
| 50 | abortOnError = false | ||
| 51 | |||
| 52 | //Uncomment disable lines for test builds... | ||
| 53 | //disable 'MissingTranslation'bin | ||
| 54 | //disable 'ExtraTranslation' | ||
| 55 | } | ||
| 56 | |||
| 57 | defaultConfig { | 50 | defaultConfig { |
| 58 | // TODO If this is ever modified, change application_id in strings.xml | 51 | // TODO If this is ever modified, change application_id in strings.xml |
| 59 | applicationId = "org.yuzu.yuzu_emu" | 52 | applicationId = "org.yuzu.yuzu_emu" |
| @@ -167,6 +160,24 @@ android { | |||
| 167 | } | 160 | } |
| 168 | } | 161 | } |
| 169 | 162 | ||
| 163 | tasks.getByPath("preBuild").dependsOn("ktlintCheck") | ||
| 164 | |||
| 165 | ktlint { | ||
| 166 | version.set("0.47.1") | ||
| 167 | android.set(true) | ||
| 168 | ignoreFailures.set(false) | ||
| 169 | disabledRules.set( | ||
| 170 | setOf( | ||
| 171 | "no-wildcard-imports", | ||
| 172 | "package-name", | ||
| 173 | "import-ordering" | ||
| 174 | ) | ||
| 175 | ) | ||
| 176 | reporters { | ||
| 177 | reporter(ReporterType.CHECKSTYLE) | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 170 | dependencies { | 181 | dependencies { |
| 171 | implementation("androidx.core:core-ktx:1.10.1") | 182 | implementation("androidx.core:core-ktx:1.10.1") |
| 172 | implementation("androidx.appcompat:appcompat:1.6.1") | 183 | implementation("androidx.appcompat:appcompat:1.6.1") |
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index a6f87fc2e..e31ad69e2 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml | |||
| @@ -53,6 +53,7 @@ SPDX-License-Identifier: GPL-3.0-or-later | |||
| 53 | <activity | 53 | <activity |
| 54 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" | 54 | android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" |
| 55 | android:theme="@style/Theme.Yuzu.Main" | 55 | android:theme="@style/Theme.Yuzu.Main" |
| 56 | android:screenOrientation="userLandscape" | ||
| 56 | android:supportsPictureInPicture="true" | 57 | android:supportsPictureInPicture="true" |
| 57 | android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" | 58 | android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" |
| 58 | android:exported="true"> | 59 | android:exported="true"> |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index f3bfbe7eb..f860cdd4b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt | |||
| @@ -14,18 +14,18 @@ import android.widget.TextView | |||
| 14 | import androidx.annotation.Keep | 14 | import androidx.annotation.Keep |
| 15 | import androidx.fragment.app.DialogFragment | 15 | import androidx.fragment.app.DialogFragment |
| 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 17 | import java.lang.ref.WeakReference | ||
| 17 | import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext | 18 | import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext |
| 18 | import org.yuzu.yuzu_emu.activities.EmulationActivity | 19 | import org.yuzu.yuzu_emu.activities.EmulationActivity |
| 19 | import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath | 20 | import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath |
| 20 | import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize | ||
| 21 | import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri | ||
| 22 | import org.yuzu.yuzu_emu.utils.FileUtil.exists | 21 | import org.yuzu.yuzu_emu.utils.FileUtil.exists |
| 22 | import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize | ||
| 23 | import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory | 23 | import org.yuzu.yuzu_emu.utils.FileUtil.isDirectory |
| 24 | import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri | ||
| 24 | import org.yuzu.yuzu_emu.utils.Log.error | 25 | import org.yuzu.yuzu_emu.utils.Log.error |
| 25 | import org.yuzu.yuzu_emu.utils.Log.verbose | 26 | import org.yuzu.yuzu_emu.utils.Log.verbose |
| 26 | import org.yuzu.yuzu_emu.utils.Log.warning | 27 | import org.yuzu.yuzu_emu.utils.Log.warning |
| 27 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable | 28 | import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable |
| 28 | import java.lang.ref.WeakReference | ||
| 29 | 29 | ||
| 30 | /** | 30 | /** |
| 31 | * Class which contains methods that interact | 31 | * Class which contains methods that interact |
| @@ -76,7 +76,9 @@ object NativeLibrary { | |||
| 76 | fun openContentUri(path: String?, openmode: String?): Int { | 76 | fun openContentUri(path: String?, openmode: String?): Int { |
| 77 | return if (isNativePath(path!!)) { | 77 | return if (isNativePath(path!!)) { |
| 78 | YuzuApplication.documentsTree!!.openContentUri(path, openmode) | 78 | YuzuApplication.documentsTree!!.openContentUri(path, openmode) |
| 79 | } else openContentUri(appContext, path, openmode) | 79 | } else { |
| 80 | openContentUri(appContext, path, openmode) | ||
| 81 | } | ||
| 80 | } | 82 | } |
| 81 | 83 | ||
| 82 | @Keep | 84 | @Keep |
| @@ -84,7 +86,9 @@ object NativeLibrary { | |||
| 84 | fun getSize(path: String?): Long { | 86 | fun getSize(path: String?): Long { |
| 85 | return if (isNativePath(path!!)) { | 87 | return if (isNativePath(path!!)) { |
| 86 | YuzuApplication.documentsTree!!.getFileSize(path) | 88 | YuzuApplication.documentsTree!!.getFileSize(path) |
| 87 | } else getFileSize(appContext, path) | 89 | } else { |
| 90 | getFileSize(appContext, path) | ||
| 91 | } | ||
| 88 | } | 92 | } |
| 89 | 93 | ||
| 90 | @Keep | 94 | @Keep |
| @@ -92,7 +96,9 @@ object NativeLibrary { | |||
| 92 | fun exists(path: String?): Boolean { | 96 | fun exists(path: String?): Boolean { |
| 93 | return if (isNativePath(path!!)) { | 97 | return if (isNativePath(path!!)) { |
| 94 | YuzuApplication.documentsTree!!.exists(path) | 98 | YuzuApplication.documentsTree!!.exists(path) |
| 95 | } else exists(appContext, path) | 99 | } else { |
| 100 | exists(appContext, path) | ||
| 101 | } | ||
| 96 | } | 102 | } |
| 97 | 103 | ||
| 98 | @Keep | 104 | @Keep |
| @@ -100,7 +106,9 @@ object NativeLibrary { | |||
| 100 | fun isDirectory(path: String?): Boolean { | 106 | fun isDirectory(path: String?): Boolean { |
| 101 | return if (isNativePath(path!!)) { | 107 | return if (isNativePath(path!!)) { |
| 102 | YuzuApplication.documentsTree!!.isDirectory(path) | 108 | YuzuApplication.documentsTree!!.isDirectory(path) |
| 103 | } else isDirectory(appContext, path) | 109 | } else { |
| 110 | isDirectory(appContext, path) | ||
| 111 | } | ||
| 104 | } | 112 | } |
| 105 | 113 | ||
| 106 | /** | 114 | /** |
| @@ -454,7 +462,9 @@ object NativeLibrary { | |||
| 454 | Html.FROM_HTML_MODE_LEGACY | 462 | Html.FROM_HTML_MODE_LEGACY |
| 455 | ) | 463 | ) |
| 456 | ) | 464 | ) |
| 457 | .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() } | 465 | .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> |
| 466 | emulationActivity.finish() | ||
| 467 | } | ||
| 458 | .setOnDismissListener { emulationActivity.finish() } | 468 | .setOnDismissListener { emulationActivity.finish() } |
| 459 | emulationActivity.runOnUiThread { | 469 | emulationActivity.runOnUiThread { |
| 460 | val alert = builder.create() | 470 | val alert = builder.create() |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt index 4c947b786..04ab6a220 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt | |||
| @@ -7,12 +7,12 @@ import android.app.Application | |||
| 7 | import android.app.NotificationChannel | 7 | import android.app.NotificationChannel |
| 8 | import android.app.NotificationManager | 8 | import android.app.NotificationManager |
| 9 | import android.content.Context | 9 | import android.content.Context |
| 10 | import java.io.File | ||
| 10 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 11 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 11 | import org.yuzu.yuzu_emu.utils.DocumentsTree | 12 | import org.yuzu.yuzu_emu.utils.DocumentsTree |
| 12 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper | 13 | import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
| 13 | import java.io.File | ||
| 14 | 14 | ||
| 15 | fun Context.getPublicFilesDir() : File = getExternalFilesDir(null) ?: filesDir | 15 | fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir |
| 16 | 16 | ||
| 17 | class YuzuApplication : Application() { | 17 | class YuzuApplication : Application() { |
| 18 | private fun createNotificationChannels() { | 18 | private fun createNotificationChannels() { |
| @@ -21,7 +21,9 @@ class YuzuApplication : Application() { | |||
| 21 | getString(R.string.emulation_notification_channel_name), | 21 | getString(R.string.emulation_notification_channel_name), |
| 22 | NotificationManager.IMPORTANCE_LOW | 22 | NotificationManager.IMPORTANCE_LOW |
| 23 | ) | 23 | ) |
| 24 | emulationChannel.description = getString(R.string.emulation_notification_channel_description) | 24 | emulationChannel.description = getString( |
| 25 | R.string.emulation_notification_channel_description | ||
| 26 | ) | ||
| 25 | emulationChannel.setSound(null, null) | 27 | emulationChannel.setSound(null, null) |
| 26 | emulationChannel.vibrationPattern = null | 28 | emulationChannel.vibrationPattern = null |
| 27 | 29 | ||
| @@ -48,7 +50,7 @@ class YuzuApplication : Application() { | |||
| 48 | GpuDriverHelper.initializeDriverParameters(applicationContext) | 50 | GpuDriverHelper.initializeDriverParameters(applicationContext) |
| 49 | NativeLibrary.logDeviceInfo() | 51 | NativeLibrary.logDeviceInfo() |
| 50 | 52 | ||
| 51 | createNotificationChannels(); | 53 | createNotificationChannels() |
| 52 | } | 54 | } |
| 53 | 55 | ||
| 54 | companion object { | 56 | companion object { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 5ca519f0a..f0a6753a9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt | |||
| @@ -33,6 +33,7 @@ import androidx.core.view.WindowCompat | |||
| 33 | import androidx.core.view.WindowInsetsCompat | 33 | import androidx.core.view.WindowInsetsCompat |
| 34 | import androidx.core.view.WindowInsetsControllerCompat | 34 | import androidx.core.view.WindowInsetsControllerCompat |
| 35 | import androidx.navigation.fragment.NavHostFragment | 35 | import androidx.navigation.fragment.NavHostFragment |
| 36 | import kotlin.math.roundToInt | ||
| 36 | import org.yuzu.yuzu_emu.NativeLibrary | 37 | import org.yuzu.yuzu_emu.NativeLibrary |
| 37 | import org.yuzu.yuzu_emu.R | 38 | import org.yuzu.yuzu_emu.R |
| 38 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding | 39 | import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding |
| @@ -45,7 +46,6 @@ import org.yuzu.yuzu_emu.utils.ForegroundService | |||
| 45 | import org.yuzu.yuzu_emu.utils.InputHandler | 46 | import org.yuzu.yuzu_emu.utils.InputHandler |
| 46 | import org.yuzu.yuzu_emu.utils.NfcReader | 47 | import org.yuzu.yuzu_emu.utils.NfcReader |
| 47 | import org.yuzu.yuzu_emu.utils.ThemeHelper | 48 | import org.yuzu.yuzu_emu.utils.ThemeHelper |
| 48 | import kotlin.math.roundToInt | ||
| 49 | 49 | ||
| 50 | class EmulationActivity : AppCompatActivity(), SensorEventListener { | 50 | class EmulationActivity : AppCompatActivity(), SensorEventListener { |
| 51 | private lateinit var binding: ActivityEmulationBinding | 51 | private lateinit var binding: ActivityEmulationBinding |
| @@ -256,7 +256,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 256 | } | 256 | } |
| 257 | } | 257 | } |
| 258 | 258 | ||
| 259 | private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): PictureInPictureParams.Builder { | 259 | private fun PictureInPictureParams.Builder.getPictureInPictureAspectBuilder(): |
| 260 | PictureInPictureParams.Builder { | ||
| 260 | val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) { | 261 | val aspectRatio = when (IntSetting.RENDERER_ASPECT_RATIO.int) { |
| 261 | 0 -> Rational(16, 9) | 262 | 0 -> Rational(16, 9) |
| 262 | 1 -> Rational(4, 3) | 263 | 1 -> Rational(4, 3) |
| @@ -267,7 +268,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 267 | return this.apply { aspectRatio?.let { setAspectRatio(it) } } | 268 | return this.apply { aspectRatio?.let { setAspectRatio(it) } } |
| 268 | } | 269 | } |
| 269 | 270 | ||
| 270 | private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder(): PictureInPictureParams.Builder { | 271 | private fun PictureInPictureParams.Builder.getPictureInPictureActionsBuilder(): |
| 272 | PictureInPictureParams.Builder { | ||
| 271 | val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf() | 273 | val pictureInPictureActions: MutableList<RemoteAction> = mutableListOf() |
| 272 | val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE | 274 | val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE |
| 273 | 275 | ||
| @@ -310,7 +312,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 310 | val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() | 312 | val pictureInPictureParamsBuilder = PictureInPictureParams.Builder() |
| 311 | .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() | 313 | .getPictureInPictureActionsBuilder().getPictureInPictureAspectBuilder() |
| 312 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | 314 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
| 313 | pictureInPictureParamsBuilder.setAutoEnterEnabled(BooleanSetting.PICTURE_IN_PICTURE.boolean) | 315 | pictureInPictureParamsBuilder.setAutoEnterEnabled( |
| 316 | BooleanSetting.PICTURE_IN_PICTURE.boolean | ||
| 317 | ) | ||
| 314 | } | 318 | } |
| 315 | setPictureInPictureParams(pictureInPictureParamsBuilder.build()) | 319 | setPictureInPictureParams(pictureInPictureParamsBuilder.build()) |
| 316 | } | 320 | } |
| @@ -341,7 +345,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { | |||
| 341 | } else { | 345 | } else { |
| 342 | try { | 346 | try { |
| 343 | unregisterReceiver(pictureInPictureReceiver) | 347 | unregisterReceiver(pictureInPictureReceiver) |
| 344 | } catch (ignored : Exception) { | 348 | } catch (ignored: Exception) { |
| 345 | } | 349 | } |
| 346 | } | 350 | } |
| 347 | } | 351 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index 83d08841b..e91277d35 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt | |||
| @@ -28,10 +28,9 @@ import org.yuzu.yuzu_emu.HomeNavigationDirections | |||
| 28 | import org.yuzu.yuzu_emu.NativeLibrary | 28 | import org.yuzu.yuzu_emu.NativeLibrary |
| 29 | import org.yuzu.yuzu_emu.R | 29 | import org.yuzu.yuzu_emu.R |
| 30 | import org.yuzu.yuzu_emu.YuzuApplication | 30 | import org.yuzu.yuzu_emu.YuzuApplication |
| 31 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder | ||
| 31 | import org.yuzu.yuzu_emu.databinding.CardGameBinding | 32 | import org.yuzu.yuzu_emu.databinding.CardGameBinding |
| 32 | import org.yuzu.yuzu_emu.activities.EmulationActivity | ||
| 33 | import org.yuzu.yuzu_emu.model.Game | 33 | import org.yuzu.yuzu_emu.model.Game |
| 34 | import org.yuzu.yuzu_emu.adapters.GameAdapter.GameViewHolder | ||
| 35 | import org.yuzu.yuzu_emu.model.GamesViewModel | 34 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 36 | 35 | ||
| 37 | class GameAdapter(private val activity: AppCompatActivity) : | 36 | class GameAdapter(private val activity: AppCompatActivity) : |
| @@ -60,7 +59,10 @@ class GameAdapter(private val activity: AppCompatActivity) : | |||
| 60 | override fun onClick(view: View) { | 59 | override fun onClick(view: View) { |
| 61 | val holder = view.tag as GameViewHolder | 60 | val holder = view.tag as GameViewHolder |
| 62 | 61 | ||
| 63 | val gameExists = DocumentFile.fromSingleUri(YuzuApplication.appContext, Uri.parse(holder.game.path))?.exists() == true | 62 | val gameExists = DocumentFile.fromSingleUri( |
| 63 | YuzuApplication.appContext, | ||
| 64 | Uri.parse(holder.game.path) | ||
| 65 | )?.exists() == true | ||
| 64 | if (!gameExists) { | 66 | if (!gameExists) { |
| 65 | Toast.makeText( | 67 | Toast.makeText( |
| 66 | YuzuApplication.appContext, | 68 | YuzuApplication.appContext, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt index b719dd539..d3df3bc81 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt | |||
| @@ -58,11 +58,12 @@ class HomeSettingAdapter(private val activity: AppCompatActivity, var options: L | |||
| 58 | ) | 58 | ) |
| 59 | 59 | ||
| 60 | when (option.titleId) { | 60 | when (option.titleId) { |
| 61 | R.string.get_early_access -> binding.optionLayout.background = | 61 | R.string.get_early_access -> |
| 62 | ContextCompat.getDrawable( | 62 | binding.optionLayout.background = |
| 63 | binding.optionCard.context, | 63 | ContextCompat.getDrawable( |
| 64 | R.drawable.premium_background | 64 | binding.optionCard.context, |
| 65 | ) | 65 | R.drawable.premium_background |
| 66 | ) | ||
| 66 | } | 67 | } |
| 67 | } | 68 | } |
| 68 | } | 69 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt index 82a6712b6..e058067c9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt | |||
| @@ -12,10 +12,10 @@ import android.view.WindowInsets | |||
| 12 | import android.view.inputmethod.InputMethodManager | 12 | import android.view.inputmethod.InputMethodManager |
| 13 | import androidx.annotation.Keep | 13 | import androidx.annotation.Keep |
| 14 | import androidx.core.view.ViewCompat | 14 | import androidx.core.view.ViewCompat |
| 15 | import java.io.Serializable | ||
| 15 | import org.yuzu.yuzu_emu.NativeLibrary | 16 | import org.yuzu.yuzu_emu.NativeLibrary |
| 16 | import org.yuzu.yuzu_emu.R | 17 | import org.yuzu.yuzu_emu.R |
| 17 | import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment | 18 | import org.yuzu.yuzu_emu.applets.keyboard.ui.KeyboardDialogFragment |
| 18 | import java.io.Serializable | ||
| 19 | 19 | ||
| 20 | @Keep | 20 | @Keep |
| 21 | object SoftwareKeyboard { | 21 | object SoftwareKeyboard { |
| @@ -40,19 +40,22 @@ object SoftwareKeyboard { | |||
| 40 | // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. | 40 | // There isn't a good way to know that the IMM is dismissed, so poll every 500ms to submit inline keyboard result. |
| 41 | val handler = Handler(Looper.myLooper()!!) | 41 | val handler = Handler(Looper.myLooper()!!) |
| 42 | val delayMs = 500 | 42 | val delayMs = 500 |
| 43 | handler.postDelayed(object : Runnable { | 43 | handler.postDelayed( |
| 44 | override fun run() { | 44 | object : Runnable { |
| 45 | val insets = ViewCompat.getRootWindowInsets(overlayView) | 45 | override fun run() { |
| 46 | val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime()) | 46 | val insets = ViewCompat.getRootWindowInsets(overlayView) |
| 47 | if (isKeyboardVisible) { | 47 | val isKeyboardVisible = insets!!.isVisible(WindowInsets.Type.ime()) |
| 48 | handler.postDelayed(this, delayMs.toLong()) | 48 | if (isKeyboardVisible) { |
| 49 | return | 49 | handler.postDelayed(this, delayMs.toLong()) |
| 50 | } | 50 | return |
| 51 | } | ||
| 51 | 52 | ||
| 52 | // No longer visible, submit the result. | 53 | // No longer visible, submit the result. |
| 53 | NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) | 54 | NativeLibrary.submitInlineKeyboardInput(KeyEvent.KEYCODE_ENTER) |
| 54 | } | 55 | } |
| 55 | }, delayMs.toLong()) | 56 | }, |
| 57 | delayMs.toLong() | ||
| 58 | ) | ||
| 56 | } | 59 | } |
| 57 | 60 | ||
| 58 | @JvmStatic | 61 | @JvmStatic |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt index 3b1559c80..a18efef19 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt | |||
| @@ -20,7 +20,10 @@ object DiskShaderCacheProgress { | |||
| 20 | emulationActivity.getString(R.string.loading), | 20 | emulationActivity.getString(R.string.loading), |
| 21 | emulationActivity.getString(R.string.preparing_shaders) | 21 | emulationActivity.getString(R.string.preparing_shaders) |
| 22 | ) | 22 | ) |
| 23 | fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG) | 23 | fragment.show( |
| 24 | emulationActivity.supportFragmentManager, | ||
| 25 | ShaderProgressDialogFragment.TAG | ||
| 26 | ) | ||
| 24 | } | 27 | } |
| 25 | synchronized(finishLock) { finishLock.wait() } | 28 | synchronized(finishLock) { finishLock.wait() } |
| 26 | } | 29 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt index 2c68c9ac3..8a8e0a6e8 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt | |||
| @@ -62,7 +62,9 @@ class ShaderProgressDialogFragment : DialogFragment() { | |||
| 62 | shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> | 62 | shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> |
| 63 | alertDialog.setMessage(msg) | 63 | alertDialog.setMessage(msg) |
| 64 | } | 64 | } |
| 65 | synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() } | 65 | synchronized(DiskShaderCacheProgress.finishLock) { |
| 66 | DiskShaderCacheProgress.finishLock.notifyAll() | ||
| 67 | } | ||
| 66 | } | 68 | } |
| 67 | 69 | ||
| 68 | override fun onDestroyView() { | 70 | override fun onDestroyView() { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt index 4c3a9ca80..f3be156b5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/DocumentProvider.kt | |||
| @@ -13,11 +13,11 @@ import android.os.ParcelFileDescriptor | |||
| 13 | import android.provider.DocumentsContract | 13 | import android.provider.DocumentsContract |
| 14 | import android.provider.DocumentsProvider | 14 | import android.provider.DocumentsProvider |
| 15 | import android.webkit.MimeTypeMap | 15 | import android.webkit.MimeTypeMap |
| 16 | import java.io.* | ||
| 16 | import org.yuzu.yuzu_emu.BuildConfig | 17 | import org.yuzu.yuzu_emu.BuildConfig |
| 17 | import org.yuzu.yuzu_emu.R | 18 | import org.yuzu.yuzu_emu.R |
| 18 | import org.yuzu.yuzu_emu.YuzuApplication | 19 | import org.yuzu.yuzu_emu.YuzuApplication |
| 19 | import org.yuzu.yuzu_emu.getPublicFilesDir | 20 | import org.yuzu.yuzu_emu.getPublicFilesDir |
| 20 | import java.io.* | ||
| 21 | 21 | ||
| 22 | class DocumentProvider : DocumentsProvider() { | 22 | class DocumentProvider : DocumentsProvider() { |
| 23 | private val baseDirectory: File | 23 | private val baseDirectory: File |
| @@ -44,7 +44,7 @@ class DocumentProvider : DocumentsProvider() { | |||
| 44 | DocumentsContract.Document.COLUMN_SIZE | 44 | DocumentsContract.Document.COLUMN_SIZE |
| 45 | ) | 45 | ) |
| 46 | 46 | ||
| 47 | const val AUTHORITY : String = BuildConfig.APPLICATION_ID + ".user" | 47 | const val AUTHORITY: String = BuildConfig.APPLICATION_ID + ".user" |
| 48 | const val ROOT_ID: String = "root" | 48 | const val ROOT_ID: String = "root" |
| 49 | } | 49 | } |
| 50 | 50 | ||
| @@ -58,7 +58,11 @@ class DocumentProvider : DocumentsProvider() { | |||
| 58 | private fun getFile(documentId: String): File { | 58 | private fun getFile(documentId: String): File { |
| 59 | if (documentId.startsWith(ROOT_ID)) { | 59 | if (documentId.startsWith(ROOT_ID)) { |
| 60 | val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1)) | 60 | val file = baseDirectory.resolve(documentId.drop(ROOT_ID.length + 1)) |
| 61 | if (!file.exists()) throw FileNotFoundException("${file.absolutePath} ($documentId) not found") | 61 | if (!file.exists()) { |
| 62 | throw FileNotFoundException( | ||
| 63 | "${file.absolutePath} ($documentId) not found" | ||
| 64 | ) | ||
| 65 | } | ||
| 62 | return file | 66 | return file |
| 63 | } else { | 67 | } else { |
| 64 | throw FileNotFoundException("'$documentId' is not in any known root") | 68 | throw FileNotFoundException("'$documentId' is not in any known root") |
| @@ -80,7 +84,8 @@ class DocumentProvider : DocumentsProvider() { | |||
| 80 | add(DocumentsContract.Root.COLUMN_SUMMARY, null) | 84 | add(DocumentsContract.Root.COLUMN_SUMMARY, null) |
| 81 | add( | 85 | add( |
| 82 | DocumentsContract.Root.COLUMN_FLAGS, | 86 | DocumentsContract.Root.COLUMN_FLAGS, |
| 83 | DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD | 87 | DocumentsContract.Root.FLAG_SUPPORTS_CREATE or |
| 88 | DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD | ||
| 84 | ) | 89 | ) |
| 85 | add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name)) | 90 | add(DocumentsContract.Root.COLUMN_TITLE, context!!.getString(R.string.app_name)) |
| 86 | add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory)) | 91 | add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocumentId(baseDirectory)) |
| @@ -127,11 +132,13 @@ class DocumentProvider : DocumentsProvider() { | |||
| 127 | 132 | ||
| 128 | try { | 133 | try { |
| 129 | if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) { | 134 | if (DocumentsContract.Document.MIME_TYPE_DIR == mimeType) { |
| 130 | if (!newFile.mkdir()) | 135 | if (!newFile.mkdir()) { |
| 131 | throw IOException("Failed to create directory") | 136 | throw IOException("Failed to create directory") |
| 137 | } | ||
| 132 | } else { | 138 | } else { |
| 133 | if (!newFile.createNewFile()) | 139 | if (!newFile.createNewFile()) { |
| 134 | throw IOException("Failed to create file") | 140 | throw IOException("Failed to create file") |
| 141 | } | ||
| 135 | } | 142 | } |
| 136 | } catch (e: IOException) { | 143 | } catch (e: IOException) { |
| 137 | throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}") | 144 | throw FileNotFoundException("Couldn't create document '${newFile.path}': ${e.message}") |
| @@ -142,8 +149,9 @@ class DocumentProvider : DocumentsProvider() { | |||
| 142 | 149 | ||
| 143 | override fun deleteDocument(documentId: String?) { | 150 | override fun deleteDocument(documentId: String?) { |
| 144 | val file = getFile(documentId!!) | 151 | val file = getFile(documentId!!) |
| 145 | if (!file.delete()) | 152 | if (!file.delete()) { |
| 146 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") | 153 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") |
| 154 | } | ||
| 147 | } | 155 | } |
| 148 | 156 | ||
| 149 | override fun removeDocument(documentId: String, parentDocumentId: String?) { | 157 | override fun removeDocument(documentId: String, parentDocumentId: String?) { |
| @@ -151,38 +159,55 @@ class DocumentProvider : DocumentsProvider() { | |||
| 151 | val file = getFile(documentId) | 159 | val file = getFile(documentId) |
| 152 | 160 | ||
| 153 | if (parent == file || file.parentFile == null || file.parentFile!! == parent) { | 161 | if (parent == file || file.parentFile == null || file.parentFile!! == parent) { |
| 154 | if (!file.delete()) | 162 | if (!file.delete()) { |
| 155 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") | 163 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") |
| 164 | } | ||
| 156 | } else { | 165 | } else { |
| 157 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") | 166 | throw FileNotFoundException("Couldn't delete document with ID '$documentId'") |
| 158 | } | 167 | } |
| 159 | } | 168 | } |
| 160 | 169 | ||
| 161 | override fun renameDocument(documentId: String?, displayName: String?): String { | 170 | override fun renameDocument(documentId: String?, displayName: String?): String { |
| 162 | if (displayName == null) | 171 | if (displayName == null) { |
| 163 | throw FileNotFoundException("Couldn't rename document '$documentId' as the new name is null") | 172 | throw FileNotFoundException( |
| 173 | "Couldn't rename document '$documentId' as the new name is null" | ||
| 174 | ) | ||
| 175 | } | ||
| 164 | 176 | ||
| 165 | val sourceFile = getFile(documentId!!) | 177 | val sourceFile = getFile(documentId!!) |
| 166 | val sourceParentFile = sourceFile.parentFile | 178 | val sourceParentFile = sourceFile.parentFile |
| 167 | ?: throw FileNotFoundException("Couldn't rename document '$documentId' as it has no parent") | 179 | ?: throw FileNotFoundException( |
| 180 | "Couldn't rename document '$documentId' as it has no parent" | ||
| 181 | ) | ||
| 168 | val destFile = sourceParentFile.resolve(displayName) | 182 | val destFile = sourceParentFile.resolve(displayName) |
| 169 | 183 | ||
| 170 | try { | 184 | try { |
| 171 | if (!sourceFile.renameTo(destFile)) | 185 | if (!sourceFile.renameTo(destFile)) { |
| 172 | throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'") | 186 | throw FileNotFoundException( |
| 187 | "Couldn't rename document from '${sourceFile.name}' to '${destFile.name}'" | ||
| 188 | ) | ||
| 189 | } | ||
| 173 | } catch (e: Exception) { | 190 | } catch (e: Exception) { |
| 174 | throw FileNotFoundException("Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': ${e.message}") | 191 | throw FileNotFoundException( |
| 192 | "Couldn't rename document from '${sourceFile.name}' to '${destFile.name}': " + | ||
| 193 | "${e.message}" | ||
| 194 | ) | ||
| 175 | } | 195 | } |
| 176 | 196 | ||
| 177 | return getDocumentId(destFile) | 197 | return getDocumentId(destFile) |
| 178 | } | 198 | } |
| 179 | 199 | ||
| 180 | private fun copyDocument( | 200 | private fun copyDocument( |
| 181 | sourceDocumentId: String, sourceParentDocumentId: String, | 201 | sourceDocumentId: String, |
| 202 | sourceParentDocumentId: String, | ||
| 182 | targetParentDocumentId: String? | 203 | targetParentDocumentId: String? |
| 183 | ): String { | 204 | ): String { |
| 184 | if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) | 205 | if (!isChildDocument(sourceParentDocumentId, sourceDocumentId)) { |
| 185 | throw FileNotFoundException("Couldn't copy document '$sourceDocumentId' as its parent is not '$sourceParentDocumentId'") | 206 | throw FileNotFoundException( |
| 207 | "Couldn't copy document '$sourceDocumentId' as its parent is not " + | ||
| 208 | "'$sourceParentDocumentId'" | ||
| 209 | ) | ||
| 210 | } | ||
| 186 | 211 | ||
| 187 | return copyDocument(sourceDocumentId, targetParentDocumentId) | 212 | return copyDocument(sourceDocumentId, targetParentDocumentId) |
| 188 | } | 213 | } |
| @@ -193,8 +218,13 @@ class DocumentProvider : DocumentsProvider() { | |||
| 193 | val newFile = parent.resolveWithoutConflict(oldFile.name) | 218 | val newFile = parent.resolveWithoutConflict(oldFile.name) |
| 194 | 219 | ||
| 195 | try { | 220 | try { |
| 196 | if (!(newFile.createNewFile() && newFile.setWritable(true) && newFile.setReadable(true))) | 221 | if (!( |
| 222 | newFile.createNewFile() && newFile.setWritable(true) && | ||
| 223 | newFile.setReadable(true) | ||
| 224 | ) | ||
| 225 | ) { | ||
| 197 | throw IOException("Couldn't create new file") | 226 | throw IOException("Couldn't create new file") |
| 227 | } | ||
| 198 | 228 | ||
| 199 | FileInputStream(oldFile).use { inStream -> | 229 | FileInputStream(oldFile).use { inStream -> |
| 200 | FileOutputStream(newFile).use { outStream -> | 230 | FileOutputStream(newFile).use { outStream -> |
| @@ -209,12 +239,14 @@ class DocumentProvider : DocumentsProvider() { | |||
| 209 | } | 239 | } |
| 210 | 240 | ||
| 211 | override fun moveDocument( | 241 | override fun moveDocument( |
| 212 | sourceDocumentId: String, sourceParentDocumentId: String?, | 242 | sourceDocumentId: String, |
| 243 | sourceParentDocumentId: String?, | ||
| 213 | targetParentDocumentId: String? | 244 | targetParentDocumentId: String? |
| 214 | ): String { | 245 | ): String { |
| 215 | try { | 246 | try { |
| 216 | val newDocumentId = copyDocument( | 247 | val newDocumentId = copyDocument( |
| 217 | sourceDocumentId, sourceParentDocumentId!!, | 248 | sourceDocumentId, |
| 249 | sourceParentDocumentId!!, | ||
| 218 | targetParentDocumentId | 250 | targetParentDocumentId |
| 219 | ) | 251 | ) |
| 220 | removeDocument(sourceDocumentId, sourceParentDocumentId) | 252 | removeDocument(sourceDocumentId, sourceParentDocumentId) |
| @@ -245,24 +277,30 @@ class DocumentProvider : DocumentsProvider() { | |||
| 245 | add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId) | 277 | add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, localDocumentId) |
| 246 | add( | 278 | add( |
| 247 | DocumentsContract.Document.COLUMN_DISPLAY_NAME, | 279 | DocumentsContract.Document.COLUMN_DISPLAY_NAME, |
| 248 | if (localFile == baseDirectory) context!!.getString(R.string.app_name) else localFile.name | 280 | if (localFile == baseDirectory) { |
| 281 | context!!.getString(R.string.app_name) | ||
| 282 | } else { | ||
| 283 | localFile.name | ||
| 284 | } | ||
| 249 | ) | 285 | ) |
| 250 | add(DocumentsContract.Document.COLUMN_SIZE, localFile.length()) | 286 | add(DocumentsContract.Document.COLUMN_SIZE, localFile.length()) |
| 251 | add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile)) | 287 | add(DocumentsContract.Document.COLUMN_MIME_TYPE, getTypeForFile(localFile)) |
| 252 | add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified()) | 288 | add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, localFile.lastModified()) |
| 253 | add(DocumentsContract.Document.COLUMN_FLAGS, flags) | 289 | add(DocumentsContract.Document.COLUMN_FLAGS, flags) |
| 254 | if (localFile == baseDirectory) | 290 | if (localFile == baseDirectory) { |
| 255 | add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu) | 291 | add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_yuzu) |
| 292 | } | ||
| 256 | } | 293 | } |
| 257 | 294 | ||
| 258 | return cursor | 295 | return cursor |
| 259 | } | 296 | } |
| 260 | 297 | ||
| 261 | private fun getTypeForFile(file: File): Any { | 298 | private fun getTypeForFile(file: File): Any { |
| 262 | return if (file.isDirectory) | 299 | return if (file.isDirectory) { |
| 263 | DocumentsContract.Document.MIME_TYPE_DIR | 300 | DocumentsContract.Document.MIME_TYPE_DIR |
| 264 | else | 301 | } else { |
| 265 | getTypeForName(file.name) | 302 | getTypeForName(file.name) |
| 303 | } | ||
| 266 | } | 304 | } |
| 267 | 305 | ||
| 268 | private fun getTypeForName(name: String): Any { | 306 | private fun getTypeForName(name: String): Any { |
| @@ -270,8 +308,9 @@ class DocumentProvider : DocumentsProvider() { | |||
| 270 | if (lastDot >= 0) { | 308 | if (lastDot >= 0) { |
| 271 | val extension = name.substring(lastDot + 1) | 309 | val extension = name.substring(lastDot + 1) |
| 272 | val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) | 310 | val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) |
| 273 | if (mime != null) | 311 | if (mime != null) { |
| 274 | return mime | 312 | return mime |
| 313 | } | ||
| 275 | } | 314 | } |
| 276 | return "application/octect-stream" | 315 | return "application/octect-stream" |
| 277 | } | 316 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 63b4df273..d41933766 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt | |||
| @@ -8,6 +8,9 @@ enum class BooleanSetting( | |||
| 8 | override val section: String, | 8 | override val section: String, |
| 9 | override val defaultValue: Boolean | 9 | override val defaultValue: Boolean |
| 10 | ) : AbstractBooleanSetting { | 10 | ) : AbstractBooleanSetting { |
| 11 | CPU_DEBUG_MODE("cpu_debug_mode", Settings.SECTION_CPU, false), | ||
| 12 | FASTMEM("cpuopt_fastmem", Settings.SECTION_CPU, true), | ||
| 13 | FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.SECTION_CPU, true), | ||
| 11 | PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), | 14 | PICTURE_IN_PICTURE("picture_in_picture", Settings.SECTION_GENERAL, true), |
| 12 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); | 15 | USE_CUSTOM_RTC("custom_rtc_enabled", Settings.SECTION_SYSTEM, false); |
| 13 | 16 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index 6bcb7bee0..88afb2223 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt | |||
| @@ -4,11 +4,11 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model | 4 | package org.yuzu.yuzu_emu.features.settings.model |
| 5 | 5 | ||
| 6 | import android.text.TextUtils | 6 | import android.text.TextUtils |
| 7 | import java.util.* | ||
| 7 | import org.yuzu.yuzu_emu.R | 8 | import org.yuzu.yuzu_emu.R |
| 8 | import org.yuzu.yuzu_emu.YuzuApplication | 9 | import org.yuzu.yuzu_emu.YuzuApplication |
| 9 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | 10 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView |
| 10 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 11 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 11 | import java.util.* | ||
| 12 | 12 | ||
| 13 | class Settings { | 13 | class Settings { |
| 14 | private var gameId: String? = null | 14 | private var gameId: String? = null |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt index 63f95690c..6621289fd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/StringSetting.kt | |||
| @@ -8,6 +8,7 @@ enum class StringSetting( | |||
| 8 | override val section: String, | 8 | override val section: String, |
| 9 | override val defaultValue: String | 9 | override val defaultValue: String |
| 10 | ) : AbstractStringSetting { | 10 | ) : AbstractStringSetting { |
| 11 | AUDIO_OUTPUT_ENGINE("output_engine", Settings.SECTION_AUDIO, "auto"), | ||
| 11 | CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); | 12 | CUSTOM_RTC("custom_rtc", Settings.SECTION_SYSTEM, "0"); |
| 12 | 13 | ||
| 13 | override var string: String = defaultValue | 14 | override var string: String = defaultValue |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt index 0f8edbfb0..a67001311 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/HeaderSetting.kt | |||
| @@ -3,12 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 7 | |||
| 8 | class HeaderSetting( | 6 | class HeaderSetting( |
| 9 | setting: AbstractSetting?, | 7 | titleId: Int |
| 10 | titleId: Int, | 8 | ) : SettingsItem(null, titleId, 0) { |
| 11 | descriptionId: Int | ||
| 12 | ) : SettingsItem(setting, titleId, descriptionId) { | ||
| 13 | override val type = TYPE_HEADER | 9 | override val type = TYPE_HEADER |
| 14 | } | 10 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt index 9eac9904e..7306ec458 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SingleChoiceSetting.kt | |||
| @@ -4,7 +4,6 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 8 | 7 | ||
| 9 | class SingleChoiceSetting( | 8 | class SingleChoiceSetting( |
| 10 | setting: AbstractIntSetting?, | 9 | setting: AbstractIntSetting?, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt index 842648ce4..92d0167ae 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SliderSetting.kt | |||
| @@ -3,13 +3,11 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import kotlin.math.roundToInt | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting | 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting |
| 8 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 9 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
| 9 | import org.yuzu.yuzu_emu.features.settings.model.FloatSetting | ||
| 10 | import org.yuzu.yuzu_emu.features.settings.model.IntSetting | ||
| 11 | import org.yuzu.yuzu_emu.utils.Log | 10 | import org.yuzu.yuzu_emu.utils.Log |
| 12 | import kotlin.math.roundToInt | ||
| 13 | 11 | ||
| 14 | class SliderSetting( | 12 | class SliderSetting( |
| 15 | setting: AbstractSetting?, | 13 | setting: AbstractSetting?, |
| @@ -19,7 +17,7 @@ class SliderSetting( | |||
| 19 | val max: Int, | 17 | val max: Int, |
| 20 | val units: String, | 18 | val units: String, |
| 21 | val key: String? = null, | 19 | val key: String? = null, |
| 22 | val defaultValue: Int? = null, | 20 | val defaultValue: Int? = null |
| 23 | ) : SettingsItem(setting, titleId, descriptionId) { | 21 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 24 | override val type = TYPE_SLIDER | 22 | override val type = TYPE_SLIDER |
| 25 | 23 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt index 9e9b00d10..3b6731dcd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/StringSingleChoiceSetting.kt | |||
| @@ -5,24 +5,25 @@ package org.yuzu.yuzu_emu.features.settings.model.view | |||
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting |
| 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting | 7 | import org.yuzu.yuzu_emu.features.settings.model.AbstractStringSetting |
| 8 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | ||
| 9 | 8 | ||
| 10 | class StringSingleChoiceSetting( | 9 | class StringSingleChoiceSetting( |
| 11 | val key: String? = null, | ||
| 12 | setting: AbstractSetting?, | 10 | setting: AbstractSetting?, |
| 13 | titleId: Int, | 11 | titleId: Int, |
| 14 | descriptionId: Int, | 12 | descriptionId: Int, |
| 15 | val choicesId: Array<String>, | 13 | val choices: Array<String>, |
| 16 | private val valuesId: Array<String>?, | 14 | val values: Array<String>?, |
| 15 | val key: String? = null, | ||
| 17 | private val defaultValue: String? = null | 16 | private val defaultValue: String? = null |
| 18 | ) : SettingsItem(setting, titleId, descriptionId) { | 17 | ) : SettingsItem(setting, titleId, descriptionId) { |
| 19 | override val type = TYPE_STRING_SINGLE_CHOICE | 18 | override val type = TYPE_STRING_SINGLE_CHOICE |
| 20 | 19 | ||
| 21 | fun getValueAt(index: Int): String? { | 20 | fun getValueAt(index: Int): String? { |
| 22 | if (valuesId == null) return null | 21 | if (values == null) return null |
| 23 | return if (index >= 0 && index < valuesId.size) { | 22 | return if (index >= 0 && index < values.size) { |
| 24 | valuesId[index] | 23 | values[index] |
| 25 | } else "" | 24 | } else { |
| 25 | "" | ||
| 26 | } | ||
| 26 | } | 27 | } |
| 27 | 28 | ||
| 28 | val selectedValue: String | 29 | val selectedValue: String |
| @@ -35,8 +36,8 @@ class StringSingleChoiceSetting( | |||
| 35 | val selectValueIndex: Int | 36 | val selectValueIndex: Int |
| 36 | get() { | 37 | get() { |
| 37 | val selectedValue = selectedValue | 38 | val selectedValue = selectedValue |
| 38 | for (i in valuesId!!.indices) { | 39 | for (i in values!!.indices) { |
| 39 | if (valuesId[i] == selectedValue) { | 40 | if (values[i] == selectedValue) { |
| 40 | return i | 41 | return i |
| 41 | } | 42 | } |
| 42 | } | 43 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt index a3ef59c2f..8a9d13a92 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt | |||
| @@ -3,8 +3,6 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.model.view | 4 | package org.yuzu.yuzu_emu.features.settings.model.view |
| 5 | 5 | ||
| 6 | import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting | ||
| 7 | |||
| 8 | class SubmenuSetting( | 6 | class SubmenuSetting( |
| 9 | titleId: Int, | 7 | titleId: Int, |
| 10 | descriptionId: Int, | 8 | descriptionId: Int, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt index da7062b87..a5af5a7ae 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt | |||
| @@ -8,18 +8,18 @@ import android.content.Intent | |||
| 8 | import android.os.Bundle | 8 | import android.os.Bundle |
| 9 | import android.view.Menu | 9 | import android.view.Menu |
| 10 | import android.view.View | 10 | import android.view.View |
| 11 | import android.view.ViewGroup.MarginLayoutParams | ||
| 11 | import android.widget.Toast | 12 | import android.widget.Toast |
| 13 | import androidx.activity.OnBackPressedCallback | ||
| 14 | import androidx.activity.result.ActivityResultLauncher | ||
| 12 | import androidx.activity.viewModels | 15 | import androidx.activity.viewModels |
| 13 | import androidx.appcompat.app.AppCompatActivity | 16 | import androidx.appcompat.app.AppCompatActivity |
| 14 | import androidx.core.view.ViewCompat | 17 | import androidx.core.view.ViewCompat |
| 15 | import androidx.core.view.WindowCompat | 18 | import androidx.core.view.WindowCompat |
| 16 | import androidx.core.view.WindowInsetsCompat | 19 | import androidx.core.view.WindowInsetsCompat |
| 17 | import android.view.ViewGroup.MarginLayoutParams | ||
| 18 | import androidx.activity.OnBackPressedCallback | ||
| 19 | import androidx.activity.result.ActivityResultLauncher | ||
| 20 | import androidx.core.view.updatePadding | 20 | import androidx.core.view.updatePadding |
| 21 | import com.google.android.material.color.MaterialColors | 21 | import com.google.android.material.color.MaterialColors |
| 22 | import org.yuzu.yuzu_emu.NativeLibrary | 22 | import java.io.IOException |
| 23 | import org.yuzu.yuzu_emu.R | 23 | import org.yuzu.yuzu_emu.R |
| 24 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding | 24 | import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding |
| 25 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting | 25 | import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting |
| @@ -30,7 +30,6 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel | |||
| 30 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting | 30 | import org.yuzu.yuzu_emu.features.settings.model.StringSetting |
| 31 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 31 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 32 | import org.yuzu.yuzu_emu.utils.* | 32 | import org.yuzu.yuzu_emu.utils.* |
| 33 | import java.io.IOException | ||
| 34 | 33 | ||
| 35 | class SettingsActivity : AppCompatActivity(), SettingsActivityView { | 34 | class SettingsActivity : AppCompatActivity(), SettingsActivityView { |
| 36 | private val presenter = SettingsActivityPresenter(this) | 35 | private val presenter = SettingsActivityPresenter(this) |
| @@ -60,7 +59,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 60 | setSupportActionBar(binding.toolbarSettings) | 59 | setSupportActionBar(binding.toolbarSettings) |
| 61 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) | 60 | supportActionBar!!.setDisplayHomeAsUpEnabled(true) |
| 62 | 61 | ||
| 63 | if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { | 62 | if (InsetsHelper.getSystemGestureType(applicationContext) != |
| 63 | InsetsHelper.GESTURE_NAVIGATION | ||
| 64 | ) { | ||
| 64 | binding.navigationBarShade.setBackgroundColor( | 65 | binding.navigationBarShade.setBackgroundColor( |
| 65 | ThemeHelper.getColorWithOpacity( | 66 | ThemeHelper.getColorWithOpacity( |
| 66 | MaterialColors.getColor( | 67 | MaterialColors.getColor( |
| @@ -76,7 +77,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 76 | this, | 77 | this, |
| 77 | object : OnBackPressedCallback(true) { | 78 | object : OnBackPressedCallback(true) { |
| 78 | override fun handleOnBackPressed() = navigateBack() | 79 | override fun handleOnBackPressed() = navigateBack() |
| 79 | }) | 80 | } |
| 81 | ) | ||
| 80 | 82 | ||
| 81 | setInsets() | 83 | setInsets() |
| 82 | } | 84 | } |
| @@ -149,11 +151,13 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 149 | private fun areSystemAnimationsEnabled(): Boolean { | 151 | private fun areSystemAnimationsEnabled(): Boolean { |
| 150 | val duration = android.provider.Settings.Global.getFloat( | 152 | val duration = android.provider.Settings.Global.getFloat( |
| 151 | contentResolver, | 153 | contentResolver, |
| 152 | android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, 1f | 154 | android.provider.Settings.Global.ANIMATOR_DURATION_SCALE, |
| 155 | 1f | ||
| 153 | ) | 156 | ) |
| 154 | val transition = android.provider.Settings.Global.getFloat( | 157 | val transition = android.provider.Settings.Global.getFloat( |
| 155 | contentResolver, | 158 | contentResolver, |
| 156 | android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, 1f | 159 | android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE, |
| 160 | 1f | ||
| 157 | ) | 161 | ) |
| 158 | return duration != 0f && transition != 0f | 162 | return duration != 0f && transition != 0f |
| 159 | } | 163 | } |
| @@ -208,7 +212,9 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { | |||
| 208 | get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? | 212 | get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment? |
| 209 | 213 | ||
| 210 | private fun setInsets() { | 214 | private fun setInsets() { |
| 211 | ViewCompat.setOnApplyWindowInsetsListener(binding.frameContent) { view: View, windowInsets: WindowInsetsCompat -> | 215 | ViewCompat.setOnApplyWindowInsetsListener( |
| 216 | binding.frameContent | ||
| 217 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 212 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 218 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 213 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 219 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 214 | view.updatePadding( | 220 | view.updatePadding( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt index 4361d95fb..93e677b21 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt | |||
| @@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.features.settings.ui | |||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import android.os.Bundle | 7 | import android.os.Bundle |
| 8 | import android.text.TextUtils | 8 | import android.text.TextUtils |
| 9 | import java.io.File | ||
| 9 | import org.yuzu.yuzu_emu.NativeLibrary | 10 | import org.yuzu.yuzu_emu.NativeLibrary |
| 10 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 11 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 11 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile | 12 | import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile |
| 12 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 13 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 13 | import org.yuzu.yuzu_emu.utils.Log | 14 | import org.yuzu.yuzu_emu.utils.Log |
| 14 | import java.io.File | ||
| 15 | 15 | ||
| 16 | class SettingsActivityPresenter(private val activityView: SettingsActivityView) { | 16 | class SettingsActivityPresenter(private val activityView: SettingsActivityView) { |
| 17 | val settings: Settings get() = activityView.settings | 17 | val settings: Settings get() = activityView.settings |
| @@ -46,9 +46,15 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView) | |||
| 46 | 46 | ||
| 47 | private fun prepareDirectoriesIfNeeded() { | 47 | private fun prepareDirectoriesIfNeeded() { |
| 48 | val configFile = | 48 | val configFile = |
| 49 | File(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") | 49 | File( |
| 50 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 51 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 52 | ) | ||
| 50 | if (!configFile.exists()) { | 53 | if (!configFile.exists()) { |
| 51 | Log.error(DirectoryInitialization.userDirectory + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini") | 54 | Log.error( |
| 55 | "${DirectoryInitialization.userDirectory}/config/" + | ||
| 56 | "${SettingsFile.FILE_NAME_CONFIG}.ini" | ||
| 57 | ) | ||
| 52 | Log.error("yuzu config file could not be found!") | 58 | Log.error("yuzu config file could not be found!") |
| 53 | } | 59 | } |
| 54 | 60 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index 1eb4899fc..ce0b92c90 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt | |||
| @@ -13,7 +13,6 @@ import android.view.ViewGroup | |||
| 13 | import android.widget.TextView | 13 | import android.widget.TextView |
| 14 | import androidx.appcompat.app.AlertDialog | 14 | import androidx.appcompat.app.AlertDialog |
| 15 | import androidx.appcompat.app.AppCompatActivity | 15 | import androidx.appcompat.app.AppCompatActivity |
| 16 | import androidx.fragment.app.setFragmentResultListener | ||
| 17 | import androidx.recyclerview.widget.RecyclerView | 16 | import androidx.recyclerview.widget.RecyclerView |
| 18 | import com.google.android.material.datepicker.MaterialDatePicker | 17 | import com.google.android.material.datepicker.MaterialDatePicker |
| 19 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 18 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| @@ -139,7 +138,7 @@ class SettingsAdapter( | |||
| 139 | clickedItem = item | 138 | clickedItem = item |
| 140 | dialog = MaterialAlertDialogBuilder(context) | 139 | dialog = MaterialAlertDialogBuilder(context) |
| 141 | .setTitle(item.nameId) | 140 | .setTitle(item.nameId) |
| 142 | .setSingleChoiceItems(item.choicesId, item.selectValueIndex, this) | 141 | .setSingleChoiceItems(item.choices, item.selectValueIndex, this) |
| 143 | .show() | 142 | .show() |
| 144 | } | 143 | } |
| 145 | 144 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt index 867147950..70a74c4dd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt | |||
| @@ -50,7 +50,10 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 50 | 50 | ||
| 51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
| 52 | settingsAdapter = SettingsAdapter(this, requireActivity()) | 52 | settingsAdapter = SettingsAdapter(this, requireActivity()) |
| 53 | val dividerDecoration = MaterialDividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL) | 53 | val dividerDecoration = MaterialDividerItemDecoration( |
| 54 | requireContext(), | ||
| 55 | LinearLayoutManager.VERTICAL | ||
| 56 | ) | ||
| 54 | dividerDecoration.isLastItemDecorated = false | 57 | dividerDecoration.isLastItemDecorated = false |
| 55 | binding.listSettings.apply { | 58 | binding.listSettings.apply { |
| 56 | adapter = settingsAdapter | 59 | adapter = settingsAdapter |
| @@ -99,7 +102,9 @@ class SettingsFragment : Fragment(), SettingsFragmentView { | |||
| 99 | } | 102 | } |
| 100 | 103 | ||
| 101 | private fun setInsets() { | 104 | private fun setInsets() { |
| 102 | ViewCompat.setOnApplyWindowInsetsListener(binding.listSettings) { view: View, windowInsets: WindowInsetsCompat -> | 105 | ViewCompat.setOnApplyWindowInsetsListener( |
| 106 | binding.listSettings | ||
| 107 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 103 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 108 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 104 | view.updatePadding(bottom = insets.bottom) | 109 | view.updatePadding(bottom = insets.bottom) |
| 105 | windowInsets | 110 | windowInsets |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index b611389a1..59c1d9d54 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt | |||
| @@ -7,7 +7,6 @@ import android.content.SharedPreferences | |||
| 7 | import android.os.Build | 7 | import android.os.Build |
| 8 | import android.text.TextUtils | 8 | import android.text.TextUtils |
| 9 | import androidx.preference.PreferenceManager | 9 | import androidx.preference.PreferenceManager |
| 10 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| 11 | import org.yuzu.yuzu_emu.R | 10 | import org.yuzu.yuzu_emu.R |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | 11 | import org.yuzu.yuzu_emu.YuzuApplication |
| 13 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting | 12 | import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting |
| @@ -43,7 +42,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 43 | } | 42 | } |
| 44 | 43 | ||
| 45 | fun putSetting(setting: AbstractSetting) { | 44 | fun putSetting(setting: AbstractSetting) { |
| 46 | if (setting.section == null) { | 45 | if (setting.section == null || setting.key == null) { |
| 47 | return | 46 | return |
| 48 | } | 47 | } |
| 49 | 48 | ||
| @@ -236,7 +235,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 236 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { | 235 | private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { |
| 237 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) | 236 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_graphics)) |
| 238 | sl.apply { | 237 | sl.apply { |
| 239 | |||
| 240 | add( | 238 | add( |
| 241 | SingleChoiceSetting( | 239 | SingleChoiceSetting( |
| 242 | IntSetting.RENDERER_ACCURACY, | 240 | IntSetting.RENDERER_ACCURACY, |
| @@ -355,18 +353,31 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 355 | 353 | ||
| 356 | private fun addAudioSettings(sl: ArrayList<SettingsItem>) { | 354 | private fun addAudioSettings(sl: ArrayList<SettingsItem>) { |
| 357 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) | 355 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) |
| 358 | sl.add( | 356 | sl.apply { |
| 359 | SliderSetting( | 357 | add( |
| 360 | IntSetting.AUDIO_VOLUME, | 358 | StringSingleChoiceSetting( |
| 361 | R.string.audio_volume, | 359 | StringSetting.AUDIO_OUTPUT_ENGINE, |
| 362 | R.string.audio_volume_description, | 360 | R.string.audio_output_engine, |
| 363 | 0, | 361 | 0, |
| 364 | 100, | 362 | settingsActivity.resources.getStringArray(R.array.outputEngineEntries), |
| 365 | "%", | 363 | settingsActivity.resources.getStringArray(R.array.outputEngineValues), |
| 366 | IntSetting.AUDIO_VOLUME.key, | 364 | StringSetting.AUDIO_OUTPUT_ENGINE.key, |
| 367 | IntSetting.AUDIO_VOLUME.defaultValue | 365 | StringSetting.AUDIO_OUTPUT_ENGINE.defaultValue |
| 368 | ) | 366 | ) |
| 369 | ) | 367 | ) |
| 368 | add( | ||
| 369 | SliderSetting( | ||
| 370 | IntSetting.AUDIO_VOLUME, | ||
| 371 | R.string.audio_volume, | ||
| 372 | R.string.audio_volume_description, | ||
| 373 | 0, | ||
| 374 | 100, | ||
| 375 | "%", | ||
| 376 | IntSetting.AUDIO_VOLUME.key, | ||
| 377 | IntSetting.AUDIO_VOLUME.defaultValue | ||
| 378 | ) | ||
| 379 | ) | ||
| 380 | } | ||
| 370 | } | 381 | } |
| 371 | 382 | ||
| 372 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { | 383 | private fun addThemeSettings(sl: ArrayList<SettingsItem>) { |
| @@ -469,6 +480,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 469 | private fun addDebugSettings(sl: ArrayList<SettingsItem>) { | 480 | private fun addDebugSettings(sl: ArrayList<SettingsItem>) { |
| 470 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) | 481 | settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_debug)) |
| 471 | sl.apply { | 482 | sl.apply { |
| 483 | add(HeaderSetting(R.string.gpu)) | ||
| 472 | add( | 484 | add( |
| 473 | SingleChoiceSetting( | 485 | SingleChoiceSetting( |
| 474 | IntSetting.RENDERER_BACKEND, | 486 | IntSetting.RENDERER_BACKEND, |
| @@ -489,6 +501,39 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) | |||
| 489 | IntSetting.RENDERER_DEBUG.defaultValue | 501 | IntSetting.RENDERER_DEBUG.defaultValue |
| 490 | ) | 502 | ) |
| 491 | ) | 503 | ) |
| 504 | |||
| 505 | add(HeaderSetting(R.string.cpu)) | ||
| 506 | add( | ||
| 507 | SwitchSetting( | ||
| 508 | BooleanSetting.CPU_DEBUG_MODE, | ||
| 509 | R.string.cpu_debug_mode, | ||
| 510 | R.string.cpu_debug_mode_description, | ||
| 511 | BooleanSetting.CPU_DEBUG_MODE.key, | ||
| 512 | BooleanSetting.CPU_DEBUG_MODE.defaultValue | ||
| 513 | ) | ||
| 514 | ) | ||
| 515 | |||
| 516 | val fastmem = object : AbstractBooleanSetting { | ||
| 517 | override var boolean: Boolean | ||
| 518 | get() = | ||
| 519 | BooleanSetting.FASTMEM.boolean && BooleanSetting.FASTMEM_EXCLUSIVES.boolean | ||
| 520 | set(value) { | ||
| 521 | BooleanSetting.FASTMEM.boolean = value | ||
| 522 | BooleanSetting.FASTMEM_EXCLUSIVES.boolean = value | ||
| 523 | } | ||
| 524 | override val key: String? = null | ||
| 525 | override val section: String = Settings.SECTION_CPU | ||
| 526 | override val isRuntimeEditable: Boolean = false | ||
| 527 | override val valueAsString: String = "" | ||
| 528 | override val defaultValue: Any = true | ||
| 529 | } | ||
| 530 | add( | ||
| 531 | SwitchSetting( | ||
| 532 | fastmem, | ||
| 533 | R.string.fastmem, | ||
| 534 | 0 | ||
| 535 | ) | ||
| 536 | ) | ||
| 492 | } | 537 | } |
| 493 | } | 538 | } |
| 494 | } | 539 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt index 04c045e77..7955532ee 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/DateTimeViewHolder.kt | |||
| @@ -4,15 +4,15 @@ | |||
| 4 | package org.yuzu.yuzu_emu.features.settings.ui.viewholder | 4 | package org.yuzu.yuzu_emu.features.settings.ui.viewholder |
| 5 | 5 | ||
| 6 | import android.view.View | 6 | import android.view.View |
| 7 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | ||
| 8 | import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting | ||
| 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 10 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
| 11 | import java.time.Instant | 7 | import java.time.Instant |
| 12 | import java.time.ZoneId | 8 | import java.time.ZoneId |
| 13 | import java.time.ZonedDateTime | 9 | import java.time.ZonedDateTime |
| 14 | import java.time.format.DateTimeFormatter | 10 | import java.time.format.DateTimeFormatter |
| 15 | import java.time.format.FormatStyle | 11 | import java.time.format.FormatStyle |
| 12 | import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding | ||
| 13 | import org.yuzu.yuzu_emu.features.settings.model.view.DateTimeSetting | ||
| 14 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | ||
| 15 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | ||
| 16 | 16 | ||
| 17 | class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : | 17 | class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : |
| 18 | SettingViewHolder(binding.root, adapter) { | 18 | SettingViewHolder(binding.root, adapter) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt index de764a27f..e4e321bd3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt | |||
| @@ -26,6 +26,14 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti | |||
| 26 | for (i in values.indices) { | 26 | for (i in values.indices) { |
| 27 | if (values[i] == item.selectedValue) { | 27 | if (values[i] == item.selectedValue) { |
| 28 | binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] | 28 | binding.textSettingDescription.text = resMgr.getStringArray(item.choicesId)[i] |
| 29 | return | ||
| 30 | } | ||
| 31 | } | ||
| 32 | } else if (item is StringSingleChoiceSetting) { | ||
| 33 | for (i in item.values!!.indices) { | ||
| 34 | if (item.values[i] == item.selectedValue) { | ||
| 35 | binding.textSettingDescription.text = item.choices[i] | ||
| 36 | return | ||
| 29 | } | 37 | } |
| 30 | } | 38 | } |
| 31 | } else { | 39 | } else { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt index b163bd6ca..54f531795 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt | |||
| @@ -6,8 +6,8 @@ package org.yuzu.yuzu_emu.features.settings.ui.viewholder | |||
| 6 | import android.view.View | 6 | import android.view.View |
| 7 | import android.widget.CompoundButton | 7 | import android.widget.CompoundButton |
| 8 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding | 8 | import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding |
| 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting | ||
| 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem | 9 | import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem |
| 10 | import org.yuzu.yuzu_emu.features.settings.model.view.SwitchSetting | ||
| 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter | 11 | import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter |
| 12 | 12 | ||
| 13 | class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : | 13 | class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter: SettingsAdapter) : |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt index e29bca11d..70a52df5d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.features.settings.utils | 4 | package org.yuzu.yuzu_emu.features.settings.utils |
| 5 | 5 | ||
| 6 | import java.io.* | ||
| 7 | import java.util.* | ||
| 6 | import org.ini4j.Wini | 8 | import org.ini4j.Wini |
| 7 | import org.yuzu.yuzu_emu.NativeLibrary | 9 | import org.yuzu.yuzu_emu.NativeLibrary |
| 8 | import org.yuzu.yuzu_emu.R | 10 | import org.yuzu.yuzu_emu.R |
| @@ -13,8 +15,6 @@ import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivityView | |||
| 13 | import org.yuzu.yuzu_emu.utils.BiMap | 15 | import org.yuzu.yuzu_emu.utils.BiMap |
| 14 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 16 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 15 | import org.yuzu.yuzu_emu.utils.Log | 17 | import org.yuzu.yuzu_emu.utils.Log |
| 16 | import java.io.* | ||
| 17 | import java.util.* | ||
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | * Contains static methods for interacting with .ini files in which settings are stored. | 20 | * Contains static methods for interacting with .ini files in which settings are stored. |
| @@ -137,9 +137,12 @@ object SettingsFile { | |||
| 137 | for (settingKey in sortedKeySet) { | 137 | for (settingKey in sortedKeySet) { |
| 138 | val setting = settings[settingKey] | 138 | val setting = settings[settingKey] |
| 139 | NativeLibrary.setUserSetting( | 139 | NativeLibrary.setUserSetting( |
| 140 | gameId, mapSectionNameFromIni( | 140 | gameId, |
| 141 | mapSectionNameFromIni( | ||
| 141 | section.name | 142 | section.name |
| 142 | ), setting!!.key, setting.valueAsString | 143 | ), |
| 144 | setting!!.key, | ||
| 145 | setting.valueAsString | ||
| 143 | ) | 146 | ) |
| 144 | } | 147 | } |
| 145 | } | 148 | } |
| @@ -148,13 +151,17 @@ object SettingsFile { | |||
| 148 | private fun mapSectionNameFromIni(generalSectionName: String): String? { | 151 | private fun mapSectionNameFromIni(generalSectionName: String): String? { |
| 149 | return if (sectionsMap.getForward(generalSectionName) != null) { | 152 | return if (sectionsMap.getForward(generalSectionName) != null) { |
| 150 | sectionsMap.getForward(generalSectionName) | 153 | sectionsMap.getForward(generalSectionName) |
| 151 | } else generalSectionName | 154 | } else { |
| 155 | generalSectionName | ||
| 156 | } | ||
| 152 | } | 157 | } |
| 153 | 158 | ||
| 154 | private fun mapSectionNameToIni(generalSectionName: String): String { | 159 | private fun mapSectionNameToIni(generalSectionName: String): String { |
| 155 | return if (sectionsMap.getBackward(generalSectionName) != null) { | 160 | return if (sectionsMap.getBackward(generalSectionName) != null) { |
| 156 | sectionsMap.getBackward(generalSectionName).toString() | 161 | sectionsMap.getBackward(generalSectionName).toString() |
| 157 | } else generalSectionName | 162 | } else { |
| 163 | generalSectionName | ||
| 164 | } | ||
| 158 | } | 165 | } |
| 159 | 166 | ||
| 160 | fun getSettingsFile(fileName: String): File { | 167 | fun getSettingsFile(fileName: String): File { |
| @@ -237,5 +244,21 @@ object SettingsFile { | |||
| 237 | val setting = settings[key] | 244 | val setting = settings[key] |
| 238 | parser.put(header, setting!!.key, setting.valueAsString) | 245 | parser.put(header, setting!!.key, setting.valueAsString) |
| 239 | } | 246 | } |
| 247 | |||
| 248 | BooleanSetting.values().forEach { | ||
| 249 | if (!keySet.contains(it.key)) { | ||
| 250 | parser.put(header, it.key, it.valueAsString) | ||
| 251 | } | ||
| 252 | } | ||
| 253 | IntSetting.values().forEach { | ||
| 254 | if (!keySet.contains(it.key)) { | ||
| 255 | parser.put(header, it.key, it.valueAsString) | ||
| 256 | } | ||
| 257 | } | ||
| 258 | StringSetting.values().forEach { | ||
| 259 | if (!keySet.contains(it.key)) { | ||
| 260 | parser.put(header, it.key, it.valueAsString) | ||
| 261 | } | ||
| 262 | } | ||
| 240 | } | 263 | } |
| 241 | } | 264 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt index c92e2755c..2ff827c6b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AboutFragment.kt | |||
| @@ -66,7 +66,11 @@ class AboutFragment : Fragment() { | |||
| 66 | true | 66 | true |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | binding.buttonContributors.setOnClickListener { openLink(getString(R.string.contributors_link)) } | 69 | binding.buttonContributors.setOnClickListener { |
| 70 | openLink( | ||
| 71 | getString(R.string.contributors_link) | ||
| 72 | ) | ||
| 73 | } | ||
| 70 | binding.buttonLicenses.setOnClickListener { | 74 | binding.buttonLicenses.setOnClickListener { |
| 71 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) | 75 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |
| 72 | binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) | 76 | binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) |
| @@ -101,7 +105,9 @@ class AboutFragment : Fragment() { | |||
| 101 | } | 105 | } |
| 102 | 106 | ||
| 103 | private fun setInsets() = | 107 | private fun setInsets() = |
| 104 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 108 | ViewCompat.setOnApplyWindowInsetsListener( |
| 109 | binding.root | ||
| 110 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 105 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 111 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 106 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 112 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 107 | 113 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt index d8bbc1ce4..dbc16da4a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EarlyAccessFragment.kt | |||
| @@ -49,7 +49,11 @@ class EarlyAccessFragment : Fragment() { | |||
| 49 | parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack() | 49 | parentFragmentManager.primaryNavigationFragment?.findNavController()?.popBackStack() |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | binding.getEarlyAccessButton.setOnClickListener { openLink(getString(R.string.play_store_link)) } | 52 | binding.getEarlyAccessButton.setOnClickListener { |
| 53 | openLink( | ||
| 54 | getString(R.string.play_store_link) | ||
| 55 | ) | ||
| 56 | } | ||
| 53 | 57 | ||
| 54 | setInsets() | 58 | setInsets() |
| 55 | } | 59 | } |
| @@ -60,7 +64,9 @@ class EarlyAccessFragment : Fragment() { | |||
| 60 | } | 64 | } |
| 61 | 65 | ||
| 62 | private fun setInsets() = | 66 | private fun setInsets() = |
| 63 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 67 | ViewCompat.setOnApplyWindowInsetsListener( |
| 68 | binding.root | ||
| 69 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 64 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 70 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 65 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 71 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 66 | 72 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 4b2305892..4643418c1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt | |||
| @@ -83,21 +83,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | onReturnFromSettings = context.activityResultRegistry.register( | 85 | onReturnFromSettings = context.activityResultRegistry.register( |
| 86 | "SettingsResult", ActivityResultContracts.StartActivityForResult() | 86 | "SettingsResult", |
| 87 | ) { | 87 | ActivityResultContracts.StartActivityForResult() |
| 88 | binding.surfaceEmulation.setAspectRatio( | 88 | ) { updateScreenLayout() } |
| 89 | when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 90 | 0 -> Rational(16, 9) | ||
| 91 | 1 -> Rational(4, 3) | ||
| 92 | 2 -> Rational(21, 9) | ||
| 93 | 3 -> Rational(16, 10) | ||
| 94 | 4 -> null // Stretch | ||
| 95 | else -> Rational(16, 9) | ||
| 96 | } | ||
| 97 | ) | ||
| 98 | emulationActivity?.buildPictureInPictureParams() | ||
| 99 | updateScreenLayout() | ||
| 100 | } | ||
| 101 | } else { | 89 | } else { |
| 102 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") | 90 | throw IllegalStateException("EmulationFragment must have EmulationActivity parent") |
| 103 | } | 91 | } |
| @@ -191,9 +179,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 191 | requireActivity(), | 179 | requireActivity(), |
| 192 | object : OnBackPressedCallback(true) { | 180 | object : OnBackPressedCallback(true) { |
| 193 | override fun handleOnBackPressed() { | 181 | override fun handleOnBackPressed() { |
| 194 | if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open() | 182 | if (binding.drawerLayout.isOpen) { |
| 183 | binding.drawerLayout.close() | ||
| 184 | } else { | ||
| 185 | binding.drawerLayout.open() | ||
| 186 | } | ||
| 195 | } | 187 | } |
| 196 | }) | 188 | } |
| 189 | ) | ||
| 197 | 190 | ||
| 198 | viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { | 191 | viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { |
| 199 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { | 192 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { |
| @@ -236,17 +229,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 236 | DirectoryInitialization.start(requireContext()) | 229 | DirectoryInitialization.start(requireContext()) |
| 237 | } | 230 | } |
| 238 | 231 | ||
| 239 | binding.surfaceEmulation.setAspectRatio( | ||
| 240 | when (IntSetting.RENDERER_ASPECT_RATIO.int) { | ||
| 241 | 0 -> Rational(16, 9) | ||
| 242 | 1 -> Rational(4, 3) | ||
| 243 | 2 -> Rational(21, 9) | ||
| 244 | 3 -> Rational(16, 10) | ||
| 245 | 4 -> null // Stretch | ||
| 246 | else -> Rational(16, 9) | ||
| 247 | } | ||
| 248 | ) | ||
| 249 | |||
| 250 | updateScreenLayout() | 232 | updateScreenLayout() |
| 251 | 233 | ||
| 252 | emulationState.run(emulationActivity!!.isActivityRecreated) | 234 | emulationState.run(emulationActivity!!.isActivityRecreated) |
| @@ -309,44 +291,66 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 309 | } | 291 | } |
| 310 | 292 | ||
| 311 | @SuppressLint("SourceLockedOrientationActivity") | 293 | @SuppressLint("SourceLockedOrientationActivity") |
| 312 | private fun updateScreenLayout() { | 294 | private fun updateOrientation() { |
| 313 | emulationActivity?.let { | 295 | emulationActivity?.let { |
| 314 | it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { | 296 | it.requestedOrientation = when (IntSetting.RENDERER_SCREEN_LAYOUT.int) { |
| 315 | Settings.LayoutOption_MobileLandscape -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | 297 | Settings.LayoutOption_MobileLandscape -> |
| 316 | Settings.LayoutOption_MobilePortrait -> ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | 298 | ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE |
| 299 | Settings.LayoutOption_MobilePortrait -> | ||
| 300 | ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT | ||
| 317 | Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | 301 | Settings.LayoutOption_Unspecified -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED |
| 318 | else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE | 302 | else -> ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE |
| 319 | } | 303 | } |
| 320 | } | 304 | } |
| 321 | onConfigurationChanged(resources.configuration) | ||
| 322 | } | 305 | } |
| 323 | 306 | ||
| 324 | private fun updateFoldableLayout(emulationActivity: EmulationActivity, newLayoutInfo: WindowLayoutInfo) { | 307 | private fun updateScreenLayout() { |
| 325 | val isFolding = (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { | 308 | binding.surfaceEmulation.setAspectRatio( |
| 326 | if (it.isSeparating) { | 309 | when (IntSetting.RENDERER_ASPECT_RATIO.int) { |
| 327 | emulationActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | 310 | 0 -> Rational(16, 9) |
| 328 | if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { | 311 | 1 -> Rational(4, 3) |
| 329 | // Restrict emulation and overlays to the top of the screen | 312 | 2 -> Rational(21, 9) |
| 330 | binding.emulationContainer.layoutParams.height = it.bounds.top | 313 | 3 -> Rational(16, 10) |
| 331 | binding.overlayContainer.layoutParams.height = it.bounds.top | 314 | 4 -> null // Stretch |
| 332 | // Restrict input and menu drawer to the bottom of the screen | 315 | else -> Rational(16, 9) |
| 333 | binding.inputContainer.layoutParams.height = it.bounds.bottom | ||
| 334 | binding.inGameMenu.layoutParams.height = it.bounds.bottom | ||
| 335 | |||
| 336 | isInFoldableLayout = true | ||
| 337 | binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE | ||
| 338 | refreshInputOverlay() | ||
| 339 | } | ||
| 340 | } | 316 | } |
| 341 | it.isSeparating | 317 | ) |
| 342 | } ?: false | 318 | emulationActivity?.buildPictureInPictureParams() |
| 319 | updateOrientation() | ||
| 320 | } | ||
| 321 | |||
| 322 | private fun updateFoldableLayout( | ||
| 323 | emulationActivity: EmulationActivity, | ||
| 324 | newLayoutInfo: WindowLayoutInfo | ||
| 325 | ) { | ||
| 326 | val isFolding = | ||
| 327 | (newLayoutInfo.displayFeatures.find { it is FoldingFeature } as? FoldingFeature)?.let { | ||
| 328 | if (it.isSeparating) { | ||
| 329 | emulationActivity.requestedOrientation = | ||
| 330 | ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED | ||
| 331 | if (it.orientation == FoldingFeature.Orientation.HORIZONTAL) { | ||
| 332 | // Restrict emulation and overlays to the top of the screen | ||
| 333 | binding.emulationContainer.layoutParams.height = it.bounds.top | ||
| 334 | binding.overlayContainer.layoutParams.height = it.bounds.top | ||
| 335 | // Restrict input and menu drawer to the bottom of the screen | ||
| 336 | binding.inputContainer.layoutParams.height = it.bounds.bottom | ||
| 337 | binding.inGameMenu.layoutParams.height = it.bounds.bottom | ||
| 338 | |||
| 339 | isInFoldableLayout = true | ||
| 340 | binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE | ||
| 341 | refreshInputOverlay() | ||
| 342 | } | ||
| 343 | } | ||
| 344 | it.isSeparating | ||
| 345 | } ?: false | ||
| 343 | if (!isFolding) { | 346 | if (!isFolding) { |
| 344 | binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 347 | binding.emulationContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 345 | binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 348 | binding.inputContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 346 | binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 349 | binding.overlayContainer.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 347 | binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT | 350 | binding.inGameMenu.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT |
| 348 | isInFoldableLayout = false | 351 | isInFoldableLayout = false |
| 349 | updateScreenLayout() | 352 | updateOrientation() |
| 353 | onConfigurationChanged(resources.configuration) | ||
| 350 | } | 354 | } |
| 351 | binding.emulationContainer.requestLayout() | 355 | binding.emulationContainer.requestLayout() |
| 352 | binding.inputContainer.requestLayout() | 356 | binding.inputContainer.requestLayout() |
| @@ -516,18 +520,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 516 | inputScaleSlider.apply { | 520 | inputScaleSlider.apply { |
| 517 | valueTo = 150F | 521 | valueTo = 150F |
| 518 | value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() | 522 | value = preferences.getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() |
| 519 | addOnChangeListener(Slider.OnChangeListener { _, value, _ -> | 523 | addOnChangeListener( |
| 520 | inputScaleValue.text = "${value.toInt()}%" | 524 | Slider.OnChangeListener { _, value, _ -> |
| 521 | setControlScale(value.toInt()) | 525 | inputScaleValue.text = "${value.toInt()}%" |
| 522 | }) | 526 | setControlScale(value.toInt()) |
| 527 | } | ||
| 528 | ) | ||
| 523 | } | 529 | } |
| 524 | inputOpacitySlider.apply { | 530 | inputOpacitySlider.apply { |
| 525 | valueTo = 100F | 531 | valueTo = 100F |
| 526 | value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() | 532 | value = preferences.getInt(Settings.PREF_CONTROL_OPACITY, 100).toFloat() |
| 527 | addOnChangeListener(Slider.OnChangeListener { _, value, _ -> | 533 | addOnChangeListener( |
| 528 | inputOpacityValue.text = "${value.toInt()}%" | 534 | Slider.OnChangeListener { _, value, _ -> |
| 529 | setControlOpacity(value.toInt()) | 535 | inputOpacityValue.text = "${value.toInt()}%" |
| 530 | }) | 536 | setControlOpacity(value.toInt()) |
| 537 | } | ||
| 538 | ) | ||
| 531 | } | 539 | } |
| 532 | inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" | 540 | inputScaleValue.text = "${inputScaleSlider.value.toInt()}%" |
| 533 | inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" | 541 | inputOpacityValue.text = "${inputOpacitySlider.value.toInt()}%" |
| @@ -559,7 +567,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 559 | } | 567 | } |
| 560 | 568 | ||
| 561 | private fun setInsets() { | 569 | private fun setInsets() { |
| 562 | ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat -> | 570 | ViewCompat.setOnApplyWindowInsetsListener( |
| 571 | binding.inGameMenu | ||
| 572 | ) { v: View, windowInsets: WindowInsetsCompat -> | ||
| 563 | val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 573 | val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 564 | var left = 0 | 574 | var left = 0 |
| 565 | var right = 0 | 575 | var right = 0 |
| @@ -679,8 +689,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { | |||
| 679 | state = State.PAUSED | 689 | state = State.PAUSED |
| 680 | } | 690 | } |
| 681 | 691 | ||
| 682 | State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.") | 692 | State.PAUSED -> Log.warning( |
| 683 | else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.") | 693 | "[EmulationFragment] Surface cleared while emulation paused." |
| 694 | ) | ||
| 695 | else -> Log.warning( | ||
| 696 | "[EmulationFragment] Surface cleared while emulation stopped." | ||
| 697 | ) | ||
| 684 | } | 698 | } |
| 685 | } | 699 | } |
| 686 | } | 700 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index 536163eb6..6f8adbba5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt | |||
| @@ -103,7 +103,9 @@ class HomeSettingsFragment : Fragment() { | |||
| 103 | R.string.select_games_folder, | 103 | R.string.select_games_folder, |
| 104 | R.string.select_games_folder_description, | 104 | R.string.select_games_folder_description, |
| 105 | R.drawable.ic_add | 105 | R.drawable.ic_add |
| 106 | ) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, | 106 | ) { |
| 107 | mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) | ||
| 108 | }, | ||
| 107 | HomeSetting( | 109 | HomeSetting( |
| 108 | R.string.manage_save_data, | 110 | R.string.manage_save_data, |
| 109 | R.string.import_export_saves_description, | 111 | R.string.import_export_saves_description, |
| @@ -225,7 +227,11 @@ class HomeSettingsFragment : Fragment() { | |||
| 225 | val intent = Intent(action) | 227 | val intent = Intent(action) |
| 226 | intent.addCategory(Intent.CATEGORY_DEFAULT) | 228 | intent.addCategory(Intent.CATEGORY_DEFAULT) |
| 227 | intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID) | 229 | intent.data = DocumentsContract.buildRootUri(authority, DocumentProvider.ROOT_ID) |
| 228 | intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) | 230 | intent.addFlags( |
| 231 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or | ||
| 232 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or | ||
| 233 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | ||
| 234 | ) | ||
| 229 | return intent | 235 | return intent |
| 230 | } | 236 | } |
| 231 | 237 | ||
| @@ -307,7 +313,9 @@ class HomeSettingsFragment : Fragment() { | |||
| 307 | } | 313 | } |
| 308 | 314 | ||
| 309 | private fun setInsets() = | 315 | private fun setInsets() = |
| 310 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 316 | ViewCompat.setOnApplyWindowInsetsListener( |
| 317 | binding.root | ||
| 318 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 311 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 319 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 312 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 320 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 313 | val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation) | 321 | val spacingNavigation = resources.getDimensionPixelSize(R.dimen.spacing_navigation) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt index 36e63bb9e..e1495ee8c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/ImportExportSavesFragment.kt | |||
| @@ -15,6 +15,14 @@ import androidx.appcompat.app.AppCompatActivity | |||
| 15 | import androidx.documentfile.provider.DocumentFile | 15 | import androidx.documentfile.provider.DocumentFile |
| 16 | import androidx.fragment.app.DialogFragment | 16 | import androidx.fragment.app.DialogFragment |
| 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 17 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 18 | import java.io.BufferedOutputStream | ||
| 19 | import java.io.File | ||
| 20 | import java.io.FileOutputStream | ||
| 21 | import java.io.FilenameFilter | ||
| 22 | import java.time.LocalDateTime | ||
| 23 | import java.time.format.DateTimeFormatter | ||
| 24 | import java.util.zip.ZipEntry | ||
| 25 | import java.util.zip.ZipOutputStream | ||
| 18 | import kotlinx.coroutines.CoroutineScope | 26 | import kotlinx.coroutines.CoroutineScope |
| 19 | import kotlinx.coroutines.Dispatchers | 27 | import kotlinx.coroutines.Dispatchers |
| 20 | import kotlinx.coroutines.launch | 28 | import kotlinx.coroutines.launch |
| @@ -24,14 +32,6 @@ import org.yuzu.yuzu_emu.YuzuApplication | |||
| 24 | import org.yuzu.yuzu_emu.features.DocumentProvider | 32 | import org.yuzu.yuzu_emu.features.DocumentProvider |
| 25 | import org.yuzu.yuzu_emu.getPublicFilesDir | 33 | import org.yuzu.yuzu_emu.getPublicFilesDir |
| 26 | import org.yuzu.yuzu_emu.utils.FileUtil | 34 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 27 | import java.io.BufferedOutputStream | ||
| 28 | import java.io.File | ||
| 29 | import java.io.FileOutputStream | ||
| 30 | import java.io.FilenameFilter | ||
| 31 | import java.time.LocalDateTime | ||
| 32 | import java.time.format.DateTimeFormatter | ||
| 33 | import java.util.zip.ZipEntry | ||
| 34 | import java.util.zip.ZipOutputStream | ||
| 35 | 35 | ||
| 36 | class ImportExportSavesFragment : DialogFragment() { | 36 | class ImportExportSavesFragment : DialogFragment() { |
| 37 | private val context = YuzuApplication.appContext | 37 | private val context = YuzuApplication.appContext |
| @@ -98,7 +98,7 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 98 | val outputZipFile = File( | 98 | val outputZipFile = File( |
| 99 | tempFolder, | 99 | tempFolder, |
| 100 | "yuzu saves - ${ | 100 | "yuzu saves - ${ |
| 101 | LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) | 101 | LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) |
| 102 | }.zip" | 102 | }.zip" |
| 103 | ) | 103 | ) |
| 104 | outputZipFile.createNewFile() | 104 | outputZipFile.createNewFile() |
| @@ -106,12 +106,14 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 106 | saveFolder.walkTopDown().forEach { file -> | 106 | saveFolder.walkTopDown().forEach { file -> |
| 107 | val zipFileName = | 107 | val zipFileName = |
| 108 | file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/") | 108 | file.absolutePath.removePrefix(savesFolderRoot).removePrefix("/") |
| 109 | if (zipFileName == "") | 109 | if (zipFileName == "") { |
| 110 | return@forEach | 110 | return@forEach |
| 111 | } | ||
| 111 | val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") | 112 | val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}") |
| 112 | zos.putNextEntry(entry) | 113 | zos.putNextEntry(entry) |
| 113 | if (file.isFile) | 114 | if (file.isFile) { |
| 114 | file.inputStream().use { fis -> fis.copyTo(zos) } | 115 | file.inputStream().use { fis -> fis.copyTo(zos) } |
| 116 | } | ||
| 115 | } | 117 | } |
| 116 | } | 118 | } |
| 117 | lastZipCreated = outputZipFile | 119 | lastZipCreated = outputZipFile |
| @@ -137,7 +139,8 @@ class ImportExportSavesFragment : DialogFragment() { | |||
| 137 | 139 | ||
| 138 | withContext(Dispatchers.Main) { | 140 | withContext(Dispatchers.Main) { |
| 139 | val file = DocumentFile.fromSingleUri( | 141 | val file = DocumentFile.fromSingleUri( |
| 140 | context, DocumentsContract.buildDocumentUri( | 142 | context, |
| 143 | DocumentsContract.buildDocumentUri( | ||
| 141 | DocumentProvider.AUTHORITY, | 144 | DocumentProvider.AUTHORITY, |
| 142 | "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" | 145 | "${DocumentProvider.ROOT_ID}/temp/${lastZipFile.name}" |
| 143 | ) | 146 | ) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt index c7880d8cc..739b26f99 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt | |||
| @@ -14,7 +14,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder | |||
| 14 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding | 14 | import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
| 15 | import org.yuzu.yuzu_emu.model.TaskViewModel | 15 | import org.yuzu.yuzu_emu.model.TaskViewModel |
| 16 | 16 | ||
| 17 | |||
| 18 | class IndeterminateProgressDialogFragment : DialogFragment() { | 17 | class IndeterminateProgressDialogFragment : DialogFragment() { |
| 19 | private val taskViewModel: TaskViewModel by activityViewModels() | 18 | private val taskViewModel: TaskViewModel by activityViewModels() |
| 20 | 19 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt index 59141e823..b6e9129f7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt | |||
| @@ -113,7 +113,9 @@ class LicensesFragment : Fragment() { | |||
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | private fun setInsets() = | 115 | private fun setInsets() = |
| 116 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 116 | ViewCompat.setOnApplyWindowInsetsListener( |
| 117 | binding.root | ||
| 118 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 117 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 119 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 118 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 120 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 119 | 121 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt index adbe3696b..dd6c895fd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt | |||
| @@ -20,6 +20,7 @@ import androidx.fragment.app.activityViewModels | |||
| 20 | import androidx.preference.PreferenceManager | 20 | import androidx.preference.PreferenceManager |
| 21 | import info.debatty.java.stringsimilarity.Jaccard | 21 | import info.debatty.java.stringsimilarity.Jaccard |
| 22 | import info.debatty.java.stringsimilarity.JaroWinkler | 22 | import info.debatty.java.stringsimilarity.JaroWinkler |
| 23 | import java.util.Locale | ||
| 23 | import org.yuzu.yuzu_emu.R | 24 | import org.yuzu.yuzu_emu.R |
| 24 | import org.yuzu.yuzu_emu.YuzuApplication | 25 | import org.yuzu.yuzu_emu.YuzuApplication |
| 25 | import org.yuzu.yuzu_emu.adapters.GameAdapter | 26 | import org.yuzu.yuzu_emu.adapters.GameAdapter |
| @@ -29,8 +30,6 @@ import org.yuzu.yuzu_emu.model.Game | |||
| 29 | import org.yuzu.yuzu_emu.model.GamesViewModel | 30 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 30 | import org.yuzu.yuzu_emu.model.HomeViewModel | 31 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 31 | import org.yuzu.yuzu_emu.utils.FileUtil | 32 | import org.yuzu.yuzu_emu.utils.FileUtil |
| 32 | import org.yuzu.yuzu_emu.utils.Log | ||
| 33 | import java.util.Locale | ||
| 34 | 33 | ||
| 35 | class SearchFragment : Fragment() { | 34 | class SearchFragment : Fragment() { |
| 36 | private var _binding: FragmentSearchBinding? = null | 35 | private var _binding: FragmentSearchBinding? = null |
| @@ -130,15 +129,15 @@ class SearchFragment : Fragment() { | |||
| 130 | R.id.chip_homebrew -> baseList.filter { it.isHomebrew } | 129 | R.id.chip_homebrew -> baseList.filter { it.isHomebrew } |
| 131 | 130 | ||
| 132 | R.id.chip_retail -> baseList.filter { | 131 | R.id.chip_retail -> baseList.filter { |
| 133 | FileUtil.hasExtension(it.path, "xci") | 132 | FileUtil.hasExtension(it.path, "xci") || |
| 134 | || FileUtil.hasExtension(it.path, "nsp") | 133 | FileUtil.hasExtension(it.path, "nsp") |
| 135 | } | 134 | } |
| 136 | 135 | ||
| 137 | else -> baseList | 136 | else -> baseList |
| 138 | } | 137 | } |
| 139 | 138 | ||
| 140 | if (binding.searchText.text.toString().isEmpty() | 139 | if (binding.searchText.text.toString().isEmpty() && |
| 141 | && binding.chipGroup.checkedChipId != View.NO_ID | 140 | binding.chipGroup.checkedChipId != View.NO_ID |
| 142 | ) { | 141 | ) { |
| 143 | gamesViewModel.setSearchedGames(filteredList) | 142 | gamesViewModel.setSearchedGames(filteredList) |
| 144 | return | 143 | return |
| @@ -173,14 +172,16 @@ class SearchFragment : Fragment() { | |||
| 173 | private fun focusSearch() { | 172 | private fun focusSearch() { |
| 174 | if (_binding != null) { | 173 | if (_binding != null) { |
| 175 | binding.searchText.requestFocus() | 174 | binding.searchText.requestFocus() |
| 176 | val imm = | 175 | val imm = requireActivity() |
| 177 | requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? | 176 | .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? |
| 178 | imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) | 177 | imm?.showSoftInput(binding.searchText, InputMethodManager.SHOW_IMPLICIT) |
| 179 | } | 178 | } |
| 180 | } | 179 | } |
| 181 | 180 | ||
| 182 | private fun setInsets() = | 181 | private fun setInsets() = |
| 183 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 182 | ViewCompat.setOnApplyWindowInsetsListener( |
| 183 | binding.root | ||
| 184 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 184 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 185 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 185 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 186 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 186 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) | 187 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_med) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt index 258773380..6c4ddaf6b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt | |||
| @@ -25,6 +25,7 @@ import androidx.navigation.findNavController | |||
| 25 | import androidx.preference.PreferenceManager | 25 | import androidx.preference.PreferenceManager |
| 26 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback | 26 | import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback |
| 27 | import com.google.android.material.transition.MaterialFadeThrough | 27 | import com.google.android.material.transition.MaterialFadeThrough |
| 28 | import java.io.File | ||
| 28 | import org.yuzu.yuzu_emu.R | 29 | import org.yuzu.yuzu_emu.R |
| 29 | import org.yuzu.yuzu_emu.YuzuApplication | 30 | import org.yuzu.yuzu_emu.YuzuApplication |
| 30 | import org.yuzu.yuzu_emu.adapters.SetupAdapter | 31 | import org.yuzu.yuzu_emu.adapters.SetupAdapter |
| @@ -35,7 +36,6 @@ import org.yuzu.yuzu_emu.model.SetupPage | |||
| 35 | import org.yuzu.yuzu_emu.ui.main.MainActivity | 36 | import org.yuzu.yuzu_emu.ui.main.MainActivity |
| 36 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization | 37 | import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
| 37 | import org.yuzu.yuzu_emu.utils.GameHelper | 38 | import org.yuzu.yuzu_emu.utils.GameHelper |
| 38 | import java.io.File | ||
| 39 | 39 | ||
| 40 | class SetupFragment : Fragment() { | 40 | class SetupFragment : Fragment() { |
| 41 | private var _binding: FragmentSetupBinding? = null | 41 | private var _binding: FragmentSetupBinding? = null |
| @@ -82,7 +82,8 @@ class SetupFragment : Fragment() { | |||
| 82 | requireActivity().finish() | 82 | requireActivity().finish() |
| 83 | } | 83 | } |
| 84 | } | 84 | } |
| 85 | }) | 85 | } |
| 86 | ) | ||
| 86 | 87 | ||
| 87 | requireActivity().window.navigationBarColor = | 88 | requireActivity().window.navigationBarColor = |
| 88 | ContextCompat.getColor(requireContext(), android.R.color.transparent) | 89 | ContextCompat.getColor(requireContext(), android.R.color.transparent) |
| @@ -148,14 +149,20 @@ class SetupFragment : Fragment() { | |||
| 148 | R.drawable.ic_add, | 149 | R.drawable.ic_add, |
| 149 | true, | 150 | true, |
| 150 | R.string.add_games, | 151 | R.string.add_games, |
| 151 | { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) }, | 152 | { |
| 153 | mainActivity.getGamesDirectory.launch( | ||
| 154 | Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data | ||
| 155 | ) | ||
| 156 | }, | ||
| 152 | true, | 157 | true, |
| 153 | R.string.add_games_warning, | 158 | R.string.add_games_warning, |
| 154 | R.string.add_games_warning_description, | 159 | R.string.add_games_warning_description, |
| 155 | R.string.add_games_warning_help, | 160 | R.string.add_games_warning_help, |
| 156 | { | 161 | { |
| 157 | val preferences = | 162 | val preferences = |
| 158 | PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) | 163 | PreferenceManager.getDefaultSharedPreferences( |
| 164 | YuzuApplication.appContext | ||
| 165 | ) | ||
| 159 | preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() | 166 | preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() |
| 160 | } | 167 | } |
| 161 | ) | 168 | ) |
| @@ -260,7 +267,9 @@ class SetupFragment : Fragment() { | |||
| 260 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) | 267 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) |
| 261 | private val permissionLauncher = | 268 | private val permissionLauncher = |
| 262 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { | 269 | registerForActivityResult(ActivityResultContracts.RequestPermission()) { |
| 263 | if (!it && !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { | 270 | if (!it && |
| 271 | !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) | ||
| 272 | ) { | ||
| 264 | PermissionDeniedDialogFragment().show( | 273 | PermissionDeniedDialogFragment().show( |
| 265 | childFragmentManager, | 274 | childFragmentManager, |
| 266 | PermissionDeniedDialogFragment.TAG | 275 | PermissionDeniedDialogFragment.TAG |
| @@ -315,7 +324,9 @@ class SetupFragment : Fragment() { | |||
| 315 | } | 324 | } |
| 316 | 325 | ||
| 317 | private fun setInsets() = | 326 | private fun setInsets() = |
| 318 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 327 | ViewCompat.setOnApplyWindowInsetsListener( |
| 328 | binding.root | ||
| 329 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 319 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 330 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 320 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 331 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 321 | view.setPadding( | 332 | view.setPadding( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt index be5e4c86c..bdd6ea628 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/layout/AutofitGridLayoutManager.kt | |||
| @@ -44,7 +44,9 @@ class AutofitGridLayoutManager( | |||
| 44 | override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { | 44 | override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) { |
| 45 | val width = width | 45 | val width = width |
| 46 | val height = height | 46 | val height = height |
| 47 | if (columnWidth > 0 && width > 0 && height > 0 && (isColumnWidthChanged || lastWidth != width || lastHeight != height)) { | 47 | if (columnWidth > 0 && width > 0 && height > 0 && |
| 48 | (isColumnWidthChanged || lastWidth != width || lastHeight != height) | ||
| 49 | ) { | ||
| 48 | val totalSpace: Int = if (orientation == VERTICAL) { | 50 | val totalSpace: Int = if (orientation == VERTICAL) { |
| 49 | width - paddingRight - paddingLeft | 51 | width - paddingRight - paddingLeft |
| 50 | } else { | 52 | } else { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index 35d8000c5..6a048e39f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt | |||
| @@ -4,9 +4,9 @@ | |||
| 4 | package org.yuzu.yuzu_emu.model | 4 | package org.yuzu.yuzu_emu.model |
| 5 | 5 | ||
| 6 | import android.os.Parcelable | 6 | import android.os.Parcelable |
| 7 | import java.util.HashSet | ||
| 7 | import kotlinx.parcelize.Parcelize | 8 | import kotlinx.parcelize.Parcelize |
| 8 | import kotlinx.serialization.Serializable | 9 | import kotlinx.serialization.Serializable |
| 9 | import java.util.HashSet | ||
| 10 | 10 | ||
| 11 | @Parcelize | 11 | @Parcelize |
| 12 | @Serializable | 12 | @Serializable |
| @@ -23,8 +23,9 @@ class Game( | |||
| 23 | val keyLastPlayedTime get() = "${gameId}_LastPlayed" | 23 | val keyLastPlayedTime get() = "${gameId}_LastPlayed" |
| 24 | 24 | ||
| 25 | override fun equals(other: Any?): Boolean { | 25 | override fun equals(other: Any?): Boolean { |
| 26 | if (other !is Game) | 26 | if (other !is Game) { |
| 27 | return false | 27 | return false |
| 28 | } | ||
| 28 | 29 | ||
| 29 | return hashCode() == other.hashCode() | 30 | return hashCode() == other.hashCode() |
| 30 | } | 31 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt index d9b301210..1fe42f922 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt | |||
| @@ -10,6 +10,7 @@ import androidx.lifecycle.MutableLiveData | |||
| 10 | import androidx.lifecycle.ViewModel | 10 | import androidx.lifecycle.ViewModel |
| 11 | import androidx.lifecycle.viewModelScope | 11 | import androidx.lifecycle.viewModelScope |
| 12 | import androidx.preference.PreferenceManager | 12 | import androidx.preference.PreferenceManager |
| 13 | import java.util.Locale | ||
| 13 | import kotlinx.coroutines.Dispatchers | 14 | import kotlinx.coroutines.Dispatchers |
| 14 | import kotlinx.coroutines.launch | 15 | import kotlinx.coroutines.launch |
| 15 | import kotlinx.coroutines.withContext | 16 | import kotlinx.coroutines.withContext |
| @@ -20,7 +21,6 @@ import kotlinx.serialization.json.Json | |||
| 20 | import org.yuzu.yuzu_emu.NativeLibrary | 21 | import org.yuzu.yuzu_emu.NativeLibrary |
| 21 | import org.yuzu.yuzu_emu.YuzuApplication | 22 | import org.yuzu.yuzu_emu.YuzuApplication |
| 22 | import org.yuzu.yuzu_emu.utils.GameHelper | 23 | import org.yuzu.yuzu_emu.utils.GameHelper |
| 23 | import java.util.Locale | ||
| 24 | 24 | ||
| 25 | @OptIn(ExperimentalSerializationApi::class) | 25 | @OptIn(ExperimentalSerializationApi::class) |
| 26 | class GamesViewModel : ViewModel() { | 26 | class GamesViewModel : ViewModel() { |
| @@ -99,8 +99,9 @@ class GamesViewModel : ViewModel() { | |||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | fun reloadGames(directoryChanged: Boolean) { | 101 | fun reloadGames(directoryChanged: Boolean) { |
| 102 | if (isReloading.value == true) | 102 | if (isReloading.value == true) { |
| 103 | return | 103 | return |
| 104 | } | ||
| 104 | _isReloading.postValue(true) | 105 | _isReloading.postValue(true) |
| 105 | 106 | ||
| 106 | viewModelScope.launch { | 107 | viewModelScope.launch { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index d12d08e9f..6251ec783 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt | |||
| @@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.overlay | |||
| 6 | import android.app.Activity | 6 | import android.app.Activity |
| 7 | import android.content.Context | 7 | import android.content.Context |
| 8 | import android.content.SharedPreferences | 8 | import android.content.SharedPreferences |
| 9 | import android.content.res.Configuration | ||
| 10 | import android.graphics.Bitmap | 9 | import android.graphics.Bitmap |
| 11 | import android.graphics.Canvas | 10 | import android.graphics.Canvas |
| 12 | import android.graphics.Point | 11 | import android.graphics.Point |
| @@ -24,6 +23,8 @@ import android.view.WindowInsets | |||
| 24 | import androidx.core.content.ContextCompat | 23 | import androidx.core.content.ContextCompat |
| 25 | import androidx.preference.PreferenceManager | 24 | import androidx.preference.PreferenceManager |
| 26 | import androidx.window.layout.WindowMetricsCalculator | 25 | import androidx.window.layout.WindowMetricsCalculator |
| 26 | import kotlin.math.max | ||
| 27 | import kotlin.math.min | ||
| 27 | import org.yuzu.yuzu_emu.NativeLibrary | 28 | import org.yuzu.yuzu_emu.NativeLibrary |
| 28 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonType | 29 | import org.yuzu.yuzu_emu.NativeLibrary.ButtonType |
| 29 | import org.yuzu.yuzu_emu.NativeLibrary.StickType | 30 | import org.yuzu.yuzu_emu.NativeLibrary.StickType |
| @@ -31,14 +32,13 @@ import org.yuzu.yuzu_emu.R | |||
| 31 | import org.yuzu.yuzu_emu.YuzuApplication | 32 | import org.yuzu.yuzu_emu.YuzuApplication |
| 32 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 33 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 33 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | 34 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings |
| 34 | import kotlin.math.max | ||
| 35 | import kotlin.math.min | ||
| 36 | 35 | ||
| 37 | /** | 36 | /** |
| 38 | * Draws the interactive input overlay on top of the | 37 | * Draws the interactive input overlay on top of the |
| 39 | * [SurfaceView] that is rendering emulation. | 38 | * [SurfaceView] that is rendering emulation. |
| 40 | */ | 39 | */ |
| 41 | class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context, attrs), | 40 | class InputOverlay(context: Context, attrs: AttributeSet?) : |
| 41 | SurfaceView(context, attrs), | ||
| 42 | OnTouchListener { | 42 | OnTouchListener { |
| 43 | private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() | 43 | private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet() |
| 44 | private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() | 44 | private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet() |
| @@ -95,7 +95,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 95 | 95 | ||
| 96 | var shouldUpdateView = false | 96 | var shouldUpdateView = false |
| 97 | val playerIndex = | 97 | val playerIndex = |
| 98 | if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device | 98 | if (NativeLibrary.isHandheldOnly()) { |
| 99 | NativeLibrary.ConsoleDevice | ||
| 100 | } else { | ||
| 101 | NativeLibrary.Player1Device | ||
| 102 | } | ||
| 99 | 103 | ||
| 100 | for (button in overlayButtons) { | 104 | for (button in overlayButtons) { |
| 101 | if (!button.updateStatus(event)) { | 105 | if (!button.updateStatus(event)) { |
| @@ -158,8 +162,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 158 | shouldUpdateView = true | 162 | shouldUpdateView = true |
| 159 | } | 163 | } |
| 160 | 164 | ||
| 161 | if (shouldUpdateView) | 165 | if (shouldUpdateView) { |
| 162 | invalidate() | 166 | invalidate() |
| 167 | } | ||
| 163 | 168 | ||
| 164 | if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { | 169 | if (!preferences.getBoolean(Settings.PREF_TOUCH_ENABLED, true)) { |
| 165 | return true | 170 | return true |
| @@ -243,9 +248,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 243 | // If no button is being moved now, remember the currently touched button to move. | 248 | // If no button is being moved now, remember the currently touched button to move. |
| 244 | if (buttonBeingConfigured == null && | 249 | if (buttonBeingConfigured == null && |
| 245 | button.bounds.contains( | 250 | button.bounds.contains( |
| 246 | fingerPositionX, | 251 | fingerPositionX, |
| 247 | fingerPositionY | 252 | fingerPositionY |
| 248 | ) | 253 | ) |
| 249 | ) { | 254 | ) { |
| 250 | buttonBeingConfigured = button | 255 | buttonBeingConfigured = button |
| 251 | buttonBeingConfigured!!.onConfigureTouch(event) | 256 | buttonBeingConfigured!!.onConfigureTouch(event) |
| @@ -309,9 +314,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 309 | MotionEvent.ACTION_DOWN, | 314 | MotionEvent.ACTION_DOWN, |
| 310 | MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null && | 315 | MotionEvent.ACTION_POINTER_DOWN -> if (joystickBeingConfigured == null && |
| 311 | joystick.bounds.contains( | 316 | joystick.bounds.contains( |
| 312 | fingerPositionX, | 317 | fingerPositionX, |
| 313 | fingerPositionY | 318 | fingerPositionY |
| 314 | ) | 319 | ) |
| 315 | ) { | 320 | ) { |
| 316 | joystickBeingConfigured = joystick | 321 | joystickBeingConfigured = joystick |
| 317 | joystickBeingConfigured!!.onConfigureTouch(event) | 322 | joystickBeingConfigured!!.onConfigureTouch(event) |
| @@ -668,7 +673,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 668 | R.integer.SWITCH_STICK_L_Y_FOLDABLE | 673 | R.integer.SWITCH_STICK_L_Y_FOLDABLE |
| 669 | ) | 674 | ) |
| 670 | 675 | ||
| 671 | private fun getResourceValue(orientation: String, position: Int) : Float { | 676 | private fun getResourceValue(orientation: String, position: Int): Float { |
| 672 | return when (orientation) { | 677 | return when (orientation) { |
| 673 | PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 | 678 | PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 |
| 674 | FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 | 679 | FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 |
| @@ -820,7 +825,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 820 | * @param context Context for getting the vector drawable | 825 | * @param context Context for getting the vector drawable |
| 821 | * @param drawableId The ID of the drawable to scale. | 826 | * @param drawableId The ID of the drawable to scale. |
| 822 | * @param scale The scale factor for the bitmap. | 827 | * @param scale The scale factor for the bitmap. |
| 823 | * @return The scaled [Bitmap] | 828 | * @return The scaled [Bitmap] |
| 824 | */ | 829 | */ |
| 825 | private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap { | 830 | private fun getBitmap(context: Context, drawableId: Int, scale: Float): Bitmap { |
| 826 | val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable | 831 | val vectorDrawable = ContextCompat.getDrawable(context, drawableId) as VectorDrawable |
| @@ -854,7 +859,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 854 | * Gets the safe screen size for drawing the overlay | 859 | * Gets the safe screen size for drawing the overlay |
| 855 | * | 860 | * |
| 856 | * @param context Context for getting the window metrics | 861 | * @param context Context for getting the window metrics |
| 857 | * @return A pair of points, the first being the top left corner of the safe area, | 862 | * @return A pair of points, the first being the top left corner of the safe area, |
| 858 | * the second being the bottom right corner of the safe area | 863 | * the second being the bottom right corner of the safe area |
| 859 | */ | 864 | */ |
| 860 | private fun getSafeScreenSize(context: Context): Pair<Point, Point> { | 865 | private fun getSafeScreenSize(context: Context): Pair<Point, Point> { |
| @@ -872,10 +877,16 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context | |||
| 872 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { | 877 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { |
| 873 | val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout | 878 | val insets = context.windowManager.currentWindowMetrics.windowInsets.displayCutout |
| 874 | if (insets != null) { | 879 | if (insets != null) { |
| 875 | if (insets.boundingRectTop.bottom != 0 && insets.boundingRectTop.bottom > maxY / 2) | 880 | if (insets.boundingRectTop.bottom != 0 && |
| 881 | insets.boundingRectTop.bottom > maxY / 2 | ||
| 882 | ) { | ||
| 876 | maxY = insets.boundingRectTop.bottom.toFloat() | 883 | maxY = insets.boundingRectTop.bottom.toFloat() |
| 877 | if (insets.boundingRectRight.left != 0 && insets.boundingRectRight.left > maxX / 2) | 884 | } |
| 885 | if (insets.boundingRectRight.left != 0 && | ||
| 886 | insets.boundingRectRight.left > maxX / 2 | ||
| 887 | ) { | ||
| 878 | maxX = insets.boundingRectRight.left.toFloat() | 888 | maxX = insets.boundingRectRight.left.toFloat() |
| 889 | } | ||
| 879 | 890 | ||
| 880 | minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left | 891 | minX = insets.boundingRectLeft.right - insets.boundingRectLeft.left |
| 881 | minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom | 892 | minY = insets.boundingRectBottom.top - insets.boundingRectBottom.bottom |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt index 43d664d21..8aef6f5a5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt | |||
| @@ -133,7 +133,10 @@ class InputOverlayDrawableDpad( | |||
| 133 | downButtonState = axisY > VIRT_AXIS_DEADZONE | 133 | downButtonState = axisY > VIRT_AXIS_DEADZONE |
| 134 | leftButtonState = axisX < -VIRT_AXIS_DEADZONE | 134 | leftButtonState = axisX < -VIRT_AXIS_DEADZONE |
| 135 | rightButtonState = axisX > VIRT_AXIS_DEADZONE | 135 | rightButtonState = axisX > VIRT_AXIS_DEADZONE |
| 136 | return oldUpState != upButtonState || oldDownState != downButtonState || oldLeftState != leftButtonState || oldRightState != rightButtonState | 136 | return oldUpState != upButtonState || |
| 137 | oldDownState != downButtonState || | ||
| 138 | oldLeftState != leftButtonState || | ||
| 139 | oldRightState != rightButtonState | ||
| 137 | } | 140 | } |
| 138 | return false | 141 | return false |
| 139 | } | 142 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt index f1d32192a..fb48f584d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt | |||
| @@ -9,12 +9,12 @@ import android.graphics.Canvas | |||
| 9 | import android.graphics.Rect | 9 | import android.graphics.Rect |
| 10 | import android.graphics.drawable.BitmapDrawable | 10 | import android.graphics.drawable.BitmapDrawable |
| 11 | import android.view.MotionEvent | 11 | import android.view.MotionEvent |
| 12 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 13 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | ||
| 14 | import kotlin.math.atan2 | 12 | import kotlin.math.atan2 |
| 15 | import kotlin.math.cos | 13 | import kotlin.math.cos |
| 16 | import kotlin.math.sin | 14 | import kotlin.math.sin |
| 17 | import kotlin.math.sqrt | 15 | import kotlin.math.sqrt |
| 16 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 17 | import org.yuzu.yuzu_emu.utils.EmulationMenuSettings | ||
| 18 | 18 | ||
| 19 | /** | 19 | /** |
| 20 | * Custom [BitmapDrawable] that is capable | 20 | * Custom [BitmapDrawable] that is capable |
| @@ -241,14 +241,22 @@ class InputOverlayDrawableJoystick( | |||
| 241 | private fun setInnerBounds() { | 241 | private fun setInnerBounds() { |
| 242 | var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() | 242 | var x = virtBounds.centerX() + (xAxis * (virtBounds.width() / 2)).toInt() |
| 243 | var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() | 243 | var y = virtBounds.centerY() + (yAxis * (virtBounds.height() / 2)).toInt() |
| 244 | if (x > virtBounds.centerX() + virtBounds.width() / 2) x = | 244 | if (x > virtBounds.centerX() + virtBounds.width() / 2) { |
| 245 | virtBounds.centerX() + virtBounds.width() / 2 | 245 | x = |
| 246 | if (x < virtBounds.centerX() - virtBounds.width() / 2) x = | 246 | virtBounds.centerX() + virtBounds.width() / 2 |
| 247 | virtBounds.centerX() - virtBounds.width() / 2 | 247 | } |
| 248 | if (y > virtBounds.centerY() + virtBounds.height() / 2) y = | 248 | if (x < virtBounds.centerX() - virtBounds.width() / 2) { |
| 249 | virtBounds.centerY() + virtBounds.height() / 2 | 249 | x = |
| 250 | if (y < virtBounds.centerY() - virtBounds.height() / 2) y = | 250 | virtBounds.centerX() - virtBounds.width() / 2 |
| 251 | virtBounds.centerY() - virtBounds.height() / 2 | 251 | } |
| 252 | if (y > virtBounds.centerY() + virtBounds.height() / 2) { | ||
| 253 | y = | ||
| 254 | virtBounds.centerY() + virtBounds.height() / 2 | ||
| 255 | } | ||
| 256 | if (y < virtBounds.centerY() - virtBounds.height() / 2) { | ||
| 257 | y = | ||
| 258 | virtBounds.centerY() - virtBounds.height() / 2 | ||
| 259 | } | ||
| 252 | val width = pressedStateInnerBitmap.bounds.width() / 2 | 260 | val width = pressedStateInnerBitmap.bounds.width() / 2 |
| 253 | val height = pressedStateInnerBitmap.bounds.height() / 2 | 261 | val height = pressedStateInnerBitmap.bounds.height() / 2 |
| 254 | defaultStateInnerBitmap.setBounds( | 262 | defaultStateInnerBitmap.setBounds( |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt index 97eef40d2..b0156dca5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt | |||
| @@ -99,7 +99,9 @@ class GamesFragment : Fragment() { | |||
| 99 | } | 99 | } |
| 100 | shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> | 100 | shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> |
| 101 | if (shouldSwapData) { | 101 | if (shouldSwapData) { |
| 102 | (binding.gridGames.adapter as GameAdapter).submitList(gamesViewModel.games.value!!) | 102 | (binding.gridGames.adapter as GameAdapter).submitList( |
| 103 | gamesViewModel.games.value!! | ||
| 104 | ) | ||
| 103 | gamesViewModel.setShouldSwapData(false) | 105 | gamesViewModel.setShouldSwapData(false) |
| 104 | } | 106 | } |
| 105 | } | 107 | } |
| @@ -128,7 +130,9 @@ class GamesFragment : Fragment() { | |||
| 128 | } | 130 | } |
| 129 | 131 | ||
| 130 | private fun setInsets() = | 132 | private fun setInsets() = |
| 131 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, windowInsets: WindowInsetsCompat -> | 133 | ViewCompat.setOnApplyWindowInsetsListener( |
| 134 | binding.root | ||
| 135 | ) { view: View, windowInsets: WindowInsetsCompat -> | ||
| 132 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 136 | val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 133 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) | 137 | val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
| 134 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large) | 138 | val extraListSpacing = resources.getDimensionPixelSize(R.dimen.spacing_large) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt index 041d16f3a..cc1d87f1b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt | |||
| @@ -26,6 +26,9 @@ import androidx.preference.PreferenceManager | |||
| 26 | import com.google.android.material.color.MaterialColors | 26 | import com.google.android.material.color.MaterialColors |
| 27 | import com.google.android.material.dialog.MaterialAlertDialogBuilder | 27 | import com.google.android.material.dialog.MaterialAlertDialogBuilder |
| 28 | import com.google.android.material.navigation.NavigationBarView | 28 | import com.google.android.material.navigation.NavigationBarView |
| 29 | import java.io.File | ||
| 30 | import java.io.FilenameFilter | ||
| 31 | import java.io.IOException | ||
| 29 | import kotlinx.coroutines.Dispatchers | 32 | import kotlinx.coroutines.Dispatchers |
| 30 | import kotlinx.coroutines.launch | 33 | import kotlinx.coroutines.launch |
| 31 | import kotlinx.coroutines.withContext | 34 | import kotlinx.coroutines.withContext |
| @@ -43,9 +46,6 @@ import org.yuzu.yuzu_emu.fragments.MessageDialogFragment | |||
| 43 | import org.yuzu.yuzu_emu.model.GamesViewModel | 46 | import org.yuzu.yuzu_emu.model.GamesViewModel |
| 44 | import org.yuzu.yuzu_emu.model.HomeViewModel | 47 | import org.yuzu.yuzu_emu.model.HomeViewModel |
| 45 | import org.yuzu.yuzu_emu.utils.* | 48 | import org.yuzu.yuzu_emu.utils.* |
| 46 | import java.io.File | ||
| 47 | import java.io.FilenameFilter | ||
| 48 | import java.io.IOException | ||
| 49 | 49 | ||
| 50 | class MainActivity : AppCompatActivity(), ThemeProvider { | 50 | class MainActivity : AppCompatActivity(), ThemeProvider { |
| 51 | private lateinit var binding: ActivityMainBinding | 51 | private lateinit var binding: ActivityMainBinding |
| @@ -86,7 +86,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 86 | ThemeHelper.SYSTEM_BAR_ALPHA | 86 | ThemeHelper.SYSTEM_BAR_ALPHA |
| 87 | ) | 87 | ) |
| 88 | ) | 88 | ) |
| 89 | if (InsetsHelper.getSystemGestureType(applicationContext) != InsetsHelper.GESTURE_NAVIGATION) { | 89 | if (InsetsHelper.getSystemGestureType(applicationContext) != |
| 90 | InsetsHelper.GESTURE_NAVIGATION | ||
| 91 | ) { | ||
| 90 | binding.navigationBarShade.setBackgroundColor( | 92 | binding.navigationBarShade.setBackgroundColor( |
| 91 | ThemeHelper.getColorWithOpacity( | 93 | ThemeHelper.getColorWithOpacity( |
| 92 | MaterialColors.getColor( | 94 | MaterialColors.getColor( |
| @@ -172,7 +174,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 172 | binding.navigationView.height.toFloat() * 2 | 174 | binding.navigationView.height.toFloat() * 2 |
| 173 | translationY(0f) | 175 | translationY(0f) |
| 174 | } else { | 176 | } else { |
| 175 | if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { | 177 | if (ViewCompat.getLayoutDirection(binding.navigationView) == |
| 178 | ViewCompat.LAYOUT_DIRECTION_LTR | ||
| 179 | ) { | ||
| 176 | binding.navigationView.translationX = | 180 | binding.navigationView.translationX = |
| 177 | binding.navigationView.width.toFloat() * -2 | 181 | binding.navigationView.width.toFloat() * -2 |
| 178 | translationX(0f) | 182 | translationX(0f) |
| @@ -189,7 +193,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 189 | if (smallLayout) { | 193 | if (smallLayout) { |
| 190 | translationY(binding.navigationView.height.toFloat() * 2) | 194 | translationY(binding.navigationView.height.toFloat() * 2) |
| 191 | } else { | 195 | } else { |
| 192 | if (ViewCompat.getLayoutDirection(binding.navigationView) == ViewCompat.LAYOUT_DIRECTION_LTR) { | 196 | if (ViewCompat.getLayoutDirection(binding.navigationView) == |
| 197 | ViewCompat.LAYOUT_DIRECTION_LTR | ||
| 198 | ) { | ||
| 193 | translationX(binding.navigationView.width.toFloat() * -2) | 199 | translationX(binding.navigationView.width.toFloat() * -2) |
| 194 | } else { | 200 | } else { |
| 195 | translationX(binding.navigationView.width.toFloat() * 2) | 201 | translationX(binding.navigationView.width.toFloat() * 2) |
| @@ -234,7 +240,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 234 | } | 240 | } |
| 235 | 241 | ||
| 236 | private fun setInsets() = | 242 | private fun setInsets() = |
| 237 | ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _: View, windowInsets: WindowInsetsCompat -> | 243 | ViewCompat.setOnApplyWindowInsetsListener( |
| 244 | binding.root | ||
| 245 | ) { _: View, windowInsets: WindowInsetsCompat -> | ||
| 238 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | 246 | val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
| 239 | val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams | 247 | val mlpStatusShade = binding.statusBarShade.layoutParams as MarginLayoutParams |
| 240 | mlpStatusShade.height = insets.top | 248 | mlpStatusShade.height = insets.top |
| @@ -256,8 +264,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 256 | 264 | ||
| 257 | val getGamesDirectory = | 265 | val getGamesDirectory = |
| 258 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> | 266 | registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> |
| 259 | if (result == null) | 267 | if (result == null) { |
| 260 | return@registerForActivityResult | 268 | return@registerForActivityResult |
| 269 | } | ||
| 261 | 270 | ||
| 262 | contentResolver.takePersistableUriPermission( | 271 | contentResolver.takePersistableUriPermission( |
| 263 | result, | 272 | result, |
| @@ -281,8 +290,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 281 | 290 | ||
| 282 | val getProdKey = | 291 | val getProdKey = |
| 283 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 292 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 284 | if (result == null) | 293 | if (result == null) { |
| 285 | return@registerForActivityResult | 294 | return@registerForActivityResult |
| 295 | } | ||
| 286 | 296 | ||
| 287 | if (!FileUtil.hasExtension(result, "keys")) { | 297 | if (!FileUtil.hasExtension(result, "keys")) { |
| 288 | MessageDialogFragment.newInstance( | 298 | MessageDialogFragment.newInstance( |
| @@ -324,8 +334,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 324 | 334 | ||
| 325 | val getFirmware = | 335 | val getFirmware = |
| 326 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 336 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 327 | if (result == null) | 337 | if (result == null) { |
| 328 | return@registerForActivityResult | 338 | return@registerForActivityResult |
| 339 | } | ||
| 329 | 340 | ||
| 330 | val inputZip = contentResolver.openInputStream(result) | 341 | val inputZip = contentResolver.openInputStream(result) |
| 331 | if (inputZip == null) { | 342 | if (inputZip == null) { |
| @@ -376,8 +387,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 376 | 387 | ||
| 377 | val getAmiiboKey = | 388 | val getAmiiboKey = |
| 378 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 389 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 379 | if (result == null) | 390 | if (result == null) { |
| 380 | return@registerForActivityResult | 391 | return@registerForActivityResult |
| 392 | } | ||
| 381 | 393 | ||
| 382 | if (!FileUtil.hasExtension(result, "bin")) { | 394 | if (!FileUtil.hasExtension(result, "bin")) { |
| 383 | MessageDialogFragment.newInstance( | 395 | MessageDialogFragment.newInstance( |
| @@ -418,8 +430,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 418 | 430 | ||
| 419 | val getDriver = | 431 | val getDriver = |
| 420 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> | 432 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
| 421 | if (result == null) | 433 | if (result == null) { |
| 422 | return@registerForActivityResult | 434 | return@registerForActivityResult |
| 435 | } | ||
| 423 | 436 | ||
| 424 | val takeFlags = | 437 | val takeFlags = |
| 425 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION | 438 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION |
| @@ -470,8 +483,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { | |||
| 470 | 483 | ||
| 471 | val installGameUpdate = | 484 | val installGameUpdate = |
| 472 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { | 485 | registerForActivityResult(ActivityResultContracts.OpenDocument()) { |
| 473 | if (it == null) | 486 | if (it == null) { |
| 474 | return@registerForActivityResult | 487 | return@registerForActivityResult |
| 488 | } | ||
| 475 | 489 | ||
| 476 | IndeterminateProgressDialogFragment.newInstance( | 490 | IndeterminateProgressDialogFragment.newInstance( |
| 477 | this@MainActivity, | 491 | this@MainActivity, |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt index 791cea904..eeefcdf20 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ControllerMappingHelper.kt | |||
| @@ -19,7 +19,9 @@ class ControllerMappingHelper { | |||
| 19 | // The two analog triggers generate analog motion events as well as a keycode. | 19 | // The two analog triggers generate analog motion events as well as a keycode. |
| 20 | // We always prefer to use the analog values, so throw away the button press | 20 | // We always prefer to use the analog values, so throw away the button press |
| 21 | keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2 | 21 | keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2 |
| 22 | } else false | 22 | } else { |
| 23 | false | ||
| 24 | } | ||
| 23 | } | 25 | } |
| 24 | 26 | ||
| 25 | /** | 27 | /** |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt index 36c479e6c..2ee63697e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt | |||
| @@ -4,8 +4,8 @@ | |||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 8 | import java.io.IOException | 7 | import java.io.IOException |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | 9 | ||
| 10 | object DirectoryInitialization { | 10 | object DirectoryInitialization { |
| 11 | private var userPath: String? = null | 11 | private var userPath: String? = null |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt index f8abae445..cf226ad94 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt | |||
| @@ -5,10 +5,10 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.net.Uri | 6 | import android.net.Uri |
| 7 | import androidx.documentfile.provider.DocumentFile | 7 | import androidx.documentfile.provider.DocumentFile |
| 8 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 9 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 10 | import java.io.File | 8 | import java.io.File |
| 11 | import java.util.* | 9 | import java.util.* |
| 10 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 11 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 12 | 12 | ||
| 13 | class DocumentsTree { | 13 | class DocumentsTree { |
| 14 | private var root: DocumentsNode? = null | 14 | private var root: DocumentsNode? = null |
| @@ -29,7 +29,9 @@ class DocumentsTree { | |||
| 29 | val node = resolvePath(filepath) | 29 | val node = resolvePath(filepath) |
| 30 | return if (node == null || node.isDirectory) { | 30 | return if (node == null || node.isDirectory) { |
| 31 | 0 | 31 | 0 |
| 32 | } else FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) | 32 | } else { |
| 33 | FileUtil.getFileSize(YuzuApplication.appContext, node.uri.toString()) | ||
| 34 | } | ||
| 33 | } | 35 | } |
| 34 | 36 | ||
| 35 | fun exists(filepath: String): Boolean { | 37 | fun exists(filepath: String): Boolean { |
| @@ -111,7 +113,9 @@ class DocumentsTree { | |||
| 111 | fun isNativePath(path: String): Boolean { | 113 | fun isNativePath(path: String): Boolean { |
| 112 | return if (path.isNotEmpty()) { | 114 | return if (path.isNotEmpty()) { |
| 113 | path[0] == '/' | 115 | path[0] == '/' |
| 114 | } else false | 116 | } else { |
| 117 | false | ||
| 118 | } | ||
| 115 | } | 119 | } |
| 116 | } | 120 | } |
| 117 | } | 121 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt index 492b1ad91..9f3bbe56f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt | |||
| @@ -9,8 +9,6 @@ import android.net.Uri | |||
| 9 | import android.provider.DocumentsContract | 9 | import android.provider.DocumentsContract |
| 10 | import android.provider.OpenableColumns | 10 | import android.provider.OpenableColumns |
| 11 | import androidx.documentfile.provider.DocumentFile | 11 | import androidx.documentfile.provider.DocumentFile |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 13 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 14 | import java.io.BufferedInputStream | 12 | import java.io.BufferedInputStream |
| 15 | import java.io.File | 13 | import java.io.File |
| 16 | import java.io.FileOutputStream | 14 | import java.io.FileOutputStream |
| @@ -19,6 +17,8 @@ import java.io.InputStream | |||
| 19 | import java.net.URLDecoder | 17 | import java.net.URLDecoder |
| 20 | import java.util.zip.ZipEntry | 18 | import java.util.zip.ZipEntry |
| 21 | import java.util.zip.ZipInputStream | 19 | import java.util.zip.ZipInputStream |
| 20 | import org.yuzu.yuzu_emu.YuzuApplication | ||
| 21 | import org.yuzu.yuzu_emu.model.MinimalDocumentFile | ||
| 22 | 22 | ||
| 23 | object FileUtil { | 23 | object FileUtil { |
| 24 | const val PATH_TREE = "tree" | 24 | const val PATH_TREE = "tree" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt index dc9b7c744..086d17606 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt | |||
| @@ -54,7 +54,7 @@ class ForegroundService : Service() { | |||
| 54 | 54 | ||
| 55 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { | 55 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
| 56 | if (intent == null) { | 56 | if (intent == null) { |
| 57 | return START_NOT_STICKY; | 57 | return START_NOT_STICKY |
| 58 | } | 58 | } |
| 59 | if (intent.action == ACTION_STOP) { | 59 | if (intent.action == ACTION_STOP) { |
| 60 | NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) | 60 | NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 42b207618..ee9f3e570 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt | |||
| @@ -6,12 +6,12 @@ package org.yuzu.yuzu_emu.utils | |||
| 6 | import android.content.SharedPreferences | 6 | import android.content.SharedPreferences |
| 7 | import android.net.Uri | 7 | import android.net.Uri |
| 8 | import androidx.preference.PreferenceManager | 8 | import androidx.preference.PreferenceManager |
| 9 | import java.util.* | ||
| 9 | import kotlinx.serialization.encodeToString | 10 | import kotlinx.serialization.encodeToString |
| 10 | import kotlinx.serialization.json.Json | 11 | import kotlinx.serialization.json.Json |
| 11 | import org.yuzu.yuzu_emu.NativeLibrary | 12 | import org.yuzu.yuzu_emu.NativeLibrary |
| 12 | import org.yuzu.yuzu_emu.YuzuApplication | 13 | import org.yuzu.yuzu_emu.YuzuApplication |
| 13 | import org.yuzu.yuzu_emu.model.Game | 14 | import org.yuzu.yuzu_emu.model.Game |
| 14 | import java.util.* | ||
| 15 | 15 | ||
| 16 | object GameHelper { | 16 | object GameHelper { |
| 17 | const val KEY_GAME_PATH = "game_path" | 17 | const val KEY_GAME_PATH = "game_path" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt index 528011d7f..dad159481 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt | |||
| @@ -5,14 +5,14 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.content.Context | 6 | import android.content.Context |
| 7 | import android.net.Uri | 7 | import android.net.Uri |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage | ||
| 10 | import java.io.BufferedInputStream | 8 | import java.io.BufferedInputStream |
| 11 | import java.io.File | 9 | import java.io.File |
| 12 | import java.io.FileInputStream | 10 | import java.io.FileInputStream |
| 13 | import java.io.FileOutputStream | 11 | import java.io.FileOutputStream |
| 14 | import java.io.IOException | 12 | import java.io.IOException |
| 15 | import java.util.zip.ZipInputStream | 13 | import java.util.zip.ZipInputStream |
| 14 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 15 | import org.yuzu.yuzu_emu.utils.FileUtil.copyUriToInternalStorage | ||
| 16 | 16 | ||
| 17 | object GpuDriverHelper { | 17 | object GpuDriverHelper { |
| 18 | private const val META_JSON_FILENAME = "meta.json" | 18 | private const val META_JSON_FILENAME = "meta.json" |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt index 70bdb4097..a4e64070a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt | |||
| @@ -3,12 +3,12 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import org.json.JSONException | ||
| 7 | import org.json.JSONObject | ||
| 8 | import java.io.IOException | 6 | import java.io.IOException |
| 9 | import java.nio.charset.StandardCharsets | 7 | import java.nio.charset.StandardCharsets |
| 10 | import java.nio.file.Files | 8 | import java.nio.file.Files |
| 11 | import java.nio.file.Paths | 9 | import java.nio.file.Paths |
| 10 | import org.json.JSONException | ||
| 11 | import org.json.JSONObject | ||
| 12 | 12 | ||
| 13 | class GpuDriverMetadata(metadataFilePath: String) { | 13 | class GpuDriverMetadata(metadataFilePath: String) { |
| 14 | var name: String? = null | 14 | var name: String? = null |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt index 24e999b29..e963dfbc1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt | |||
| @@ -5,8 +5,8 @@ package org.yuzu.yuzu_emu.utils | |||
| 5 | 5 | ||
| 6 | import android.view.KeyEvent | 6 | import android.view.KeyEvent |
| 7 | import android.view.MotionEvent | 7 | import android.view.MotionEvent |
| 8 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 9 | import kotlin.math.sqrt | 8 | import kotlin.math.sqrt |
| 9 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 10 | 10 | ||
| 11 | class InputHandler { | 11 | class InputHandler { |
| 12 | fun initialize() { | 12 | fun initialize() { |
| @@ -68,7 +68,11 @@ class InputHandler { | |||
| 68 | 6 -> NativeLibrary.Player6Device | 68 | 6 -> NativeLibrary.Player6Device |
| 69 | 7 -> NativeLibrary.Player7Device | 69 | 7 -> NativeLibrary.Player7Device |
| 70 | 8 -> NativeLibrary.Player8Device | 70 | 8 -> NativeLibrary.Player8Device |
| 71 | else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device | 71 | else -> if (NativeLibrary.isHandheldOnly()) { |
| 72 | NativeLibrary.ConsoleDevice | ||
| 73 | } else { | ||
| 74 | NativeLibrary.Player1Device | ||
| 75 | } | ||
| 72 | } | 76 | } |
| 73 | } | 77 | } |
| 74 | 78 | ||
| @@ -107,7 +111,11 @@ class InputHandler { | |||
| 107 | } | 111 | } |
| 108 | 112 | ||
| 109 | private fun getAxisToButton(axis: Float): Int { | 113 | private fun getAxisToButton(axis: Float): Int { |
| 110 | return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED | 114 | return if (axis > 0.5f) { |
| 115 | NativeLibrary.ButtonState.PRESSED | ||
| 116 | } else { | ||
| 117 | NativeLibrary.ButtonState.RELEASED | ||
| 118 | } | ||
| 111 | } | 119 | } |
| 112 | 120 | ||
| 113 | private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { | 121 | private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { |
| @@ -287,7 +295,6 @@ class InputHandler { | |||
| 287 | } | 295 | } |
| 288 | } | 296 | } |
| 289 | 297 | ||
| 290 | |||
| 291 | private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { | 298 | private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { |
| 292 | // Joycon support is half dead. Right joystick doesn't work | 299 | // Joycon support is half dead. Right joystick doesn't work |
| 293 | val playerNumber = getPlayerNumber(event.device.controllerNumber) | 300 | val playerNumber = getPlayerNumber(event.device.controllerNumber) |
| @@ -355,6 +362,4 @@ class InputHandler { | |||
| 355 | ) | 362 | ) |
| 356 | } | 363 | } |
| 357 | } | 364 | } |
| 358 | 365 | } | |
| 359 | |||
| 360 | } \ No newline at end of file | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt index 19c53c481..595f0d284 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt | |||
| @@ -4,9 +4,7 @@ | |||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.annotation.SuppressLint | 6 | import android.annotation.SuppressLint |
| 7 | import android.app.Activity | ||
| 8 | import android.content.Context | 7 | import android.content.Context |
| 9 | import android.graphics.Rect | ||
| 10 | 8 | ||
| 11 | object InsetsHelper { | 9 | object InsetsHelper { |
| 12 | const val THREE_BUTTON_NAVIGATION = 0 | 10 | const val THREE_BUTTON_NAVIGATION = 0 |
| @@ -20,12 +18,8 @@ object InsetsHelper { | |||
| 20 | resources.getIdentifier("config_navBarInteractionMode", "integer", "android") | 18 | resources.getIdentifier("config_navBarInteractionMode", "integer", "android") |
| 21 | return if (resourceId != 0) { | 19 | return if (resourceId != 0) { |
| 22 | resources.getInteger(resourceId) | 20 | resources.getInteger(resourceId) |
| 23 | } else 0 | 21 | } else { |
| 24 | } | 22 | 0 |
| 25 | 23 | } | |
| 26 | fun getBottomPaddingRequired(activity: Activity): Int { | ||
| 27 | val visibleFrame = Rect() | ||
| 28 | activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame) | ||
| 29 | return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels | ||
| 30 | } | 24 | } |
| 31 | } | 25 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt index 344dd8a0a..68ed66565 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/NfcReader.kt | |||
| @@ -13,8 +13,8 @@ import android.nfc.tech.NfcA | |||
| 13 | import android.os.Build | 13 | import android.os.Build |
| 14 | import android.os.Handler | 14 | import android.os.Handler |
| 15 | import android.os.Looper | 15 | import android.os.Looper |
| 16 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 17 | import java.io.IOException | 16 | import java.io.IOException |
| 17 | import org.yuzu.yuzu_emu.NativeLibrary | ||
| 18 | 18 | ||
| 19 | class NfcReader(private val activity: Activity) { | 19 | class NfcReader(private val activity: Activity) { |
| 20 | private var nfcAdapter: NfcAdapter? = null | 20 | private var nfcAdapter: NfcAdapter? = null |
| @@ -25,10 +25,13 @@ class NfcReader(private val activity: Activity) { | |||
| 25 | 25 | ||
| 26 | pendingIntent = PendingIntent.getActivity( | 26 | pendingIntent = PendingIntent.getActivity( |
| 27 | activity, | 27 | activity, |
| 28 | 0, Intent(activity, activity.javaClass), | 28 | 0, |
| 29 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) | 29 | Intent(activity, activity.javaClass), |
| 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| 30 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE | 31 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE |
| 31 | else PendingIntent.FLAG_UPDATE_CURRENT | 32 | } else { |
| 33 | PendingIntent.FLAG_UPDATE_CURRENT | ||
| 34 | } | ||
| 32 | ) | 35 | ) |
| 33 | 36 | ||
| 34 | val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) | 37 | val tagDetected = IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) |
| @@ -45,9 +48,9 @@ class NfcReader(private val activity: Activity) { | |||
| 45 | 48 | ||
| 46 | fun onNewIntent(intent: Intent) { | 49 | fun onNewIntent(intent: Intent) { |
| 47 | val action = intent.action | 50 | val action = intent.action |
| 48 | if (NfcAdapter.ACTION_TAG_DISCOVERED != action | 51 | if (NfcAdapter.ACTION_TAG_DISCOVERED != action && |
| 49 | && NfcAdapter.ACTION_TECH_DISCOVERED != action | 52 | NfcAdapter.ACTION_TECH_DISCOVERED != action && |
| 50 | && NfcAdapter.ACTION_NDEF_DISCOVERED != action | 53 | NfcAdapter.ACTION_NDEF_DISCOVERED != action |
| 51 | ) { | 54 | ) { |
| 52 | return | 55 | return |
| 53 | } | 56 | } |
| @@ -84,7 +87,7 @@ class NfcReader(private val activity: Activity) { | |||
| 84 | } | 87 | } |
| 85 | 88 | ||
| 86 | private fun ntag215ReadAll(amiibo: NfcA): ByteArray? { | 89 | private fun ntag215ReadAll(amiibo: NfcA): ByteArray? { |
| 87 | val bufferSize = amiibo.maxTransceiveLength; | 90 | val bufferSize = amiibo.maxTransceiveLength |
| 88 | val tagSize = 0x21C | 91 | val tagSize = 0x21C |
| 89 | val pageSize = 4 | 92 | val pageSize = 4 |
| 90 | val lastPage = tagSize / pageSize - 1 | 93 | val lastPage = tagSize / pageSize - 1 |
| @@ -103,7 +106,7 @@ class NfcReader(private val activity: Activity) { | |||
| 103 | val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1) | 106 | val data = ntag215FastRead(amiibo, dataStart, dataEnd - 1) |
| 104 | System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize) | 107 | System.arraycopy(data, 0, tagData, i, (dataEnd - dataStart) * pageSize) |
| 105 | } catch (e: IOException) { | 108 | } catch (e: IOException) { |
| 106 | return null; | 109 | return null |
| 107 | } | 110 | } |
| 108 | } | 111 | } |
| 109 | return tagData | 112 | return tagData |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt index 87ee7f2e6..00e58faec 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt | |||
| @@ -11,30 +11,34 @@ import java.io.Serializable | |||
| 11 | 11 | ||
| 12 | object SerializableHelper { | 12 | object SerializableHelper { |
| 13 | inline fun <reified T : Serializable> Bundle.serializable(key: String): T? { | 13 | inline fun <reified T : Serializable> Bundle.serializable(key: String): T? { |
| 14 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 14 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 15 | getSerializable(key, T::class.java) | 15 | getSerializable(key, T::class.java) |
| 16 | else | 16 | } else { |
| 17 | getSerializable(key) as? T | 17 | getSerializable(key) as? T |
| 18 | } | ||
| 18 | } | 19 | } |
| 19 | 20 | ||
| 20 | inline fun <reified T : Serializable> Intent.serializable(key: String): T? { | 21 | inline fun <reified T : Serializable> Intent.serializable(key: String): T? { |
| 21 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 22 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 22 | getSerializableExtra(key, T::class.java) | 23 | getSerializableExtra(key, T::class.java) |
| 23 | else | 24 | } else { |
| 24 | getSerializableExtra(key) as? T | 25 | getSerializableExtra(key) as? T |
| 26 | } | ||
| 25 | } | 27 | } |
| 26 | 28 | ||
| 27 | inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? { | 29 | inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? { |
| 28 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 30 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 29 | getParcelable(key, T::class.java) | 31 | getParcelable(key, T::class.java) |
| 30 | else | 32 | } else { |
| 31 | getParcelable(key) as? T | 33 | getParcelable(key) as? T |
| 34 | } | ||
| 32 | } | 35 | } |
| 33 | 36 | ||
| 34 | inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? { | 37 | inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? { |
| 35 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | 38 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
| 36 | getParcelableExtra(key, T::class.java) | 39 | getParcelableExtra(key, T::class.java) |
| 37 | else | 40 | } else { |
| 38 | getParcelableExtra(key) as? T | 41 | getParcelableExtra(key) as? T |
| 42 | } | ||
| 39 | } | 43 | } |
| 40 | } | 44 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt index e55767c0f..f312e24cf 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ThemeHelper.kt | |||
| @@ -3,21 +3,19 @@ | |||
| 3 | 3 | ||
| 4 | package org.yuzu.yuzu_emu.utils | 4 | package org.yuzu.yuzu_emu.utils |
| 5 | 5 | ||
| 6 | import android.app.Activity | ||
| 7 | import android.content.res.Configuration | 6 | import android.content.res.Configuration |
| 8 | import android.graphics.Color | 7 | import android.graphics.Color |
| 9 | import androidx.annotation.ColorInt | 8 | import androidx.annotation.ColorInt |
| 10 | import androidx.appcompat.app.AppCompatActivity | 9 | import androidx.appcompat.app.AppCompatActivity |
| 11 | import androidx.appcompat.app.AppCompatDelegate | 10 | import androidx.appcompat.app.AppCompatDelegate |
| 12 | import androidx.core.content.ContextCompat | ||
| 13 | import androidx.core.view.WindowCompat | 11 | import androidx.core.view.WindowCompat |
| 14 | import androidx.core.view.WindowInsetsControllerCompat | 12 | import androidx.core.view.WindowInsetsControllerCompat |
| 15 | import androidx.preference.PreferenceManager | 13 | import androidx.preference.PreferenceManager |
| 14 | import kotlin.math.roundToInt | ||
| 16 | import org.yuzu.yuzu_emu.R | 15 | import org.yuzu.yuzu_emu.R |
| 17 | import org.yuzu.yuzu_emu.YuzuApplication | 16 | import org.yuzu.yuzu_emu.YuzuApplication |
| 18 | import org.yuzu.yuzu_emu.features.settings.model.Settings | 17 | import org.yuzu.yuzu_emu.features.settings.model.Settings |
| 19 | import org.yuzu.yuzu_emu.ui.main.ThemeProvider | 18 | import org.yuzu.yuzu_emu.ui.main.ThemeProvider |
| 20 | import kotlin.math.roundToInt | ||
| 21 | 19 | ||
| 22 | object ThemeHelper { | 20 | object ThemeHelper { |
| 23 | const val SYSTEM_BAR_ALPHA = 0.9f | 21 | const val SYSTEM_BAR_ALPHA = 0.9f |
| @@ -36,8 +34,8 @@ object ThemeHelper { | |||
| 36 | // Using a specific night mode check because this could apply incorrectly when using the | 34 | // Using a specific night mode check because this could apply incorrectly when using the |
| 37 | // light app mode, dark system mode, and black backgrounds. Launching the settings activity | 35 | // light app mode, dark system mode, and black backgrounds. Launching the settings activity |
| 38 | // will then show light mode colors/navigation bars but with black backgrounds. | 36 | // will then show light mode colors/navigation bars but with black backgrounds. |
| 39 | if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) | 37 | if (preferences.getBoolean(Settings.PREF_BLACK_BACKGROUNDS, false) && |
| 40 | && isNightMode(activity) | 38 | isNightMode(activity) |
| 41 | ) { | 39 | ) { |
| 42 | activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) | 40 | activity.setTheme(R.style.ThemeOverlay_Yuzu_Dark) |
| 43 | } | 41 | } |
| @@ -46,8 +44,10 @@ object ThemeHelper { | |||
| 46 | @ColorInt | 44 | @ColorInt |
| 47 | fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int { | 45 | fun getColorWithOpacity(@ColorInt color: Int, alphaFactor: Float): Int { |
| 48 | return Color.argb( | 46 | return Color.argb( |
| 49 | (alphaFactor * Color.alpha(color)).roundToInt(), Color.red(color), | 47 | (alphaFactor * Color.alpha(color)).roundToInt(), |
| 50 | Color.green(color), Color.blue(color) | 48 | Color.red(color), |
| 49 | Color.green(color), | ||
| 50 | Color.blue(color) | ||
| 51 | ) | 51 | ) |
| 52 | } | 52 | } |
| 53 | 53 | ||
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt index d89a89983..685ccaa76 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/FixedRatioSurfaceView.kt | |||
| @@ -38,8 +38,8 @@ class FixedRatioSurfaceView @JvmOverloads constructor( | |||
| 38 | newWidth = width | 38 | newWidth = width |
| 39 | newHeight = (width / aspectRatio).roundToInt() | 39 | newHeight = (width / aspectRatio).roundToInt() |
| 40 | } | 40 | } |
| 41 | val left = (width - newWidth) / 2; | 41 | val left = (width - newWidth) / 2 |
| 42 | val top = (height - newHeight) / 2; | 42 | val top = (height - newHeight) / 2 |
| 43 | setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) | 43 | setLeftTopRightBottom(left, top, left + newWidth, top + newHeight) |
| 44 | } else { | 44 | } else { |
| 45 | setLeftTopRightBottom(0, 0, width, height) | 45 | setLeftTopRightBottom(0, 0, width, height) |
diff --git a/src/android/app/src/main/res/layout/list_item_setting_switch.xml b/src/android/app/src/main/res/layout/list_item_setting_switch.xml index 599d845ad..a5767adee 100644 --- a/src/android/app/src/main/res/layout/list_item_setting_switch.xml +++ b/src/android/app/src/main/res/layout/list_item_setting_switch.xml | |||
| @@ -1,16 +1,16 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 3 | xmlns:tools="http://schemas.android.com/tools" | 4 | xmlns:tools="http://schemas.android.com/tools" |
| 4 | android:layout_width="match_parent" | 5 | android:layout_width="match_parent" |
| 5 | android:layout_height="wrap_content" | 6 | android:layout_height="wrap_content" |
| 6 | xmlns:app="http://schemas.android.com/apk/res-auto" | ||
| 7 | android:background="?android:attr/selectableItemBackground" | 7 | android:background="?android:attr/selectableItemBackground" |
| 8 | android:clickable="true" | 8 | android:clickable="true" |
| 9 | android:focusable="true" | 9 | android:focusable="true" |
| 10 | android:minHeight="72dp" | 10 | android:minHeight="72dp" |
| 11 | android:paddingVertical="@dimen/spacing_large" | ||
| 11 | android:paddingStart="@dimen/spacing_large" | 12 | android:paddingStart="@dimen/spacing_large" |
| 12 | android:paddingEnd="24dp" | 13 | android:paddingEnd="24dp"> |
| 13 | android:paddingVertical="@dimen/spacing_large"> | ||
| 14 | 14 | ||
| 15 | <com.google.android.material.materialswitch.MaterialSwitch | 15 | <com.google.android.material.materialswitch.MaterialSwitch |
| 16 | android:id="@+id/switch_widget" | 16 | android:id="@+id/switch_widget" |
| @@ -19,32 +19,35 @@ | |||
| 19 | android:layout_alignParentEnd="true" | 19 | android:layout_alignParentEnd="true" |
| 20 | android:layout_centerVertical="true" /> | 20 | android:layout_centerVertical="true" /> |
| 21 | 21 | ||
| 22 | <com.google.android.material.textview.MaterialTextView | 22 | <LinearLayout |
| 23 | style="@style/TextAppearance.Material3.BodySmall" | 23 | android:layout_width="match_parent" |
| 24 | android:id="@+id/text_setting_description" | ||
| 25 | android:layout_width="wrap_content" | ||
| 26 | android:layout_height="wrap_content" | ||
| 27 | android:layout_alignParentStart="true" | ||
| 28 | android:layout_alignStart="@+id/text_setting_name" | ||
| 29 | android:layout_below="@+id/text_setting_name" | ||
| 30 | android:layout_marginEnd="@dimen/spacing_large" | ||
| 31 | android:layout_marginTop="@dimen/spacing_small" | ||
| 32 | android:layout_toStartOf="@+id/switch_widget" | ||
| 33 | android:textAlignment="viewStart" | ||
| 34 | tools:text="@string/frame_limit_enable_description" /> | ||
| 35 | |||
| 36 | <com.google.android.material.textview.MaterialTextView | ||
| 37 | style="@style/TextAppearance.Material3.HeadlineMedium" | ||
| 38 | android:id="@+id/text_setting_name" | ||
| 39 | android:layout_width="0dp" | ||
| 40 | android:layout_height="wrap_content" | 24 | android:layout_height="wrap_content" |
| 41 | android:layout_alignParentStart="true" | ||
| 42 | android:layout_alignParentTop="true" | 25 | android:layout_alignParentTop="true" |
| 26 | android:layout_centerVertical="true" | ||
| 43 | android:layout_marginEnd="@dimen/spacing_large" | 27 | android:layout_marginEnd="@dimen/spacing_large" |
| 44 | android:layout_toStartOf="@+id/switch_widget" | 28 | android:layout_toStartOf="@+id/switch_widget" |
| 45 | android:textSize="16sp" | 29 | android:gravity="center_vertical" |
| 46 | android:textAlignment="viewStart" | 30 | android:orientation="vertical"> |
| 47 | app:lineHeight="28dp" | 31 | |
| 48 | tools:text="@string/frame_limit_enable" /> | 32 | <com.google.android.material.textview.MaterialTextView |
| 33 | android:id="@+id/text_setting_name" | ||
| 34 | style="@style/TextAppearance.Material3.HeadlineMedium" | ||
| 35 | android:layout_width="wrap_content" | ||
| 36 | android:layout_height="wrap_content" | ||
| 37 | android:textAlignment="viewStart" | ||
| 38 | android:textSize="16sp" | ||
| 39 | app:lineHeight="28dp" | ||
| 40 | tools:text="@string/frame_limit_enable" /> | ||
| 41 | |||
| 42 | <com.google.android.material.textview.MaterialTextView | ||
| 43 | android:id="@+id/text_setting_description" | ||
| 44 | style="@style/TextAppearance.Material3.BodySmall" | ||
| 45 | android:layout_width="wrap_content" | ||
| 46 | android:layout_height="wrap_content" | ||
| 47 | android:layout_marginTop="@dimen/spacing_small" | ||
| 48 | android:textAlignment="viewStart" | ||
| 49 | tools:text="@string/frame_limit_enable_description" /> | ||
| 50 | |||
| 51 | </LinearLayout> | ||
| 49 | 52 | ||
| 50 | </RelativeLayout> | 53 | </RelativeLayout> |
diff --git a/src/android/app/src/main/res/layout/list_item_settings_header.xml b/src/android/app/src/main/res/layout/list_item_settings_header.xml index abd24df6f..cf85bc0da 100644 --- a/src/android/app/src/main/res/layout/list_item_settings_header.xml +++ b/src/android/app/src/main/res/layout/list_item_settings_header.xml | |||
| @@ -1,20 +1,14 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:tools="http://schemas.android.com/tools" | 3 | xmlns:tools="http://schemas.android.com/tools" |
| 4 | android:id="@+id/text_header_name" | ||
| 5 | style="@style/TextAppearance.Material3.TitleSmall" | ||
| 4 | android:layout_width="match_parent" | 6 | android:layout_width="match_parent" |
| 5 | android:layout_height="48dp" | 7 | android:layout_height="wrap_content" |
| 6 | android:paddingVertical="4dp" | 8 | android:layout_gravity="start|center_vertical" |
| 7 | android:paddingHorizontal="@dimen/spacing_large"> | 9 | android:paddingHorizontal="@dimen/spacing_large" |
| 8 | 10 | android:paddingVertical="16dp" | |
| 9 | <com.google.android.material.textview.MaterialTextView | 11 | android:textAlignment="viewStart" |
| 10 | style="@style/TextAppearance.Material3.TitleSmall" | 12 | android:textColor="?attr/colorPrimary" |
| 11 | android:id="@+id/text_header_name" | 13 | android:textStyle="bold" |
| 12 | android:layout_width="match_parent" | 14 | tools:text="CPU Settings" /> |
| 13 | android:layout_height="wrap_content" | ||
| 14 | android:layout_gravity="start|center_vertical" | ||
| 15 | android:textColor="?attr/colorPrimary" | ||
| 16 | android:textAlignment="viewStart" | ||
| 17 | android:textStyle="bold" | ||
| 18 | tools:text="CPU Settings" /> | ||
| 19 | |||
| 20 | </FrameLayout> | ||
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 7f7b1938c..6d092f7a9 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml | |||
| @@ -236,4 +236,15 @@ | |||
| 236 | <item>2</item> | 236 | <item>2</item> |
| 237 | </integer-array> | 237 | </integer-array> |
| 238 | 238 | ||
| 239 | <string-array name="outputEngineEntries"> | ||
| 240 | <item>@string/auto</item> | ||
| 241 | <item>@string/cubeb</item> | ||
| 242 | <item>@string/string_null</item> | ||
| 243 | </string-array> | ||
| 244 | <string-array name="outputEngineValues"> | ||
| 245 | <item>auto</item> | ||
| 246 | <item>cubeb</item> | ||
| 247 | <item>null</item> | ||
| 248 | </string-array> | ||
| 249 | |||
| 239 | </resources> | 250 | </resources> |
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index b5bc249d4..cc1d8c39d 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <resources> | 2 | <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> |
| 3 | 3 | ||
| 4 | <!-- General application strings --> | 4 | <!-- General application strings --> |
| 5 | <string name="app_name" translatable="false">yuzu</string> | 5 | <string name="app_name" translatable="false">yuzu</string> |
| @@ -158,7 +158,6 @@ | |||
| 158 | <string name="set_custom_rtc">Set custom RTC</string> | 158 | <string name="set_custom_rtc">Set custom RTC</string> |
| 159 | 159 | ||
| 160 | <!-- Graphics settings strings --> | 160 | <!-- Graphics settings strings --> |
| 161 | <string name="renderer_api">API</string> | ||
| 162 | <string name="renderer_accuracy">Accuracy level</string> | 161 | <string name="renderer_accuracy">Accuracy level</string> |
| 163 | <string name="renderer_resolution">Resolution (Handheld/Docked)</string> | 162 | <string name="renderer_resolution">Resolution (Handheld/Docked)</string> |
| 164 | <string name="renderer_vsync">VSync mode</string> | 163 | <string name="renderer_vsync">VSync mode</string> |
| @@ -172,12 +171,21 @@ | |||
| 172 | <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, reducing stutter but may introduce glitches.</string> | 171 | <string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously, reducing stutter but may introduce glitches.</string> |
| 173 | <string name="renderer_reactive_flushing">Use reactive flushing</string> | 172 | <string name="renderer_reactive_flushing">Use reactive flushing</string> |
| 174 | <string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string> | 173 | <string name="renderer_reactive_flushing_description">Improves rendering accuracy in some games at the cost of performance.</string> |
| 175 | <string name="renderer_debug">Graphics debugging</string> | ||
| 176 | <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string> | ||
| 177 | <string name="use_disk_shader_cache">Disk shader cache</string> | 174 | <string name="use_disk_shader_cache">Disk shader cache</string> |
| 178 | <string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string> | 175 | <string name="use_disk_shader_cache_description">Reduces stuttering by locally storing and loading generated shaders.</string> |
| 179 | 176 | ||
| 177 | <!-- Debug settings strings --> | ||
| 178 | <string name="cpu">CPU</string> | ||
| 179 | <string name="cpu_debug_mode">CPU Debugging</string> | ||
| 180 | <string name="cpu_debug_mode_description">Puts the CPU in a slow debugging mode.</string> | ||
| 181 | <string name="gpu">GPU</string> | ||
| 182 | <string name="renderer_api">API</string> | ||
| 183 | <string name="renderer_debug">Graphics debugging</string> | ||
| 184 | <string name="renderer_debug_description">Sets the graphics API to a slow debugging mode.</string> | ||
| 185 | <string name="fastmem">Fastmem</string> | ||
| 186 | |||
| 180 | <!-- Audio settings strings --> | 187 | <!-- Audio settings strings --> |
| 188 | <string name="audio_output_engine">Output engine</string> | ||
| 181 | <string name="audio_volume">Volume</string> | 189 | <string name="audio_volume">Volume</string> |
| 182 | <string name="audio_volume_description">Specifies the volume of audio output.</string> | 190 | <string name="audio_volume_description">Specifies the volume of audio output.</string> |
| 183 | 191 | ||
| @@ -196,6 +204,7 @@ | |||
| 196 | <string name="learn_more">Learn more</string> | 204 | <string name="learn_more">Learn more</string> |
| 197 | <string name="auto">Auto</string> | 205 | <string name="auto">Auto</string> |
| 198 | <string name="submit">Submit</string> | 206 | <string name="submit">Submit</string> |
| 207 | <string name="string_null">Null</string> | ||
| 199 | 208 | ||
| 200 | <!-- GPU driver installation --> | 209 | <!-- GPU driver installation --> |
| 201 | <string name="select_gpu_driver">Select GPU driver</string> | 210 | <string name="select_gpu_driver">Select GPU driver</string> |
| @@ -366,6 +375,9 @@ | |||
| 366 | <string name="theme_mode_light">Light</string> | 375 | <string name="theme_mode_light">Light</string> |
| 367 | <string name="theme_mode_dark">Dark</string> | 376 | <string name="theme_mode_dark">Dark</string> |
| 368 | 377 | ||
| 378 | <!-- Audio output engines --> | ||
| 379 | <string name="cubeb">cubeb</string> | ||
| 380 | |||
| 369 | <!-- Black backgrounds theme --> | 381 | <!-- Black backgrounds theme --> |
| 370 | <string name="use_black_backgrounds">Black backgrounds</string> | 382 | <string name="use_black_backgrounds">Black backgrounds</string> |
| 371 | <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string> | 383 | <string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string> |
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 70480b725..908811e2c 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp | |||
| @@ -4,6 +4,8 @@ | |||
| 4 | #include <algorithm> | 4 | #include <algorithm> |
| 5 | #include <atomic> | 5 | #include <atomic> |
| 6 | #include <cinttypes> | 6 | #include <cinttypes> |
| 7 | #include <condition_variable> | ||
| 8 | #include <mutex> | ||
| 7 | #include <optional> | 9 | #include <optional> |
| 8 | #include <vector> | 10 | #include <vector> |
| 9 | 11 | ||
| @@ -1313,7 +1315,8 @@ void KThread::RequestDummyThreadWait() { | |||
| 1313 | ASSERT(this->IsDummyThread()); | 1315 | ASSERT(this->IsDummyThread()); |
| 1314 | 1316 | ||
| 1315 | // We will block when the scheduler lock is released. | 1317 | // We will block when the scheduler lock is released. |
| 1316 | m_dummy_thread_runnable.store(false); | 1318 | std::scoped_lock lock{m_dummy_thread_mutex}; |
| 1319 | m_dummy_thread_runnable = false; | ||
| 1317 | } | 1320 | } |
| 1318 | 1321 | ||
| 1319 | void KThread::DummyThreadBeginWait() { | 1322 | void KThread::DummyThreadBeginWait() { |
| @@ -1323,7 +1326,8 @@ void KThread::DummyThreadBeginWait() { | |||
| 1323 | } | 1326 | } |
| 1324 | 1327 | ||
| 1325 | // Block until runnable is no longer false. | 1328 | // Block until runnable is no longer false. |
| 1326 | m_dummy_thread_runnable.wait(false); | 1329 | std::unique_lock lock{m_dummy_thread_mutex}; |
| 1330 | m_dummy_thread_cv.wait(lock, [this] { return m_dummy_thread_runnable; }); | ||
| 1327 | } | 1331 | } |
| 1328 | 1332 | ||
| 1329 | void KThread::DummyThreadEndWait() { | 1333 | void KThread::DummyThreadEndWait() { |
| @@ -1331,8 +1335,11 @@ void KThread::DummyThreadEndWait() { | |||
| 1331 | ASSERT(this->IsDummyThread()); | 1335 | ASSERT(this->IsDummyThread()); |
| 1332 | 1336 | ||
| 1333 | // Wake up the waiting thread. | 1337 | // Wake up the waiting thread. |
| 1334 | m_dummy_thread_runnable.store(true); | 1338 | { |
| 1335 | m_dummy_thread_runnable.notify_one(); | 1339 | std::scoped_lock lock{m_dummy_thread_mutex}; |
| 1340 | m_dummy_thread_runnable = true; | ||
| 1341 | } | ||
| 1342 | m_dummy_thread_cv.notify_one(); | ||
| 1336 | } | 1343 | } |
| 1337 | 1344 | ||
| 1338 | void KThread::BeginWait(KThreadQueue* queue) { | 1345 | void KThread::BeginWait(KThreadQueue* queue) { |
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index f9814ac8f..37fe5db77 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h | |||
| @@ -892,7 +892,9 @@ private: | |||
| 892 | std::shared_ptr<Common::Fiber> m_host_context{}; | 892 | std::shared_ptr<Common::Fiber> m_host_context{}; |
| 893 | ThreadType m_thread_type{}; | 893 | ThreadType m_thread_type{}; |
| 894 | StepState m_step_state{}; | 894 | StepState m_step_state{}; |
| 895 | std::atomic<bool> m_dummy_thread_runnable{true}; | 895 | bool m_dummy_thread_runnable{true}; |
| 896 | std::mutex m_dummy_thread_mutex{}; | ||
| 897 | std::condition_variable m_dummy_thread_cv{}; | ||
| 896 | 898 | ||
| 897 | // For debugging | 899 | // For debugging |
| 898 | std::vector<KSynchronizationObject*> m_wait_objects_for_debugging{}; | 900 | std::vector<KSynchronizationObject*> m_wait_objects_for_debugging{}; |
diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.cpp b/src/core/hle/service/nfc/common/amiibo_crypto.cpp index b2bcb68c3..bc232c334 100644 --- a/src/core/hle/service/nfc/common/amiibo_crypto.cpp +++ b/src/core/hle/service/nfc/common/amiibo_crypto.cpp | |||
| @@ -36,12 +36,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { | |||
| 36 | 36 | ||
| 37 | // Validate UUID | 37 | // Validate UUID |
| 38 | constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` | 38 | constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` |
| 39 | if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) != | 39 | if ((CT ^ ntag_file.uuid.part1[0] ^ ntag_file.uuid.part1[1] ^ ntag_file.uuid.part1[2]) != |
| 40 | ntag_file.uuid.uid[3]) { | 40 | ntag_file.uuid.crc_check1) { |
| 41 | return false; | 41 | return false; |
| 42 | } | 42 | } |
| 43 | if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^ | 43 | if ((ntag_file.uuid.part2[0] ^ ntag_file.uuid.part2[1] ^ ntag_file.uuid.part2[2] ^ |
| 44 | ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) { | 44 | ntag_file.uuid.nintendo_id) != ntag_file.uuid_crc_check2) { |
| 45 | return false; | 45 | return false; |
| 46 | } | 46 | } |
| 47 | 47 | ||
| @@ -74,8 +74,9 @@ bool IsAmiiboValid(const NTAG215File& ntag_file) { | |||
| 74 | NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { | 74 | NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { |
| 75 | NTAG215File encoded_data{}; | 75 | NTAG215File encoded_data{}; |
| 76 | 76 | ||
| 77 | encoded_data.uid = nfc_data.uuid.uid; | 77 | encoded_data.uid = nfc_data.uuid; |
| 78 | encoded_data.nintendo_id = nfc_data.uuid.nintendo_id; | 78 | encoded_data.uid_crc_check2 = nfc_data.uuid_crc_check2; |
| 79 | encoded_data.internal_number = nfc_data.internal_number; | ||
| 79 | encoded_data.static_lock = nfc_data.static_lock; | 80 | encoded_data.static_lock = nfc_data.static_lock; |
| 80 | encoded_data.compability_container = nfc_data.compability_container; | 81 | encoded_data.compability_container = nfc_data.compability_container; |
| 81 | encoded_data.hmac_data = nfc_data.user_memory.hmac_data; | 82 | encoded_data.hmac_data = nfc_data.user_memory.hmac_data; |
| @@ -94,7 +95,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { | |||
| 94 | encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc; | 95 | encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc; |
| 95 | encoded_data.application_area = nfc_data.user_memory.application_area; | 96 | encoded_data.application_area = nfc_data.user_memory.application_area; |
| 96 | encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; | 97 | encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; |
| 97 | encoded_data.lock_bytes = nfc_data.uuid.lock_bytes; | ||
| 98 | encoded_data.model_info = nfc_data.user_memory.model_info; | 98 | encoded_data.model_info = nfc_data.user_memory.model_info; |
| 99 | encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; | 99 | encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; |
| 100 | encoded_data.dynamic_lock = nfc_data.dynamic_lock; | 100 | encoded_data.dynamic_lock = nfc_data.dynamic_lock; |
| @@ -108,9 +108,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { | |||
| 108 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { | 108 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { |
| 109 | EncryptedNTAG215File nfc_data{}; | 109 | EncryptedNTAG215File nfc_data{}; |
| 110 | 110 | ||
| 111 | nfc_data.uuid.uid = encoded_data.uid; | 111 | nfc_data.uuid = encoded_data.uid; |
| 112 | nfc_data.uuid.nintendo_id = encoded_data.nintendo_id; | 112 | nfc_data.uuid_crc_check2 = encoded_data.uid_crc_check2; |
| 113 | nfc_data.uuid.lock_bytes = encoded_data.lock_bytes; | 113 | nfc_data.internal_number = encoded_data.internal_number; |
| 114 | nfc_data.static_lock = encoded_data.static_lock; | 114 | nfc_data.static_lock = encoded_data.static_lock; |
| 115 | nfc_data.compability_container = encoded_data.compability_container; | 115 | nfc_data.compability_container = encoded_data.compability_container; |
| 116 | nfc_data.user_memory.hmac_data = encoded_data.hmac_data; | 116 | nfc_data.user_memory.hmac_data = encoded_data.hmac_data; |
| @@ -139,23 +139,12 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { | |||
| 139 | return nfc_data; | 139 | return nfc_data; |
| 140 | } | 140 | } |
| 141 | 141 | ||
| 142 | u32 GetTagPassword(const TagUuid& uuid) { | ||
| 143 | // Verify that the generated password is correct | ||
| 144 | u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]); | ||
| 145 | password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8; | ||
| 146 | password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16; | ||
| 147 | password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24; | ||
| 148 | return password; | ||
| 149 | } | ||
| 150 | |||
| 151 | HashSeed GetSeed(const NTAG215File& data) { | 142 | HashSeed GetSeed(const NTAG215File& data) { |
| 152 | HashSeed seed{ | 143 | HashSeed seed{ |
| 153 | .magic = data.write_counter, | 144 | .magic = data.write_counter, |
| 154 | .padding = {}, | 145 | .padding = {}, |
| 155 | .uid_1 = data.uid, | 146 | .uid_1 = data.uid, |
| 156 | .nintendo_id_1 = data.nintendo_id, | ||
| 157 | .uid_2 = data.uid, | 147 | .uid_2 = data.uid, |
| 158 | .nintendo_id_2 = data.nintendo_id, | ||
| 159 | .keygen_salt = data.keygen_salt, | 148 | .keygen_salt = data.keygen_salt, |
| 160 | }; | 149 | }; |
| 161 | 150 | ||
| @@ -177,10 +166,11 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed | |||
| 177 | output.insert(output.end(), key.magic_bytes.begin(), | 166 | output.insert(output.end(), key.magic_bytes.begin(), |
| 178 | key.magic_bytes.begin() + key.magic_length); | 167 | key.magic_bytes.begin() + key.magic_length); |
| 179 | 168 | ||
| 180 | output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end()); | 169 | std::array<u8, sizeof(NFP::TagUuid)> seed_uuid{}; |
| 181 | output.emplace_back(seed.nintendo_id_1); | 170 | memcpy(seed_uuid.data(), &seed.uid_1, sizeof(NFP::TagUuid)); |
| 182 | output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end()); | 171 | output.insert(output.end(), seed_uuid.begin(), seed_uuid.end()); |
| 183 | output.emplace_back(seed.nintendo_id_2); | 172 | memcpy(seed_uuid.data(), &seed.uid_2, sizeof(NFP::TagUuid)); |
| 173 | output.insert(output.end(), seed_uuid.begin(), seed_uuid.end()); | ||
| 184 | 174 | ||
| 185 | for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { | 175 | for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { |
| 186 | output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); | 176 | 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 | |||
| 264 | 254 | ||
| 265 | // Copy the rest of the data directly | 255 | // Copy the rest of the data directly |
| 266 | out_data.uid = in_data.uid; | 256 | out_data.uid = in_data.uid; |
| 267 | out_data.nintendo_id = in_data.nintendo_id; | 257 | out_data.uid_crc_check2 = in_data.uid_crc_check2; |
| 268 | out_data.lock_bytes = in_data.lock_bytes; | 258 | out_data.internal_number = in_data.internal_number; |
| 269 | out_data.static_lock = in_data.static_lock; | 259 | out_data.static_lock = in_data.static_lock; |
| 270 | out_data.compability_container = in_data.compability_container; | 260 | out_data.compability_container = in_data.compability_container; |
| 271 | 261 | ||
diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.h b/src/core/hle/service/nfc/common/amiibo_crypto.h index bf3044ed9..6a3e0841e 100644 --- a/src/core/hle/service/nfc/common/amiibo_crypto.h +++ b/src/core/hle/service/nfc/common/amiibo_crypto.h | |||
| @@ -24,10 +24,8 @@ using DrgbOutput = std::array<u8, 0x20>; | |||
| 24 | struct HashSeed { | 24 | struct HashSeed { |
| 25 | u16_be magic; | 25 | u16_be magic; |
| 26 | std::array<u8, 0xE> padding; | 26 | std::array<u8, 0xE> padding; |
| 27 | NFC::UniqueSerialNumber uid_1; | 27 | TagUuid uid_1; |
| 28 | u8 nintendo_id_1; | 28 | TagUuid uid_2; |
| 29 | NFC::UniqueSerialNumber uid_2; | ||
| 30 | u8 nintendo_id_2; | ||
| 31 | std::array<u8, 0x20> keygen_salt; | 29 | std::array<u8, 0x20> keygen_salt; |
| 32 | }; | 30 | }; |
| 33 | static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); | 31 | static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); |
| @@ -69,9 +67,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data); | |||
| 69 | /// Converts from encoded file format to encrypted file format | 67 | /// Converts from encoded file format to encrypted file format |
| 70 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); | 68 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); |
| 71 | 69 | ||
| 72 | /// Returns password needed to allow write access to protected memory | ||
| 73 | u32 GetTagPassword(const TagUuid& uuid); | ||
| 74 | |||
| 75 | // Generates Seed needed for key derivation | 70 | // Generates Seed needed for key derivation |
| 76 | HashSeed GetSeed(const NTAG215File& data); | 71 | HashSeed GetSeed(const NTAG215File& data); |
| 77 | 72 | ||
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index b14f682b5..f4b180b06 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp | |||
| @@ -242,34 +242,39 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { | |||
| 242 | return ResultWrongDeviceState; | 242 | return ResultWrongDeviceState; |
| 243 | } | 243 | } |
| 244 | 244 | ||
| 245 | UniqueSerialNumber uuid = encrypted_tag_data.uuid.uid; | 245 | UniqueSerialNumber uuid{}; |
| 246 | 246 | u8 uuid_length{}; | |
| 247 | // Generate random UUID to bypass amiibo load limits | 247 | NfcProtocol protocol{NfcProtocol::TypeA}; |
| 248 | if (Settings::values.random_amiibo_id) { | 248 | TagType tag_type{TagType::Type2}; |
| 249 | Common::TinyMT rng{}; | ||
| 250 | rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); | ||
| 251 | rng.GenerateRandomBytes(uuid.data(), sizeof(UniqueSerialNumber)); | ||
| 252 | uuid[3] = 0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2]; | ||
| 253 | } | ||
| 254 | 249 | ||
| 255 | if (is_mifare) { | 250 | if (is_mifare) { |
| 256 | tag_info = { | 251 | tag_type = TagType::Mifare; |
| 257 | .uuid = uuid, | 252 | uuid_length = sizeof(NFP::NtagTagUuid); |
| 258 | .uuid_extension = {}, | 253 | memcpy(uuid.data(), mifare_data.data(), uuid_length); |
| 259 | .uuid_length = static_cast<u8>(uuid.size()), | 254 | } else { |
| 260 | .protocol = NfcProtocol::TypeA, | 255 | tag_type = TagType::Type2; |
| 261 | .tag_type = TagType::Type4, | 256 | uuid_length = sizeof(NFP::NtagTagUuid); |
| 257 | NFP::NtagTagUuid nUuid{ | ||
| 258 | .part1 = encrypted_tag_data.uuid.part1, | ||
| 259 | .part2 = encrypted_tag_data.uuid.part2, | ||
| 260 | .nintendo_id = encrypted_tag_data.uuid.nintendo_id, | ||
| 262 | }; | 261 | }; |
| 263 | return ResultSuccess; | 262 | memcpy(uuid.data(), &nUuid, uuid_length); |
| 263 | |||
| 264 | // Generate random UUID to bypass amiibo load limits | ||
| 265 | if (Settings::values.random_amiibo_id) { | ||
| 266 | Common::TinyMT rng{}; | ||
| 267 | rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); | ||
| 268 | rng.GenerateRandomBytes(uuid.data(), uuid_length); | ||
| 269 | } | ||
| 264 | } | 270 | } |
| 265 | 271 | ||
| 266 | // Protocol and tag type may change here | 272 | // Protocol and tag type may change here |
| 267 | tag_info = { | 273 | tag_info = { |
| 268 | .uuid = uuid, | 274 | .uuid = uuid, |
| 269 | .uuid_extension = {}, | 275 | .uuid_length = uuid_length, |
| 270 | .uuid_length = static_cast<u8>(uuid.size()), | 276 | .protocol = protocol, |
| 271 | .protocol = NfcProtocol::TypeA, | 277 | .tag_type = tag_type, |
| 272 | .tag_type = TagType::Type2, | ||
| 273 | }; | 278 | }; |
| 274 | 279 | ||
| 275 | return ResultSuccess; | 280 | return ResultSuccess; |
| @@ -277,8 +282,38 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { | |||
| 277 | 282 | ||
| 278 | Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters, | 283 | Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters, |
| 279 | std::span<MifareReadBlockData> read_block_data) const { | 284 | std::span<MifareReadBlockData> read_block_data) const { |
| 285 | if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { | ||
| 286 | LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||
| 287 | if (device_state == DeviceState::TagRemoved) { | ||
| 288 | return ResultTagRemoved; | ||
| 289 | } | ||
| 290 | return ResultWrongDeviceState; | ||
| 291 | } | ||
| 292 | |||
| 280 | Result result = ResultSuccess; | 293 | Result result = ResultSuccess; |
| 281 | 294 | ||
| 295 | TagInfo tag_info{}; | ||
| 296 | result = GetTagInfo(tag_info, true); | ||
| 297 | |||
| 298 | if (result.IsError()) { | ||
| 299 | return result; | ||
| 300 | } | ||
| 301 | |||
| 302 | if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) { | ||
| 303 | return ResultInvalidTagType; | ||
| 304 | } | ||
| 305 | |||
| 306 | if (parameters.size() == 0) { | ||
| 307 | return ResultInvalidArgument; | ||
| 308 | } | ||
| 309 | |||
| 310 | const auto unknown = parameters[0].sector_key.unknown; | ||
| 311 | for (std::size_t i = 0; i < parameters.size(); i++) { | ||
| 312 | if (unknown != parameters[i].sector_key.unknown) { | ||
| 313 | return ResultInvalidArgument; | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 282 | for (std::size_t i = 0; i < parameters.size(); i++) { | 317 | for (std::size_t i = 0; i < parameters.size(); i++) { |
| 283 | result = ReadMifare(parameters[i], read_block_data[i]); | 318 | result = ReadMifare(parameters[i], read_block_data[i]); |
| 284 | if (result.IsError()) { | 319 | if (result.IsError()) { |
| @@ -293,17 +328,8 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, | |||
| 293 | MifareReadBlockData& read_block_data) const { | 328 | MifareReadBlockData& read_block_data) const { |
| 294 | const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); | 329 | const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); |
| 295 | read_block_data.sector_number = parameter.sector_number; | 330 | read_block_data.sector_number = parameter.sector_number; |
| 296 | |||
| 297 | if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { | ||
| 298 | LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | ||
| 299 | if (device_state == DeviceState::TagRemoved) { | ||
| 300 | return ResultTagRemoved; | ||
| 301 | } | ||
| 302 | return ResultWrongDeviceState; | ||
| 303 | } | ||
| 304 | |||
| 305 | if (mifare_data.size() < sector_index + sizeof(DataBlock)) { | 331 | if (mifare_data.size() < sector_index + sizeof(DataBlock)) { |
| 306 | return Mifare::ResultReadError; | 332 | return ResultMifareError288; |
| 307 | } | 333 | } |
| 308 | 334 | ||
| 309 | // TODO: Use parameter.sector_key to read encrypted data | 335 | // TODO: Use parameter.sector_key to read encrypted data |
| @@ -315,6 +341,28 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, | |||
| 315 | Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) { | 341 | Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) { |
| 316 | Result result = ResultSuccess; | 342 | Result result = ResultSuccess; |
| 317 | 343 | ||
| 344 | TagInfo tag_info{}; | ||
| 345 | result = GetTagInfo(tag_info, true); | ||
| 346 | |||
| 347 | if (result.IsError()) { | ||
| 348 | return result; | ||
| 349 | } | ||
| 350 | |||
| 351 | if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) { | ||
| 352 | return ResultInvalidTagType; | ||
| 353 | } | ||
| 354 | |||
| 355 | if (parameters.size() == 0) { | ||
| 356 | return ResultInvalidArgument; | ||
| 357 | } | ||
| 358 | |||
| 359 | const auto unknown = parameters[0].sector_key.unknown; | ||
| 360 | for (std::size_t i = 0; i < parameters.size(); i++) { | ||
| 361 | if (unknown != parameters[i].sector_key.unknown) { | ||
| 362 | return ResultInvalidArgument; | ||
| 363 | } | ||
| 364 | } | ||
| 365 | |||
| 318 | for (std::size_t i = 0; i < parameters.size(); i++) { | 366 | for (std::size_t i = 0; i < parameters.size(); i++) { |
| 319 | result = WriteMifare(parameters[i]); | 367 | result = WriteMifare(parameters[i]); |
| 320 | if (result.IsError()) { | 368 | if (result.IsError()) { |
| @@ -324,7 +372,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet | |||
| 324 | 372 | ||
| 325 | if (!npad_device->WriteNfc(mifare_data)) { | 373 | if (!npad_device->WriteNfc(mifare_data)) { |
| 326 | LOG_ERROR(Service_NFP, "Error writing to file"); | 374 | LOG_ERROR(Service_NFP, "Error writing to file"); |
| 327 | return Mifare::ResultReadError; | 375 | return ResultMifareError288; |
| 328 | } | 376 | } |
| 329 | 377 | ||
| 330 | return result; | 378 | return result; |
| @@ -342,7 +390,7 @@ Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) { | |||
| 342 | } | 390 | } |
| 343 | 391 | ||
| 344 | if (mifare_data.size() < sector_index + sizeof(DataBlock)) { | 392 | if (mifare_data.size() < sector_index + sizeof(DataBlock)) { |
| 345 | return Mifare::ResultReadError; | 393 | return ResultMifareError288; |
| 346 | } | 394 | } |
| 347 | 395 | ||
| 348 | // TODO: Use parameter.sector_key to encrypt the data | 396 | // TODO: Use parameter.sector_key to encrypt the data |
| @@ -366,7 +414,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target | |||
| 366 | 414 | ||
| 367 | if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { | 415 | if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { |
| 368 | LOG_ERROR(Service_NFP, "Not an amiibo"); | 416 | LOG_ERROR(Service_NFP, "Not an amiibo"); |
| 369 | return ResultNotAnAmiibo; | 417 | return ResultInvalidTagType; |
| 370 | } | 418 | } |
| 371 | 419 | ||
| 372 | // The loaded amiibo is not encrypted | 420 | // The loaded amiibo is not encrypted |
| @@ -381,14 +429,14 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target | |||
| 381 | } | 429 | } |
| 382 | 430 | ||
| 383 | if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { | 431 | if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { |
| 384 | bool has_backup = HasBackup(encrypted_tag_data.uuid.uid).IsSuccess(); | 432 | bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess(); |
| 385 | LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); | 433 | LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); |
| 386 | return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData; | 434 | return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData; |
| 387 | } | 435 | } |
| 388 | 436 | ||
| 389 | std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); | 437 | std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); |
| 390 | memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); | 438 | memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); |
| 391 | WriteBackupData(encrypted_tag_data.uuid.uid, data); | 439 | WriteBackupData(encrypted_tag_data.uuid, data); |
| 392 | 440 | ||
| 393 | device_state = DeviceState::TagMounted; | 441 | device_state = DeviceState::TagMounted; |
| 394 | mount_target = mount_target_; | 442 | mount_target = mount_target_; |
| @@ -492,7 +540,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { | |||
| 492 | } | 540 | } |
| 493 | 541 | ||
| 494 | memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); | 542 | memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); |
| 495 | WriteBackupData(encrypted_tag_data.uuid.uid, data); | 543 | WriteBackupData(encrypted_tag_data.uuid, data); |
| 496 | } | 544 | } |
| 497 | 545 | ||
| 498 | if (!npad_device->WriteNfc(data)) { | 546 | if (!npad_device->WriteNfc(data)) { |
| @@ -520,7 +568,7 @@ Result NfcDevice::Restore() { | |||
| 520 | return result; | 568 | return result; |
| 521 | } | 569 | } |
| 522 | 570 | ||
| 523 | result = ReadBackupData(tag_info.uuid, data); | 571 | result = ReadBackupData(tag_info.uuid, tag_info.uuid_length, data); |
| 524 | 572 | ||
| 525 | if (result.IsError()) { | 573 | if (result.IsError()) { |
| 526 | return result; | 574 | return result; |
| @@ -548,7 +596,7 @@ Result NfcDevice::Restore() { | |||
| 548 | } | 596 | } |
| 549 | 597 | ||
| 550 | if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) { | 598 | if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) { |
| 551 | return ResultNotAnAmiibo; | 599 | return ResultInvalidTagType; |
| 552 | } | 600 | } |
| 553 | 601 | ||
| 554 | if (!is_plain_amiibo) { | 602 | if (!is_plain_amiibo) { |
| @@ -1194,10 +1242,12 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) { | |||
| 1194 | return FlushWithBreak(break_type); | 1242 | return FlushWithBreak(break_type); |
| 1195 | } | 1243 | } |
| 1196 | 1244 | ||
| 1197 | Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const { | 1245 | Result NfcDevice::HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const { |
| 1246 | ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); | ||
| 1198 | constexpr auto backup_dir = "backup"; | 1247 | constexpr auto backup_dir = "backup"; |
| 1199 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); | 1248 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); |
| 1200 | const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, "")); | 1249 | const auto file_name = |
| 1250 | fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); | ||
| 1201 | 1251 | ||
| 1202 | if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) { | 1252 | if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) { |
| 1203 | return ResultUnableToAccessBackupFile; | 1253 | return ResultUnableToAccessBackupFile; |
| @@ -1206,10 +1256,19 @@ Result NfcDevice::HasBackup(const NFC::UniqueSerialNumber& uid) const { | |||
| 1206 | return ResultSuccess; | 1256 | return ResultSuccess; |
| 1207 | } | 1257 | } |
| 1208 | 1258 | ||
| 1209 | Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const { | 1259 | Result NfcDevice::HasBackup(const NFP::TagUuid& tag_uid) const { |
| 1260 | UniqueSerialNumber uuid{}; | ||
| 1261 | memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); | ||
| 1262 | return HasBackup(uuid, sizeof(NFP::TagUuid)); | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | Result NfcDevice::ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, | ||
| 1266 | std::span<u8> data) const { | ||
| 1267 | ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); | ||
| 1210 | constexpr auto backup_dir = "backup"; | 1268 | constexpr auto backup_dir = "backup"; |
| 1211 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); | 1269 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); |
| 1212 | const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, "")); | 1270 | const auto file_name = |
| 1271 | fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); | ||
| 1213 | 1272 | ||
| 1214 | const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name, | 1273 | const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name, |
| 1215 | Common::FS::FileAccessMode::Read, | 1274 | Common::FS::FileAccessMode::Read, |
| @@ -1228,12 +1287,21 @@ Result NfcDevice::ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u | |||
| 1228 | return ResultSuccess; | 1287 | return ResultSuccess; |
| 1229 | } | 1288 | } |
| 1230 | 1289 | ||
| 1231 | Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data) { | 1290 | Result NfcDevice::ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const { |
| 1291 | UniqueSerialNumber uuid{}; | ||
| 1292 | memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); | ||
| 1293 | return ReadBackupData(uuid, sizeof(NFP::TagUuid), data); | ||
| 1294 | } | ||
| 1295 | |||
| 1296 | Result NfcDevice::WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, | ||
| 1297 | std::span<const u8> data) { | ||
| 1298 | ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); | ||
| 1232 | constexpr auto backup_dir = "backup"; | 1299 | constexpr auto backup_dir = "backup"; |
| 1233 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); | 1300 | const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); |
| 1234 | const auto file_name = fmt::format("{0:02x}.bin", fmt::join(uid, "")); | 1301 | const auto file_name = |
| 1302 | fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); | ||
| 1235 | 1303 | ||
| 1236 | if (HasBackup(uid).IsError()) { | 1304 | if (HasBackup(uid, uuid_size).IsError()) { |
| 1237 | if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) { | 1305 | if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) { |
| 1238 | return ResultBackupPathAlreadyExist; | 1306 | return ResultBackupPathAlreadyExist; |
| 1239 | } | 1307 | } |
| @@ -1260,6 +1328,12 @@ Result NfcDevice::WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span< | |||
| 1260 | return ResultSuccess; | 1328 | return ResultSuccess; |
| 1261 | } | 1329 | } |
| 1262 | 1330 | ||
| 1331 | Result NfcDevice::WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data) { | ||
| 1332 | UniqueSerialNumber uuid{}; | ||
| 1333 | memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); | ||
| 1334 | return WriteBackupData(uuid, sizeof(NFP::TagUuid), data); | ||
| 1335 | } | ||
| 1336 | |||
| 1263 | Result NfcDevice::WriteNtf(std::span<const u8> data) { | 1337 | Result NfcDevice::WriteNtf(std::span<const u8> data) { |
| 1264 | if (device_state != DeviceState::TagMounted) { | 1338 | if (device_state != DeviceState::TagMounted) { |
| 1265 | LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); | 1339 | LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); |
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h index 6f049b687..7560210d6 100644 --- a/src/core/hle/service/nfc/common/device.h +++ b/src/core/hle/service/nfc/common/device.h | |||
| @@ -86,9 +86,14 @@ public: | |||
| 86 | Result GetAll(NFP::NfpData& data) const; | 86 | Result GetAll(NFP::NfpData& data) const; |
| 87 | Result SetAll(const NFP::NfpData& data); | 87 | Result SetAll(const NFP::NfpData& data); |
| 88 | Result BreakTag(NFP::BreakType break_type); | 88 | Result BreakTag(NFP::BreakType break_type); |
| 89 | Result HasBackup(const NFC::UniqueSerialNumber& uid) const; | 89 | Result HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const; |
| 90 | Result ReadBackupData(const NFC::UniqueSerialNumber& uid, std::span<u8> data) const; | 90 | Result HasBackup(const NFP::TagUuid& tag_uid) const; |
| 91 | Result WriteBackupData(const NFC::UniqueSerialNumber& uid, std::span<const u8> data); | 91 | Result ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, |
| 92 | std::span<u8> data) const; | ||
| 93 | Result ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const; | ||
| 94 | Result WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, | ||
| 95 | std::span<const u8> data); | ||
| 96 | Result WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data); | ||
| 92 | Result WriteNtf(std::span<const u8> data); | 97 | Result WriteNtf(std::span<const u8> data); |
| 93 | 98 | ||
| 94 | u64 GetHandle() const; | 99 | u64 GetHandle() const; |
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp index cffd602df..b0456508e 100644 --- a/src/core/hle/service/nfc/common/device_manager.cpp +++ b/src/core/hle/service/nfc/common/device_manager.cpp | |||
| @@ -550,7 +550,7 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons | |||
| 550 | } | 550 | } |
| 551 | 551 | ||
| 552 | if (result.IsSuccess()) { | 552 | if (result.IsSuccess()) { |
| 553 | result = device->ReadBackupData(tag_info.uuid, data); | 553 | result = device->ReadBackupData(tag_info.uuid, tag_info.uuid_length, data); |
| 554 | result = VerifyDeviceResult(device, result); | 554 | result = VerifyDeviceResult(device, result); |
| 555 | } | 555 | } |
| 556 | 556 | ||
| @@ -569,7 +569,7 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat | |||
| 569 | } | 569 | } |
| 570 | 570 | ||
| 571 | if (result.IsSuccess()) { | 571 | if (result.IsSuccess()) { |
| 572 | result = device->WriteBackupData(tag_info.uuid, data); | 572 | result = device->WriteBackupData(tag_info.uuid, tag_info.uuid_length, data); |
| 573 | result = VerifyDeviceResult(device, result); | 573 | result = VerifyDeviceResult(device, result); |
| 574 | } | 574 | } |
| 575 | 575 | ||
diff --git a/src/core/hle/service/nfc/mifare_result.h b/src/core/hle/service/nfc/mifare_result.h index 4b60048a5..16a9171e6 100644 --- a/src/core/hle/service/nfc/mifare_result.h +++ b/src/core/hle/service/nfc/mifare_result.h | |||
| @@ -12,6 +12,6 @@ constexpr Result ResultInvalidArgument(ErrorModule::NFCMifare, 65); | |||
| 12 | constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73); | 12 | constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73); |
| 13 | constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80); | 13 | constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80); |
| 14 | constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97); | 14 | constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97); |
| 15 | constexpr Result ResultReadError(ErrorModule::NFCMifare, 288); | 15 | constexpr Result ResultNotAMifare(ErrorModule::NFCMifare, 288); |
| 16 | 16 | ||
| 17 | } // namespace Service::NFC::Mifare | 17 | } // namespace Service::NFC::Mifare |
diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp index 198d0f2b9..130fb7f78 100644 --- a/src/core/hle/service/nfc/nfc_interface.cpp +++ b/src/core/hle/service/nfc/nfc_interface.cpp | |||
| @@ -142,9 +142,13 @@ void NfcInterface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) { | |||
| 142 | void NfcInterface::StartDetection(HLERequestContext& ctx) { | 142 | void NfcInterface::StartDetection(HLERequestContext& ctx) { |
| 143 | IPC::RequestParser rp{ctx}; | 143 | IPC::RequestParser rp{ctx}; |
| 144 | const auto device_handle{rp.Pop<u64>()}; | 144 | const auto device_handle{rp.Pop<u64>()}; |
| 145 | const auto tag_protocol{rp.PopEnum<NfcProtocol>()}; | 145 | auto tag_protocol{NfcProtocol::All}; |
| 146 | LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); | 146 | |
| 147 | if (backend_type == BackendType::Nfc) { | ||
| 148 | tag_protocol = rp.PopEnum<NfcProtocol>(); | ||
| 149 | } | ||
| 147 | 150 | ||
| 151 | LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); | ||
| 148 | auto result = GetManager()->StartDetection(device_handle, tag_protocol); | 152 | auto result = GetManager()->StartDetection(device_handle, tag_protocol); |
| 149 | result = TranslateResultToServiceError(result); | 153 | result = TranslateResultToServiceError(result); |
| 150 | 154 | ||
| @@ -355,7 +359,7 @@ Result NfcInterface::TranslateResultToNfp(Result result) const { | |||
| 355 | if (result == ResultApplicationAreaExist) { | 359 | if (result == ResultApplicationAreaExist) { |
| 356 | return NFP::ResultApplicationAreaExist; | 360 | return NFP::ResultApplicationAreaExist; |
| 357 | } | 361 | } |
| 358 | if (result == ResultNotAnAmiibo) { | 362 | if (result == ResultInvalidTagType) { |
| 359 | return NFP::ResultNotAnAmiibo; | 363 | return NFP::ResultNotAnAmiibo; |
| 360 | } | 364 | } |
| 361 | if (result == ResultUnableToAccessBackupFile) { | 365 | if (result == ResultUnableToAccessBackupFile) { |
| @@ -381,6 +385,9 @@ Result NfcInterface::TranslateResultToMifare(Result result) const { | |||
| 381 | if (result == ResultTagRemoved) { | 385 | if (result == ResultTagRemoved) { |
| 382 | return Mifare::ResultTagRemoved; | 386 | return Mifare::ResultTagRemoved; |
| 383 | } | 387 | } |
| 388 | if (result == ResultInvalidTagType) { | ||
| 389 | return Mifare::ResultNotAMifare; | ||
| 390 | } | ||
| 384 | LOG_WARNING(Service_NFC, "Result conversion not handled"); | 391 | LOG_WARNING(Service_NFC, "Result conversion not handled"); |
| 385 | return result; | 392 | return result; |
| 386 | } | 393 | } |
diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h index 59a808740..715c0e80c 100644 --- a/src/core/hle/service/nfc/nfc_result.h +++ b/src/core/hle/service/nfc/nfc_result.h | |||
| @@ -24,7 +24,8 @@ constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFC, 136); | |||
| 24 | constexpr Result ResultCorruptedData(ErrorModule::NFC, 144); | 24 | constexpr Result ResultCorruptedData(ErrorModule::NFC, 144); |
| 25 | constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152); | 25 | constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152); |
| 26 | constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168); | 26 | constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168); |
| 27 | constexpr Result ResultNotAnAmiibo(ErrorModule::NFC, 178); | 27 | constexpr Result ResultInvalidTagType(ErrorModule::NFC, 178); |
| 28 | constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216); | 28 | constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216); |
| 29 | constexpr Result ResultMifareError288(ErrorModule::NFC, 288); | ||
| 29 | 30 | ||
| 30 | } // namespace Service::NFC | 31 | } // namespace Service::NFC |
diff --git a/src/core/hle/service/nfc/nfc_types.h b/src/core/hle/service/nfc/nfc_types.h index c7ebd1fdb..68e724442 100644 --- a/src/core/hle/service/nfc/nfc_types.h +++ b/src/core/hle/service/nfc/nfc_types.h | |||
| @@ -35,32 +35,35 @@ enum class State : u32 { | |||
| 35 | 35 | ||
| 36 | // This is nn::nfc::TagType | 36 | // This is nn::nfc::TagType |
| 37 | enum class TagType : u32 { | 37 | enum class TagType : u32 { |
| 38 | None, | 38 | None = 0, |
| 39 | Type1, // ISO14443A RW 96-2k bytes 106kbit/s | 39 | Type1 = 1U << 0, // ISO14443A RW. Topaz |
| 40 | Type2, // ISO14443A RW/RO 540 bytes 106kbit/s | 40 | Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN |
| 41 | Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s | 41 | Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa |
| 42 | Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s | 42 | Type4A = 1U << 3, // ISO14443A RW/RO. DESFire |
| 43 | Type5, // ISO15693 RW/RO 540 bytes 106kbit/s | 43 | Type4B = 1U << 4, // ISO14443B RW/RO. DESFire |
| 44 | Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV | ||
| 45 | Mifare = 1U << 6, // Mifare classic. Skylanders | ||
| 46 | All = 0xFFFFFFFF, | ||
| 44 | }; | 47 | }; |
| 45 | 48 | ||
| 46 | enum class PackedTagType : u8 { | 49 | enum class PackedTagType : u8 { |
| 47 | None, | 50 | None = 0, |
| 48 | Type1, // ISO14443A RW 96-2k bytes 106kbit/s | 51 | Type1 = 1U << 0, // ISO14443A RW. Topaz |
| 49 | Type2, // ISO14443A RW/RO 540 bytes 106kbit/s | 52 | Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN |
| 50 | Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s | 53 | Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa |
| 51 | Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s | 54 | Type4A = 1U << 3, // ISO14443A RW/RO. DESFire |
| 52 | Type5, // ISO15693 RW/RO 540 bytes 106kbit/s | 55 | Type4B = 1U << 4, // ISO14443B RW/RO. DESFire |
| 56 | Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV | ||
| 57 | Mifare = 1U << 6, // Mifare classic. Skylanders | ||
| 58 | All = 0xFF, | ||
| 53 | }; | 59 | }; |
| 54 | 60 | ||
| 55 | // This is nn::nfc::NfcProtocol | 61 | // This is nn::nfc::NfcProtocol |
| 56 | // Verify this enum. It might be completely wrong default protocol is 0x48 | ||
| 57 | enum class NfcProtocol : u32 { | 62 | enum class NfcProtocol : u32 { |
| 58 | None, | 63 | None, |
| 59 | TypeA = 1U << 0, // ISO14443A | 64 | TypeA = 1U << 0, // ISO14443A |
| 60 | TypeB = 1U << 1, // ISO14443B | 65 | TypeB = 1U << 1, // ISO14443B |
| 61 | TypeF = 1U << 2, // Sony FeliCa | 66 | TypeF = 1U << 2, // Sony FeliCa |
| 62 | Unknown1 = 1U << 3, | ||
| 63 | Unknown2 = 1U << 5, | ||
| 64 | All = 0xFFFFFFFFU, | 67 | All = 0xFFFFFFFFU, |
| 65 | }; | 68 | }; |
| 66 | 69 | ||
| @@ -69,8 +72,7 @@ enum class TestWaveType : u32 { | |||
| 69 | Unknown, | 72 | Unknown, |
| 70 | }; | 73 | }; |
| 71 | 74 | ||
| 72 | using UniqueSerialNumber = std::array<u8, 7>; | 75 | using UniqueSerialNumber = std::array<u8, 10>; |
| 73 | using UniqueSerialNumberExtension = std::array<u8, 3>; | ||
| 74 | 76 | ||
| 75 | // This is nn::nfc::DeviceHandle | 77 | // This is nn::nfc::DeviceHandle |
| 76 | using DeviceHandle = u64; | 78 | using DeviceHandle = u64; |
| @@ -78,7 +80,6 @@ using DeviceHandle = u64; | |||
| 78 | // This is nn::nfc::TagInfo | 80 | // This is nn::nfc::TagInfo |
| 79 | struct TagInfo { | 81 | struct TagInfo { |
| 80 | UniqueSerialNumber uuid; | 82 | UniqueSerialNumber uuid; |
| 81 | UniqueSerialNumberExtension uuid_extension; | ||
| 82 | u8 uuid_length; | 83 | u8 uuid_length; |
| 83 | INSERT_PADDING_BYTES(0x15); | 84 | INSERT_PADDING_BYTES(0x15); |
| 84 | NfcProtocol protocol; | 85 | NfcProtocol protocol; |
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index 7d36d5ee6..aed12a7f8 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h | |||
| @@ -85,7 +85,7 @@ enum class CabinetMode : u8 { | |||
| 85 | StartFormatter, | 85 | StartFormatter, |
| 86 | }; | 86 | }; |
| 87 | 87 | ||
| 88 | using LockBytes = std::array<u8, 2>; | 88 | using UuidPart = std::array<u8, 3>; |
| 89 | using HashData = std::array<u8, 0x20>; | 89 | using HashData = std::array<u8, 0x20>; |
| 90 | using ApplicationArea = std::array<u8, 0xD8>; | 90 | using ApplicationArea = std::array<u8, 0xD8>; |
| 91 | using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; | 91 | using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; |
| @@ -93,12 +93,20 @@ using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; | |||
| 93 | // This is nn::nfp::TagInfo | 93 | // This is nn::nfp::TagInfo |
| 94 | using TagInfo = NFC::TagInfo; | 94 | using TagInfo = NFC::TagInfo; |
| 95 | 95 | ||
| 96 | struct NtagTagUuid { | ||
| 97 | UuidPart part1; | ||
| 98 | UuidPart part2; | ||
| 99 | u8 nintendo_id; | ||
| 100 | }; | ||
| 101 | static_assert(sizeof(NtagTagUuid) == 7, "NtagTagUuid is an invalid size"); | ||
| 102 | |||
| 96 | struct TagUuid { | 103 | struct TagUuid { |
| 97 | NFC::UniqueSerialNumber uid; | 104 | UuidPart part1; |
| 105 | u8 crc_check1; | ||
| 106 | UuidPart part2; | ||
| 98 | u8 nintendo_id; | 107 | u8 nintendo_id; |
| 99 | LockBytes lock_bytes; | ||
| 100 | }; | 108 | }; |
| 101 | static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size"); | 109 | static_assert(sizeof(TagUuid) == 8, "TagUuid is an invalid size"); |
| 102 | 110 | ||
| 103 | struct WriteDate { | 111 | struct WriteDate { |
| 104 | u16 year; | 112 | u16 year; |
| @@ -231,7 +239,8 @@ struct EncryptedAmiiboFile { | |||
| 231 | static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); | 239 | static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); |
| 232 | 240 | ||
| 233 | struct NTAG215File { | 241 | struct NTAG215File { |
| 234 | LockBytes lock_bytes; // Tag UUID | 242 | u8 uid_crc_check2; |
| 243 | u8 internal_number; | ||
| 235 | u16 static_lock; // Set defined pages as read only | 244 | u16 static_lock; // Set defined pages as read only |
| 236 | u32 compability_container; // Defines available memory | 245 | u32 compability_container; // Defines available memory |
| 237 | HashData hmac_data; // Hash | 246 | HashData hmac_data; // Hash |
| @@ -250,8 +259,7 @@ struct NTAG215File { | |||
| 250 | u32_be register_info_crc; | 259 | u32_be register_info_crc; |
| 251 | ApplicationArea application_area; // Encrypted Game data | 260 | ApplicationArea application_area; // Encrypted Game data |
| 252 | HashData hmac_tag; // Hash | 261 | HashData hmac_tag; // Hash |
| 253 | NFC::UniqueSerialNumber uid; // Unique serial number | 262 | TagUuid uid; |
| 254 | u8 nintendo_id; // Tag UUID | ||
| 255 | AmiiboModelInfo model_info; | 263 | AmiiboModelInfo model_info; |
| 256 | HashData keygen_salt; // Salt | 264 | HashData keygen_salt; // Salt |
| 257 | u32 dynamic_lock; // Dynamic lock | 265 | u32 dynamic_lock; // Dynamic lock |
| @@ -264,7 +272,9 @@ static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be tr | |||
| 264 | #pragma pack() | 272 | #pragma pack() |
| 265 | 273 | ||
| 266 | struct EncryptedNTAG215File { | 274 | struct EncryptedNTAG215File { |
| 267 | TagUuid uuid; // Unique serial number | 275 | TagUuid uuid; |
| 276 | u8 uuid_crc_check2; | ||
| 277 | u8 internal_number; | ||
| 268 | u16 static_lock; // Set defined pages as read only | 278 | u16 static_lock; // Set defined pages as read only |
| 269 | u32 compability_container; // Defines available memory | 279 | u32 compability_container; // Defines available memory |
| 270 | EncryptedAmiiboFile user_memory; // Writable data | 280 | EncryptedAmiiboFile user_memory; // Writable data |
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp index f8bafe553..6435b8af8 100644 --- a/src/input_common/drivers/virtual_amiibo.cpp +++ b/src/input_common/drivers/virtual_amiibo.cpp | |||
| @@ -82,6 +82,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) { | |||
| 82 | switch (nfc_file.GetSize()) { | 82 | switch (nfc_file.GetSize()) { |
| 83 | case AmiiboSize: | 83 | case AmiiboSize: |
| 84 | case AmiiboSizeWithoutPassword: | 84 | case AmiiboSizeWithoutPassword: |
| 85 | case AmiiboSizeWithSignature: | ||
| 85 | data.resize(AmiiboSize); | 86 | data.resize(AmiiboSize); |
| 86 | if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) { | 87 | if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) { |
| 87 | return Info::NotAnAmiibo; | 88 | return Info::NotAnAmiibo; |
| @@ -109,6 +110,7 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) { | |||
| 109 | switch (data.size_bytes()) { | 110 | switch (data.size_bytes()) { |
| 110 | case AmiiboSize: | 111 | case AmiiboSize: |
| 111 | case AmiiboSizeWithoutPassword: | 112 | case AmiiboSizeWithoutPassword: |
| 113 | case AmiiboSizeWithSignature: | ||
| 112 | nfc_data.resize(AmiiboSize); | 114 | nfc_data.resize(AmiiboSize); |
| 113 | break; | 115 | break; |
| 114 | case MifareSize: | 116 | case MifareSize: |
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h index 34e97cd91..09ca09e68 100644 --- a/src/input_common/drivers/virtual_amiibo.h +++ b/src/input_common/drivers/virtual_amiibo.h | |||
| @@ -57,6 +57,7 @@ public: | |||
| 57 | private: | 57 | private: |
| 58 | static constexpr std::size_t AmiiboSize = 0x21C; | 58 | static constexpr std::size_t AmiiboSize = 0x21C; |
| 59 | static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8; | 59 | static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8; |
| 60 | static constexpr std::size_t AmiiboSizeWithSignature = AmiiboSize + 0x20; | ||
| 60 | static constexpr std::size_t MifareSize = 0x400; | 61 | static constexpr std::size_t MifareSize = 0x400; |
| 61 | 62 | ||
| 62 | std::string file_path{}; | 63 | std::string file_path{}; |
diff --git a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp index 1a0cea9b7..3151c0db8 100644 --- a/src/video_core/renderer_opengl/gl_compute_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_compute_pipeline.cpp | |||
| @@ -87,7 +87,8 @@ void ComputePipeline::Configure() { | |||
| 87 | texture_cache.SynchronizeComputeDescriptors(); | 87 | texture_cache.SynchronizeComputeDescriptors(); |
| 88 | 88 | ||
| 89 | boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; | 89 | boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; |
| 90 | std::array<GLuint, MAX_TEXTURES> samplers; | 90 | boost::container::static_vector<VideoCommon::SamplerId, MAX_TEXTURES> samplers; |
| 91 | std::array<GLuint, MAX_TEXTURES> gl_samplers; | ||
| 91 | std::array<GLuint, MAX_TEXTURES> textures; | 92 | std::array<GLuint, MAX_TEXTURES> textures; |
| 92 | std::array<GLuint, MAX_IMAGES> images; | 93 | std::array<GLuint, MAX_IMAGES> images; |
| 93 | GLsizei sampler_binding{}; | 94 | GLsizei sampler_binding{}; |
| @@ -131,7 +132,6 @@ void ComputePipeline::Configure() { | |||
| 131 | for (u32 index = 0; index < desc.count; ++index) { | 132 | for (u32 index = 0; index < desc.count; ++index) { |
| 132 | const auto handle{read_handle(desc, index)}; | 133 | const auto handle{read_handle(desc, index)}; |
| 133 | views.push_back({handle.first}); | 134 | views.push_back({handle.first}); |
| 134 | samplers[sampler_binding++] = 0; | ||
| 135 | } | 135 | } |
| 136 | } | 136 | } |
| 137 | for (const auto& desc : info.image_buffer_descriptors) { | 137 | for (const auto& desc : info.image_buffer_descriptors) { |
| @@ -142,8 +142,8 @@ void ComputePipeline::Configure() { | |||
| 142 | const auto handle{read_handle(desc, index)}; | 142 | const auto handle{read_handle(desc, index)}; |
| 143 | views.push_back({handle.first}); | 143 | views.push_back({handle.first}); |
| 144 | 144 | ||
| 145 | Sampler* const sampler = texture_cache.GetComputeSampler(handle.second); | 145 | VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second); |
| 146 | samplers[sampler_binding++] = sampler->Handle(); | 146 | samplers.push_back(sampler); |
| 147 | } | 147 | } |
| 148 | } | 148 | } |
| 149 | for (const auto& desc : info.image_descriptors) { | 149 | for (const auto& desc : info.image_descriptors) { |
| @@ -186,10 +186,17 @@ void ComputePipeline::Configure() { | |||
| 186 | 186 | ||
| 187 | const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers + | 187 | const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers + |
| 188 | num_image_buffers}; | 188 | num_image_buffers}; |
| 189 | const VideoCommon::SamplerId* samplers_it{samplers.data()}; | ||
| 189 | texture_binding += num_texture_buffers; | 190 | texture_binding += num_texture_buffers; |
| 190 | image_binding += num_image_buffers; | 191 | image_binding += num_image_buffers; |
| 191 | 192 | ||
| 192 | u32 texture_scaling_mask{}; | 193 | u32 texture_scaling_mask{}; |
| 194 | |||
| 195 | for (const auto& desc : info.texture_buffer_descriptors) { | ||
| 196 | for (u32 index = 0; index < desc.count; ++index) { | ||
| 197 | gl_samplers[sampler_binding++] = 0; | ||
| 198 | } | ||
| 199 | } | ||
| 193 | for (const auto& desc : info.texture_descriptors) { | 200 | for (const auto& desc : info.texture_descriptors) { |
| 194 | for (u32 index = 0; index < desc.count; ++index) { | 201 | for (u32 index = 0; index < desc.count; ++index) { |
| 195 | ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; | 202 | ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; |
| @@ -198,6 +205,12 @@ void ComputePipeline::Configure() { | |||
| 198 | texture_scaling_mask |= 1u << texture_binding; | 205 | texture_scaling_mask |= 1u << texture_binding; |
| 199 | } | 206 | } |
| 200 | ++texture_binding; | 207 | ++texture_binding; |
| 208 | |||
| 209 | const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))}; | ||
| 210 | const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && | ||
| 211 | !image_view.SupportsAnisotropy()}; | ||
| 212 | gl_samplers[sampler_binding++] = | ||
| 213 | use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle(); | ||
| 201 | } | 214 | } |
| 202 | } | 215 | } |
| 203 | u32 image_scaling_mask{}; | 216 | u32 image_scaling_mask{}; |
| @@ -228,7 +241,7 @@ void ComputePipeline::Configure() { | |||
| 228 | if (texture_binding != 0) { | 241 | if (texture_binding != 0) { |
| 229 | ASSERT(texture_binding == sampler_binding); | 242 | ASSERT(texture_binding == sampler_binding); |
| 230 | glBindTextures(0, texture_binding, textures.data()); | 243 | glBindTextures(0, texture_binding, textures.data()); |
| 231 | glBindSamplers(0, sampler_binding, samplers.data()); | 244 | glBindSamplers(0, sampler_binding, gl_samplers.data()); |
| 232 | } | 245 | } |
| 233 | if (image_binding != 0) { | 246 | if (image_binding != 0) { |
| 234 | glBindImageTextures(0, image_binding, images.data()); | 247 | glBindImageTextures(0, image_binding, images.data()); |
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp index 89000d6e0..c58f760b8 100644 --- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp +++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp | |||
| @@ -275,9 +275,9 @@ GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_c | |||
| 275 | template <typename Spec> | 275 | template <typename Spec> |
| 276 | void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | 276 | void GraphicsPipeline::ConfigureImpl(bool is_indexed) { |
| 277 | std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; | 277 | std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views; |
| 278 | std::array<GLuint, MAX_TEXTURES> samplers; | 278 | std::array<VideoCommon::SamplerId, MAX_TEXTURES> samplers; |
| 279 | size_t views_index{}; | 279 | size_t views_index{}; |
| 280 | GLsizei sampler_binding{}; | 280 | size_t samplers_index{}; |
| 281 | 281 | ||
| 282 | texture_cache.SynchronizeGraphicsDescriptors(); | 282 | texture_cache.SynchronizeGraphicsDescriptors(); |
| 283 | 283 | ||
| @@ -337,7 +337,6 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 337 | for (u32 index = 0; index < desc.count; ++index) { | 337 | for (u32 index = 0; index < desc.count; ++index) { |
| 338 | const auto handle{read_handle(desc, index)}; | 338 | const auto handle{read_handle(desc, index)}; |
| 339 | views[views_index++] = {handle.first}; | 339 | views[views_index++] = {handle.first}; |
| 340 | samplers[sampler_binding++] = 0; | ||
| 341 | } | 340 | } |
| 342 | } | 341 | } |
| 343 | } | 342 | } |
| @@ -351,8 +350,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 351 | const auto handle{read_handle(desc, index)}; | 350 | const auto handle{read_handle(desc, index)}; |
| 352 | views[views_index++] = {handle.first}; | 351 | views[views_index++] = {handle.first}; |
| 353 | 352 | ||
| 354 | Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)}; | 353 | VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)}; |
| 355 | samplers[sampler_binding++] = sampler->Handle(); | 354 | samplers[samplers_index++] = sampler; |
| 356 | } | 355 | } |
| 357 | } | 356 | } |
| 358 | if constexpr (Spec::has_images) { | 357 | if constexpr (Spec::has_images) { |
| @@ -445,10 +444,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 445 | program_manager.BindSourcePrograms(source_programs); | 444 | program_manager.BindSourcePrograms(source_programs); |
| 446 | } | 445 | } |
| 447 | const VideoCommon::ImageViewInOut* views_it{views.data()}; | 446 | const VideoCommon::ImageViewInOut* views_it{views.data()}; |
| 447 | const VideoCommon::SamplerId* samplers_it{samplers.data()}; | ||
| 448 | GLsizei texture_binding = 0; | 448 | GLsizei texture_binding = 0; |
| 449 | GLsizei image_binding = 0; | 449 | GLsizei image_binding = 0; |
| 450 | GLsizei sampler_binding{}; | ||
| 450 | std::array<GLuint, MAX_TEXTURES> textures; | 451 | std::array<GLuint, MAX_TEXTURES> textures; |
| 451 | std::array<GLuint, MAX_IMAGES> images; | 452 | std::array<GLuint, MAX_IMAGES> images; |
| 453 | std::array<GLuint, MAX_TEXTURES> gl_samplers; | ||
| 452 | const auto prepare_stage{[&](size_t stage) { | 454 | const auto prepare_stage{[&](size_t stage) { |
| 453 | buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]); | 455 | buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]); |
| 454 | buffer_cache.BindHostStageBuffers(stage); | 456 | buffer_cache.BindHostStageBuffers(stage); |
| @@ -465,6 +467,13 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 465 | u32 stage_image_binding{}; | 467 | u32 stage_image_binding{}; |
| 466 | 468 | ||
| 467 | const auto& info{stage_infos[stage]}; | 469 | const auto& info{stage_infos[stage]}; |
| 470 | if constexpr (Spec::has_texture_buffers) { | ||
| 471 | for (const auto& desc : info.texture_buffer_descriptors) { | ||
| 472 | for (u32 index = 0; index < desc.count; ++index) { | ||
| 473 | gl_samplers[sampler_binding++] = 0; | ||
| 474 | } | ||
| 475 | } | ||
| 476 | } | ||
| 468 | for (const auto& desc : info.texture_descriptors) { | 477 | for (const auto& desc : info.texture_descriptors) { |
| 469 | for (u32 index = 0; index < desc.count; ++index) { | 478 | for (u32 index = 0; index < desc.count; ++index) { |
| 470 | ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; | 479 | ImageView& image_view{texture_cache.GetImageView((views_it++)->id)}; |
| @@ -474,6 +483,12 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 474 | } | 483 | } |
| 475 | ++texture_binding; | 484 | ++texture_binding; |
| 476 | ++stage_texture_binding; | 485 | ++stage_texture_binding; |
| 486 | |||
| 487 | const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))}; | ||
| 488 | const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && | ||
| 489 | !image_view.SupportsAnisotropy()}; | ||
| 490 | gl_samplers[sampler_binding++] = | ||
| 491 | use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle(); | ||
| 477 | } | 492 | } |
| 478 | } | 493 | } |
| 479 | for (const auto& desc : info.image_descriptors) { | 494 | for (const auto& desc : info.image_descriptors) { |
| @@ -534,7 +549,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 534 | if (texture_binding != 0) { | 549 | if (texture_binding != 0) { |
| 535 | ASSERT(texture_binding == sampler_binding); | 550 | ASSERT(texture_binding == sampler_binding); |
| 536 | glBindTextures(0, texture_binding, textures.data()); | 551 | glBindTextures(0, texture_binding, textures.data()); |
| 537 | glBindSamplers(0, sampler_binding, samplers.data()); | 552 | glBindSamplers(0, sampler_binding, gl_samplers.data()); |
| 538 | } | 553 | } |
| 539 | if (image_binding != 0) { | 554 | if (image_binding != 0) { |
| 540 | glBindImageTextures(0, image_binding, images.data()); | 555 | glBindImageTextures(0, image_binding, images.data()); |
diff --git a/src/video_core/renderer_opengl/gl_shader_context.h b/src/video_core/renderer_opengl/gl_shader_context.h index 207a75d42..d12cd06fa 100644 --- a/src/video_core/renderer_opengl/gl_shader_context.h +++ b/src/video_core/renderer_opengl/gl_shader_context.h | |||
| @@ -16,9 +16,9 @@ struct ShaderPools { | |||
| 16 | inst.ReleaseContents(); | 16 | inst.ReleaseContents(); |
| 17 | } | 17 | } |
| 18 | 18 | ||
| 19 | Shader::ObjectPool<Shader::IR::Inst> inst; | 19 | Shader::ObjectPool<Shader::IR::Inst> inst{8192}; |
| 20 | Shader::ObjectPool<Shader::IR::Block> block; | 20 | Shader::ObjectPool<Shader::IR::Block> block{32}; |
| 21 | Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block; | 21 | Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32}; |
| 22 | }; | 22 | }; |
| 23 | 23 | ||
| 24 | struct Context { | 24 | struct Context { |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index 1c5dbcdd8..3b446be07 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp | |||
| @@ -1268,36 +1268,48 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const TSCEntry& config) { | |||
| 1268 | 1268 | ||
| 1269 | UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1); | 1269 | UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1); |
| 1270 | 1270 | ||
| 1271 | sampler.Create(); | 1271 | const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f); |
| 1272 | const GLuint handle = sampler.handle; | 1272 | |
| 1273 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u)); | 1273 | const auto create_sampler = [&](const f32 anisotropy) { |
| 1274 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v)); | 1274 | OGLSampler new_sampler; |
| 1275 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p)); | 1275 | new_sampler.Create(); |
| 1276 | glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode); | 1276 | const GLuint handle = new_sampler.handle; |
| 1277 | glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func); | 1277 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u)); |
| 1278 | glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag); | 1278 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v)); |
| 1279 | glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min); | 1279 | glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p)); |
| 1280 | glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias()); | 1280 | glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode); |
| 1281 | glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod()); | 1281 | glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func); |
| 1282 | glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod()); | 1282 | glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag); |
| 1283 | glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data()); | 1283 | glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min); |
| 1284 | 1284 | glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias()); | |
| 1285 | if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) { | 1285 | glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod()); |
| 1286 | const f32 max_anisotropy = std::clamp(config.MaxAnisotropy(), 1.0f, 16.0f); | 1286 | glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod()); |
| 1287 | glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropy); | 1287 | glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data()); |
| 1288 | } else { | 1288 | |
| 1289 | LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required"); | 1289 | if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) { |
| 1290 | } | 1290 | glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, anisotropy); |
| 1291 | if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) { | 1291 | } else { |
| 1292 | glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter); | 1292 | LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required"); |
| 1293 | } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) { | 1293 | } |
| 1294 | LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required"); | 1294 | if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) { |
| 1295 | } | 1295 | glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter); |
| 1296 | if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) { | 1296 | } else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) { |
| 1297 | glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless); | 1297 | LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required"); |
| 1298 | } else if (seamless == GL_FALSE) { | 1298 | } |
| 1299 | // We default to false because it's more common | 1299 | if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) { |
| 1300 | LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required"); | 1300 | glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless); |
| 1301 | } else if (seamless == GL_FALSE) { | ||
| 1302 | // We default to false because it's more common | ||
| 1303 | LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required"); | ||
| 1304 | } | ||
| 1305 | return new_sampler; | ||
| 1306 | }; | ||
| 1307 | |||
| 1308 | sampler = create_sampler(max_anisotropy); | ||
| 1309 | |||
| 1310 | const f32 max_anisotropy_default = static_cast<f32>(1U << config.max_anisotropy); | ||
| 1311 | if (max_anisotropy > max_anisotropy_default) { | ||
| 1312 | sampler_default_anisotropy = create_sampler(max_anisotropy_default); | ||
| 1301 | } | 1313 | } |
| 1302 | } | 1314 | } |
| 1303 | 1315 | ||
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 1148b73d7..3676eaaa9 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h | |||
| @@ -309,12 +309,21 @@ class Sampler { | |||
| 309 | public: | 309 | public: |
| 310 | explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); | 310 | explicit Sampler(TextureCacheRuntime&, const Tegra::Texture::TSCEntry&); |
| 311 | 311 | ||
| 312 | GLuint Handle() const noexcept { | 312 | [[nodiscard]] GLuint Handle() const noexcept { |
| 313 | return sampler.handle; | 313 | return sampler.handle; |
| 314 | } | 314 | } |
| 315 | 315 | ||
| 316 | [[nodiscard]] GLuint HandleWithDefaultAnisotropy() const noexcept { | ||
| 317 | return sampler_default_anisotropy.handle; | ||
| 318 | } | ||
| 319 | |||
| 320 | [[nodiscard]] bool HasAddedAnisotropy() const noexcept { | ||
| 321 | return static_cast<bool>(sampler_default_anisotropy.handle); | ||
| 322 | } | ||
| 323 | |||
| 316 | private: | 324 | private: |
| 317 | OGLSampler sampler; | 325 | OGLSampler sampler; |
| 326 | OGLSampler sampler_default_anisotropy; | ||
| 318 | }; | 327 | }; |
| 319 | 328 | ||
| 320 | class Framebuffer { | 329 | class Framebuffer { |
diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 983e1c2e1..71c783709 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h | |||
| @@ -178,7 +178,7 @@ public: | |||
| 178 | inline void PushImageDescriptors(TextureCache& texture_cache, | 178 | inline void PushImageDescriptors(TextureCache& texture_cache, |
| 179 | GuestDescriptorQueue& guest_descriptor_queue, | 179 | GuestDescriptorQueue& guest_descriptor_queue, |
| 180 | const Shader::Info& info, RescalingPushConstant& rescaling, | 180 | const Shader::Info& info, RescalingPushConstant& rescaling, |
| 181 | const VkSampler*& samplers, | 181 | const VideoCommon::SamplerId*& samplers, |
| 182 | const VideoCommon::ImageViewInOut*& views) { | 182 | const VideoCommon::ImageViewInOut*& views) { |
| 183 | const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors); | 183 | const u32 num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors); |
| 184 | const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors); | 184 | const u32 num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors); |
| @@ -187,10 +187,15 @@ inline void PushImageDescriptors(TextureCache& texture_cache, | |||
| 187 | for (const auto& desc : info.texture_descriptors) { | 187 | for (const auto& desc : info.texture_descriptors) { |
| 188 | for (u32 index = 0; index < desc.count; ++index) { | 188 | for (u32 index = 0; index < desc.count; ++index) { |
| 189 | const VideoCommon::ImageViewId image_view_id{(views++)->id}; | 189 | const VideoCommon::ImageViewId image_view_id{(views++)->id}; |
| 190 | const VkSampler sampler{*(samplers++)}; | 190 | const VideoCommon::SamplerId sampler_id{*(samplers++)}; |
| 191 | ImageView& image_view{texture_cache.GetImageView(image_view_id)}; | 191 | ImageView& image_view{texture_cache.GetImageView(image_view_id)}; |
| 192 | const VkImageView vk_image_view{image_view.Handle(desc.type)}; | 192 | const VkImageView vk_image_view{image_view.Handle(desc.type)}; |
| 193 | guest_descriptor_queue.AddSampledImage(vk_image_view, sampler); | 193 | const Sampler& sampler{texture_cache.GetSampler(sampler_id)}; |
| 194 | const bool use_fallback_sampler{sampler.HasAddedAnisotropy() && | ||
| 195 | !image_view.SupportsAnisotropy()}; | ||
| 196 | const VkSampler vk_sampler{use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() | ||
| 197 | : sampler.Handle()}; | ||
| 198 | guest_descriptor_queue.AddSampledImage(vk_image_view, vk_sampler); | ||
| 194 | rescaling.PushTexture(texture_cache.IsRescaling(image_view)); | 199 | rescaling.PushTexture(texture_cache.IsRescaling(image_view)); |
| 195 | } | 200 | } |
| 196 | } | 201 | } |
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 733e70d9d..73e585c2b 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp | |||
| @@ -115,7 +115,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, | |||
| 115 | 115 | ||
| 116 | static constexpr size_t max_elements = 64; | 116 | static constexpr size_t max_elements = 64; |
| 117 | boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views; | 117 | boost::container::static_vector<VideoCommon::ImageViewInOut, max_elements> views; |
| 118 | boost::container::static_vector<VkSampler, max_elements> samplers; | 118 | boost::container::static_vector<VideoCommon::SamplerId, max_elements> samplers; |
| 119 | 119 | ||
| 120 | const auto& qmd{kepler_compute.launch_description}; | 120 | const auto& qmd{kepler_compute.launch_description}; |
| 121 | const auto& cbufs{qmd.const_buffer_config}; | 121 | const auto& cbufs{qmd.const_buffer_config}; |
| @@ -160,8 +160,8 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, | |||
| 160 | const auto handle{read_handle(desc, index)}; | 160 | const auto handle{read_handle(desc, index)}; |
| 161 | views.push_back({handle.first}); | 161 | views.push_back({handle.first}); |
| 162 | 162 | ||
| 163 | Sampler* const sampler = texture_cache.GetComputeSampler(handle.second); | 163 | VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second); |
| 164 | samplers.push_back(sampler->Handle()); | 164 | samplers.push_back(sampler); |
| 165 | } | 165 | } |
| 166 | } | 166 | } |
| 167 | for (const auto& desc : info.image_descriptors) { | 167 | for (const auto& desc : info.image_descriptors) { |
| @@ -192,7 +192,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, | |||
| 192 | buffer_cache.BindHostComputeBuffers(); | 192 | buffer_cache.BindHostComputeBuffers(); |
| 193 | 193 | ||
| 194 | RescalingPushConstant rescaling; | 194 | RescalingPushConstant rescaling; |
| 195 | const VkSampler* samplers_it{samplers.data()}; | 195 | const VideoCommon::SamplerId* samplers_it{samplers.data()}; |
| 196 | const VideoCommon::ImageViewInOut* views_it{views.data()}; | 196 | const VideoCommon::ImageViewInOut* views_it{views.data()}; |
| 197 | PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it, | 197 | PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it, |
| 198 | views_it); | 198 | views_it); |
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 506b78f08..c1595642e 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp | |||
| @@ -298,7 +298,7 @@ void GraphicsPipeline::AddTransition(GraphicsPipeline* transition) { | |||
| 298 | template <typename Spec> | 298 | template <typename Spec> |
| 299 | void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | 299 | void GraphicsPipeline::ConfigureImpl(bool is_indexed) { |
| 300 | std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views; | 300 | std::array<VideoCommon::ImageViewInOut, MAX_IMAGE_ELEMENTS> views; |
| 301 | std::array<VkSampler, MAX_IMAGE_ELEMENTS> samplers; | 301 | std::array<VideoCommon::SamplerId, MAX_IMAGE_ELEMENTS> samplers; |
| 302 | size_t sampler_index{}; | 302 | size_t sampler_index{}; |
| 303 | size_t view_index{}; | 303 | size_t view_index{}; |
| 304 | 304 | ||
| @@ -367,8 +367,8 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 367 | const auto handle{read_handle(desc, index)}; | 367 | const auto handle{read_handle(desc, index)}; |
| 368 | views[view_index++] = {handle.first}; | 368 | views[view_index++] = {handle.first}; |
| 369 | 369 | ||
| 370 | Sampler* const sampler{texture_cache.GetGraphicsSampler(handle.second)}; | 370 | VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)}; |
| 371 | samplers[sampler_index++] = sampler->Handle(); | 371 | samplers[sampler_index++] = sampler; |
| 372 | } | 372 | } |
| 373 | } | 373 | } |
| 374 | if constexpr (Spec::has_images) { | 374 | if constexpr (Spec::has_images) { |
| @@ -453,7 +453,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) { | |||
| 453 | 453 | ||
| 454 | RescalingPushConstant rescaling; | 454 | RescalingPushConstant rescaling; |
| 455 | RenderAreaPushConstant render_area; | 455 | RenderAreaPushConstant render_area; |
| 456 | const VkSampler* samplers_it{samplers.data()}; | 456 | const VideoCommon::SamplerId* samplers_it{samplers.data()}; |
| 457 | const VideoCommon::ImageViewInOut* views_it{views.data()}; | 457 | const VideoCommon::ImageViewInOut* views_it{views.data()}; |
| 458 | const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE { | 458 | const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE { |
| 459 | buffer_cache.BindHostStageBuffers(stage); | 459 | buffer_cache.BindHostStageBuffers(stage); |
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index b128c4f6e..5eeda08d2 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #include <thread> | 4 | #include <thread> |
| 5 | 5 | ||
| 6 | #include "common/polyfill_ranges.h" | ||
| 6 | #include "common/settings.h" | 7 | #include "common/settings.h" |
| 7 | #include "video_core/renderer_vulkan/vk_master_semaphore.h" | 8 | #include "video_core/renderer_vulkan/vk_master_semaphore.h" |
| 8 | #include "video_core/vulkan_common/vulkan_device.h" | 9 | #include "video_core/vulkan_common/vulkan_device.h" |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 15aa7e224..e323ea0fd 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h | |||
| @@ -92,9 +92,9 @@ struct ShaderPools { | |||
| 92 | inst.ReleaseContents(); | 92 | inst.ReleaseContents(); |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | Shader::ObjectPool<Shader::IR::Inst> inst; | 95 | Shader::ObjectPool<Shader::IR::Inst> inst{8192}; |
| 96 | Shader::ObjectPool<Shader::IR::Block> block; | 96 | Shader::ObjectPool<Shader::IR::Block> block{32}; |
| 97 | Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block; | 97 | Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32}; |
| 98 | }; | 98 | }; |
| 99 | 99 | ||
| 100 | class PipelineCache : public VideoCommon::ShaderCache { | 100 | class PipelineCache : public VideoCommon::ShaderCache { |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 8711e2a87..f025f618b 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp | |||
| @@ -1802,27 +1802,36 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t | |||
| 1802 | // Some games have samplers with garbage. Sanitize them here. | 1802 | // Some games have samplers with garbage. Sanitize them here. |
| 1803 | const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); | 1803 | const f32 max_anisotropy = std::clamp(tsc.MaxAnisotropy(), 1.0f, 16.0f); |
| 1804 | 1804 | ||
| 1805 | sampler = device.GetLogical().CreateSampler(VkSamplerCreateInfo{ | 1805 | const auto create_sampler = [&](const f32 anisotropy) { |
| 1806 | .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, | 1806 | return device.GetLogical().CreateSampler(VkSamplerCreateInfo{ |
| 1807 | .pNext = pnext, | 1807 | .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, |
| 1808 | .flags = 0, | 1808 | .pNext = pnext, |
| 1809 | .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), | 1809 | .flags = 0, |
| 1810 | .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), | 1810 | .magFilter = MaxwellToVK::Sampler::Filter(tsc.mag_filter), |
| 1811 | .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), | 1811 | .minFilter = MaxwellToVK::Sampler::Filter(tsc.min_filter), |
| 1812 | .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), | 1812 | .mipmapMode = MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter), |
| 1813 | .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), | 1813 | .addressModeU = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_u, tsc.mag_filter), |
| 1814 | .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), | 1814 | .addressModeV = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_v, tsc.mag_filter), |
| 1815 | .mipLodBias = tsc.LodBias(), | 1815 | .addressModeW = MaxwellToVK::Sampler::WrapMode(device, tsc.wrap_p, tsc.mag_filter), |
| 1816 | .anisotropyEnable = static_cast<VkBool32>(max_anisotropy > 1.0f ? VK_TRUE : VK_FALSE), | 1816 | .mipLodBias = tsc.LodBias(), |
| 1817 | .maxAnisotropy = max_anisotropy, | 1817 | .anisotropyEnable = static_cast<VkBool32>(anisotropy > 1.0f ? VK_TRUE : VK_FALSE), |
| 1818 | .compareEnable = tsc.depth_compare_enabled, | 1818 | .maxAnisotropy = anisotropy, |
| 1819 | .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), | 1819 | .compareEnable = tsc.depth_compare_enabled, |
| 1820 | .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), | 1820 | .compareOp = MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), |
| 1821 | .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), | 1821 | .minLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.0f : tsc.MinLod(), |
| 1822 | .borderColor = | 1822 | .maxLod = tsc.mipmap_filter == TextureMipmapFilter::None ? 0.25f : tsc.MaxLod(), |
| 1823 | arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color), | 1823 | .borderColor = |
| 1824 | .unnormalizedCoordinates = VK_FALSE, | 1824 | arbitrary_borders ? VK_BORDER_COLOR_FLOAT_CUSTOM_EXT : ConvertBorderColor(color), |
| 1825 | }); | 1825 | .unnormalizedCoordinates = VK_FALSE, |
| 1826 | }); | ||
| 1827 | }; | ||
| 1828 | |||
| 1829 | sampler = create_sampler(max_anisotropy); | ||
| 1830 | |||
| 1831 | const f32 max_anisotropy_default = static_cast<f32>(1U << tsc.max_anisotropy); | ||
| 1832 | if (max_anisotropy > max_anisotropy_default) { | ||
| 1833 | sampler_default_anisotropy = create_sampler(max_anisotropy_default); | ||
| 1834 | } | ||
| 1826 | } | 1835 | } |
| 1827 | 1836 | ||
| 1828 | Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers, | 1837 | Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers, |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 0f7a5ffd4..f14525dcb 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h | |||
| @@ -279,8 +279,17 @@ public: | |||
| 279 | return *sampler; | 279 | return *sampler; |
| 280 | } | 280 | } |
| 281 | 281 | ||
| 282 | [[nodiscard]] VkSampler HandleWithDefaultAnisotropy() const noexcept { | ||
| 283 | return *sampler_default_anisotropy; | ||
| 284 | } | ||
| 285 | |||
| 286 | [[nodiscard]] bool HasAddedAnisotropy() const noexcept { | ||
| 287 | return static_cast<bool>(sampler_default_anisotropy); | ||
| 288 | } | ||
| 289 | |||
| 282 | private: | 290 | private: |
| 283 | vk::Sampler sampler; | 291 | vk::Sampler sampler; |
| 292 | vk::Sampler sampler_default_anisotropy; | ||
| 284 | }; | 293 | }; |
| 285 | 294 | ||
| 286 | class Framebuffer { | 295 | class Framebuffer { |
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp index d134b6738..0c5f4450d 100644 --- a/src/video_core/texture_cache/image_view_base.cpp +++ b/src/video_core/texture_cache/image_view_base.cpp | |||
| @@ -45,4 +45,56 @@ ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_in | |||
| 45 | 45 | ||
| 46 | ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {} | 46 | ImageViewBase::ImageViewBase(const NullImageViewParams&) : image_id{NULL_IMAGE_ID} {} |
| 47 | 47 | ||
| 48 | bool ImageViewBase::SupportsAnisotropy() const noexcept { | ||
| 49 | const bool has_mips = range.extent.levels > 1; | ||
| 50 | const bool is_2d = type == ImageViewType::e2D || type == ImageViewType::e2DArray; | ||
| 51 | if (!has_mips || !is_2d) { | ||
| 52 | return false; | ||
| 53 | } | ||
| 54 | |||
| 55 | switch (format) { | ||
| 56 | case PixelFormat::R8_UNORM: | ||
| 57 | case PixelFormat::R8_SNORM: | ||
| 58 | case PixelFormat::R8_SINT: | ||
| 59 | case PixelFormat::R8_UINT: | ||
| 60 | case PixelFormat::BC4_UNORM: | ||
| 61 | case PixelFormat::BC4_SNORM: | ||
| 62 | case PixelFormat::BC5_UNORM: | ||
| 63 | case PixelFormat::BC5_SNORM: | ||
| 64 | case PixelFormat::R32G32_FLOAT: | ||
| 65 | case PixelFormat::R32G32_SINT: | ||
| 66 | case PixelFormat::R32_FLOAT: | ||
| 67 | case PixelFormat::R16_FLOAT: | ||
| 68 | case PixelFormat::R16_UNORM: | ||
| 69 | case PixelFormat::R16_SNORM: | ||
| 70 | case PixelFormat::R16_UINT: | ||
| 71 | case PixelFormat::R16_SINT: | ||
| 72 | case PixelFormat::R16G16_UNORM: | ||
| 73 | case PixelFormat::R16G16_FLOAT: | ||
| 74 | case PixelFormat::R16G16_UINT: | ||
| 75 | case PixelFormat::R16G16_SINT: | ||
| 76 | case PixelFormat::R16G16_SNORM: | ||
| 77 | case PixelFormat::R8G8_UNORM: | ||
| 78 | case PixelFormat::R8G8_SNORM: | ||
| 79 | case PixelFormat::R8G8_SINT: | ||
| 80 | case PixelFormat::R8G8_UINT: | ||
| 81 | case PixelFormat::R32G32_UINT: | ||
| 82 | case PixelFormat::R32_UINT: | ||
| 83 | case PixelFormat::R32_SINT: | ||
| 84 | case PixelFormat::G4R4_UNORM: | ||
| 85 | // Depth formats | ||
| 86 | case PixelFormat::D32_FLOAT: | ||
| 87 | case PixelFormat::D16_UNORM: | ||
| 88 | // Stencil formats | ||
| 89 | case PixelFormat::S8_UINT: | ||
| 90 | // DepthStencil formats | ||
| 91 | case PixelFormat::D24_UNORM_S8_UINT: | ||
| 92 | case PixelFormat::S8_UINT_D24_UNORM: | ||
| 93 | case PixelFormat::D32_FLOAT_S8_UINT: | ||
| 94 | return false; | ||
| 95 | default: | ||
| 96 | return true; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 48 | } // namespace VideoCommon | 100 | } // namespace VideoCommon |
diff --git a/src/video_core/texture_cache/image_view_base.h b/src/video_core/texture_cache/image_view_base.h index a25ae1d4a..87549ffff 100644 --- a/src/video_core/texture_cache/image_view_base.h +++ b/src/video_core/texture_cache/image_view_base.h | |||
| @@ -33,6 +33,8 @@ struct ImageViewBase { | |||
| 33 | return type == ImageViewType::Buffer; | 33 | return type == ImageViewType::Buffer; |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | [[nodiscard]] bool SupportsAnisotropy() const noexcept; | ||
| 37 | |||
| 36 | ImageId image_id{}; | 38 | ImageId image_id{}; |
| 37 | GPUVAddr gpu_addr = 0; | 39 | GPUVAddr gpu_addr = 0; |
| 38 | PixelFormat format{}; | 40 | PixelFormat format{}; |
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index c7f7448e9..4027d860b 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h | |||
| @@ -222,30 +222,50 @@ void TextureCache<P>::CheckFeedbackLoop(std::span<const ImageViewInOut> views) { | |||
| 222 | 222 | ||
| 223 | template <class P> | 223 | template <class P> |
| 224 | typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) { | 224 | typename P::Sampler* TextureCache<P>::GetGraphicsSampler(u32 index) { |
| 225 | return &slot_samplers[GetGraphicsSamplerId(index)]; | ||
| 226 | } | ||
| 227 | |||
| 228 | template <class P> | ||
| 229 | typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) { | ||
| 230 | return &slot_samplers[GetComputeSamplerId(index)]; | ||
| 231 | } | ||
| 232 | |||
| 233 | template <class P> | ||
| 234 | SamplerId TextureCache<P>::GetGraphicsSamplerId(u32 index) { | ||
| 225 | if (index > channel_state->graphics_sampler_table.Limit()) { | 235 | if (index > channel_state->graphics_sampler_table.Limit()) { |
| 226 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); | 236 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); |
| 227 | return &slot_samplers[NULL_SAMPLER_ID]; | 237 | return NULL_SAMPLER_ID; |
| 228 | } | 238 | } |
| 229 | const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index); | 239 | const auto [descriptor, is_new] = channel_state->graphics_sampler_table.Read(index); |
| 230 | SamplerId& id = channel_state->graphics_sampler_ids[index]; | 240 | SamplerId& id = channel_state->graphics_sampler_ids[index]; |
| 231 | if (is_new) { | 241 | if (is_new) { |
| 232 | id = FindSampler(descriptor); | 242 | id = FindSampler(descriptor); |
| 233 | } | 243 | } |
| 234 | return &slot_samplers[id]; | 244 | return id; |
| 235 | } | 245 | } |
| 236 | 246 | ||
| 237 | template <class P> | 247 | template <class P> |
| 238 | typename P::Sampler* TextureCache<P>::GetComputeSampler(u32 index) { | 248 | SamplerId TextureCache<P>::GetComputeSamplerId(u32 index) { |
| 239 | if (index > channel_state->compute_sampler_table.Limit()) { | 249 | if (index > channel_state->compute_sampler_table.Limit()) { |
| 240 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); | 250 | LOG_DEBUG(HW_GPU, "Invalid sampler index={}", index); |
| 241 | return &slot_samplers[NULL_SAMPLER_ID]; | 251 | return NULL_SAMPLER_ID; |
| 242 | } | 252 | } |
| 243 | const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index); | 253 | const auto [descriptor, is_new] = channel_state->compute_sampler_table.Read(index); |
| 244 | SamplerId& id = channel_state->compute_sampler_ids[index]; | 254 | SamplerId& id = channel_state->compute_sampler_ids[index]; |
| 245 | if (is_new) { | 255 | if (is_new) { |
| 246 | id = FindSampler(descriptor); | 256 | id = FindSampler(descriptor); |
| 247 | } | 257 | } |
| 248 | return &slot_samplers[id]; | 258 | return id; |
| 259 | } | ||
| 260 | |||
| 261 | template <class P> | ||
| 262 | const typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) const noexcept { | ||
| 263 | return slot_samplers[id]; | ||
| 264 | } | ||
| 265 | |||
| 266 | template <class P> | ||
| 267 | typename P::Sampler& TextureCache<P>::GetSampler(SamplerId id) noexcept { | ||
| 268 | return slot_samplers[id]; | ||
| 249 | } | 269 | } |
| 250 | 270 | ||
| 251 | template <class P> | 271 | template <class P> |
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index 3bfa92154..d96ddea9d 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h | |||
| @@ -159,6 +159,18 @@ public: | |||
| 159 | /// Get the sampler from the compute descriptor table in the specified index | 159 | /// Get the sampler from the compute descriptor table in the specified index |
| 160 | Sampler* GetComputeSampler(u32 index); | 160 | Sampler* GetComputeSampler(u32 index); |
| 161 | 161 | ||
| 162 | /// Get the sampler id from the graphics descriptor table in the specified index | ||
| 163 | SamplerId GetGraphicsSamplerId(u32 index); | ||
| 164 | |||
| 165 | /// Get the sampler id from the compute descriptor table in the specified index | ||
| 166 | SamplerId GetComputeSamplerId(u32 index); | ||
| 167 | |||
| 168 | /// Return a constant reference to the given sampler id | ||
| 169 | [[nodiscard]] const Sampler& GetSampler(SamplerId id) const noexcept; | ||
| 170 | |||
| 171 | /// Return a reference to the given sampler id | ||
| 172 | [[nodiscard]] Sampler& GetSampler(SamplerId id) noexcept; | ||
| 173 | |||
| 162 | /// Refresh the state for graphics image view and sampler descriptors | 174 | /// Refresh the state for graphics image view and sampler descriptors |
| 163 | void SynchronizeGraphicsDescriptors(); | 175 | void SynchronizeGraphicsDescriptors(); |
| 164 | 176 | ||
diff --git a/src/video_core/textures/texture.cpp b/src/video_core/textures/texture.cpp index 4a80a59f9..d8b88d9bc 100644 --- a/src/video_core/textures/texture.cpp +++ b/src/video_core/textures/texture.cpp | |||
| @@ -62,7 +62,12 @@ std::array<float, 4> TSCEntry::BorderColor() const noexcept { | |||
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | float TSCEntry::MaxAnisotropy() const noexcept { | 64 | float TSCEntry::MaxAnisotropy() const noexcept { |
| 65 | if (max_anisotropy == 0 && mipmap_filter != TextureMipmapFilter::Linear) { | 65 | const bool is_suitable_mipmap_filter = mipmap_filter != TextureMipmapFilter::None; |
| 66 | const bool has_regular_lods = min_lod_clamp == 0 && max_lod_clamp >= 256; | ||
| 67 | const bool is_bilinear_filter = min_filter == TextureFilter::Linear && | ||
| 68 | reduction_filter == SamplerReduction::WeightedAverage; | ||
| 69 | if (max_anisotropy == 0 && (!is_suitable_mipmap_filter || !has_regular_lods || | ||
| 70 | !is_bilinear_filter || depth_compare_enabled)) { | ||
| 66 | return 1.0f; | 71 | return 1.0f; |
| 67 | } | 72 | } |
| 68 | const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue(); | 73 | const auto anisotropic_settings = Settings::values.max_anisotropy.GetValue(); |